content: package com.fernandocejas.frodo.aspect;
  
  import com.fernandocejas.frodo.internal.Counter;
  import com.fernandocejas.frodo.internal.MessageManager;
  import com.fernandocejas.frodo.internal.StopWatch;
  import com.fernandocejas.frodo.joinpoint.FrodoJoinPoint;
  import org.aspectj.lang.JoinPoint;
  import org.aspectj.lang.annotation.After;
  import org.aspectj.lang.annotation.Aspect;
  import org.aspectj.lang.annotation.Before;
  import org.aspectj.lang.annotation.Pointcut;
  import rx.Subscriber;
  
  @Aspect
  public class LogSubscriber {
    private static final String CLASS =
        "within(@com.fernandocejas.frodo.annotation.RxLogSubscriber *) && if()";
  
    private static final String METHOD_ON_START = "execution(void *.onStart())";
    private static final String METHOD_ON_NEXT = "execution(void *.onNext(..))";
    private static final String METHOD_ON_ERROR =
        "execution(void *.onError(java.lang.Throwable)) && args(throwable)";
    private static final String METHOD_ON_COMPLETED = "execution(void *.onCompleted())";
  
    private static final String METHOD_REQUEST = "call(void *.request(long)) && args(numberOfItems)";
    private static final String METHOD_UN_SUBSCRIBE = "call(void *.unsubscribe())";
  
    private final Counter counter;
    private final StopWatch stopWatch;
    private final MessageManager messageManager;
    private boolean isFirstElementEmitted = true;
  
    public LogSubscriber() {
      this(new Counter(), new StopWatch(), new MessageManager());
    }
  
    public LogSubscriber(Counter counter, StopWatch stopWatch, MessageManager messageManager) {
      this.counter = counter;
      this.stopWatch = stopWatch;
      this.messageManager = messageManager;
    }
  
    @Pointcut(CLASS)
    public static boolean classAnnotatedWithRxLogSubscriber(JoinPoint joinPoint) {
      return joinPoint.getTarget() instanceof Subscriber;
    }
  
    @Pointcut(METHOD_ON_START)
    public void onStartMethodExecution() {
    }
  
    @Pointcut(METHOD_ON_NEXT)
    public void onNextMethodExecution() {
    }
  
    @Pointcut(METHOD_ON_ERROR)
    public void onErrorMethodExecution(Throwable throwable) {
    }
  
    @Pointcut(METHOD_ON_COMPLETED)
    public void onCompletedMethodExecution() {
    }
  
    @Pointcut(METHOD_REQUEST)
    public void onRequestMethodCall(long numberOfItems) {
    }
  
    @Pointcut(METHOD_UN_SUBSCRIBE)
    public void onUnsubscribeMethodCall() {
    }
  
    @Before("classAnnotatedWithRxLogSubscriber(joinPoint) && onStartMethodExecution()")
    public void beforeOnStartExecution(JoinPoint joinPoint) {
      messageManager.printSubscriberOnStart(joinPoint.getTarget().getClass().getSimpleName());
    }
  
    @Before("classAnnotatedWithRxLogSubscriber(joinPoint) && onNextMethodExecution()")
    public void beforeOnNextExecution(JoinPoint joinPoint) {
      countAndMeasureTime();
      final FrodoJoinPoint frodoJoinPoint = new FrodoJoinPoint(joinPoint);
      final Object value = frodoJoinPoint.getMethodParamValuesList().isEmpty() ? null
          : frodoJoinPoint.getMethodParamValuesList().get(0);
      messageManager.printSubscriberOnNext(joinPoint.getTarget().getClass().getSimpleName(), value,
          Thread.currentThread().getName());
    }
  
    @After(value = "classAnnotatedWithRxLogSubscriber(joinPoint) && onErrorMethodExecution(throwable)",
        argNames = "joinPoint,throwable")
    public void afterOnErrorExecution(JoinPoint joinPoint, Throwable throwable) {
      stopWatch.stop();
      messageManager.printSubscriberOnError(joinPoint.getTarget().getClass().getSimpleName(),
          throwable.toString(),
          stopWatch.getTotalTimeMillis(), counter.tally());
      resetCounters();
    }
  
    @Before("classAnnotatedWithRxLogSubscriber(joinPoint) && onCompletedMethodExecution()")
    public void beforeOnCompletedExecution(JoinPoint joinPoint) {
      stopWatch.stop();
      messageManager.printSubscriberOnCompleted(joinPoint.getTarget().getClass().getSimpleName(),
          stopWatch.getTotalTimeMillis(), counter.tally());
      resetCounters();
    }
  
    @After("classAnnotatedWithRxLogSubscriber(joinPoint) && onUnsubscribeMethodCall()")
    public void afterUnsubscribeMethodCall(JoinPoint joinPoint) {
      messageManager.printSubscriberUnsubscribe(joinPoint.getTarget().getClass().getSimpleName());
    }
  
    @After("classAnnotatedWithRxLogSubscriber(joinPoint) && onRequestMethodCall(numberOfItems)")
    public void afterRequestMethodCall(JoinPoint joinPoint, long numberOfItems) {
      messageManager.printSubscriberRequestedItems(joinPoint.getTarget().getClass().getSimpleName(),
          numberOfItems);
    }
  
    private void countAndMeasureTime() {
      counter.increment();
      if (isFirstElementEmitted) {
        stopWatch.start();
      }
      isFirstElementEmitted = false;
    }
  
    private void resetCounters() {
      isFirstElementEmitted = true;
      counter.clear();
      stopWatch.reset();
    }
  }
  