>() {
19 | @Override public void call(Notification super T> notification) {
20 | if (!observableInfo.getSubscribeOnThread().isPresent()
21 | && (notification.isOnNext() || notification.isOnError())) {
22 | observableInfo.setSubscribeOnThread(Thread.currentThread().getName());
23 | }
24 | }
25 | })
26 | .doOnUnsubscribe(new Action0() {
27 | @Override
28 | public void call() {
29 | if (!observableInfo.getObserveOnThread().isPresent()) {
30 | observableInfo.setObserveOnThread(Thread.currentThread().getName());
31 | }
32 | messageManager.printObservableThreadInfo(observableInfo);
33 | }
34 | });
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/frodo-runtime/src/main/java/com/fernandocejas/frodo/core/collections/UnmodifiableIterator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2008 The Guava Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.fernandocejas.frodo.core.collections;
18 |
19 | import java.util.Iterator;
20 |
21 | /**
22 | * An iterator that does not support {@link #remove}.
23 | *
24 | * @author Jared Levy
25 | * @since 2.0 (imported from Google Collections Library)
26 | *
27 | * This class contains code derived from Google
28 | * Guava
29 | */
30 | public abstract class UnmodifiableIterator implements Iterator {
31 | /**
32 | * Constructor for use by subclasses.
33 | */
34 | protected UnmodifiableIterator() {
35 | }
36 |
37 | /**
38 | * Guaranteed to throw an exception and leave the underlying data unmodified.
39 | *
40 | * @throws UnsupportedOperationException always
41 | * @deprecated Unsupported operation.
42 | */
43 | @Override
44 | public final void remove() {
45 | throw new UnsupportedOperationException();
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/frodo-runtime/src/test/java/com/fernandocejas/frodo/AndroidUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.fernandocejas.frodo;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 | import android.content.res.Resources;
6 | import java.util.HashMap;
7 | import java.util.Map;
8 | import org.junit.Rule;
9 | import org.junit.rules.TestRule;
10 | import org.junit.runner.Description;
11 | import org.junit.runner.RunWith;
12 | import org.junit.runners.model.Statement;
13 | import org.mockito.MockitoAnnotations;
14 | import org.robolectric.RobolectricGradleTestRunner;
15 | import org.robolectric.RuntimeEnvironment;
16 | import org.robolectric.annotation.Config;
17 | import org.robolectric.fakes.RoboSharedPreferences;
18 |
19 | /**
20 | * Base class for Robolectric tests.
21 | * Inherit from this class to create a test.
22 | */
23 | @RunWith(RobolectricGradleTestRunner.class)
24 | @Config(constants = BuildConfig.class,
25 | application = ApplicationStub.class,
26 | sdk = 21)
27 | public abstract class AndroidUnitTest {
28 | @Rule public TestRule injectMocksRule = new TestRule() {
29 | @Override
30 | public Statement apply(Statement base, Description description) {
31 | MockitoAnnotations.initMocks(AndroidUnitTest.this);
32 | return base;
33 | }
34 | };
35 |
36 | protected static Context context() {
37 | return RuntimeEnvironment.application;
38 | }
39 |
40 | protected static Resources resources() {
41 | return context().getResources();
42 | }
43 |
44 | protected static SharedPreferences sharedPreferences(String name, int mode) {
45 | return new RoboSharedPreferences(new HashMap>(), name, mode);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/frodo-android-sample/src/main/res/layout/activity_samples.xml:
--------------------------------------------------------------------------------
1 |
13 |
14 |
21 |
22 |
30 |
31 |
37 |
38 |
44 |
45 |
--------------------------------------------------------------------------------
/frodo-runtime/src/test/java/com/fernandocejas/frodo/internal/observable/LogEventsObservableTest.java:
--------------------------------------------------------------------------------
1 | package com.fernandocejas.frodo.internal.observable;
2 |
3 | import com.fernandocejas.frodo.internal.MessageManager;
4 | import org.junit.Before;
5 | import org.junit.Rule;
6 | import org.junit.Test;
7 | import org.junit.runner.RunWith;
8 | import org.mockito.Mock;
9 | import org.mockito.runners.MockitoJUnitRunner;
10 | import rx.observers.TestSubscriber;
11 |
12 | import static org.mockito.Matchers.any;
13 | import static org.mockito.Mockito.verify;
14 | import static org.mockito.Mockito.verifyNoMoreInteractions;
15 |
16 | @SuppressWarnings("unchecked") @RunWith(MockitoJUnitRunner.class)
17 | public class LogEventsObservableTest {
18 |
19 | @Rule public ObservableRule observableRule = new ObservableRule(this.getClass());
20 |
21 | private LogEventsObservable loggableObservable;
22 | private TestSubscriber subscriber;
23 |
24 | @Mock private MessageManager messageManager;
25 |
26 | @Before
27 | public void setUp() {
28 | subscriber = new TestSubscriber();
29 | loggableObservable =
30 | new LogEventsObservable(observableRule.joinPoint(), messageManager, observableRule.info());
31 | }
32 |
33 | @Test
34 | public void shouldLogOnlyObservableEvents() throws Throwable {
35 | loggableObservable.get(observableRule.stringType()).subscribe(subscriber);
36 |
37 | verify(messageManager).printObservableOnSubscribe(any(ObservableInfo.class));
38 | verify(messageManager).printObservableOnNext(any(ObservableInfo.class));
39 | verify(messageManager).printObservableOnCompleted(any(ObservableInfo.class));
40 | verify(messageManager).printObservableOnTerminate(any(ObservableInfo.class));
41 | verify(messageManager).printObservableOnUnsubscribe(any(ObservableInfo.class));
42 | verifyNoMoreInteractions(messageManager);
43 | }
44 | }
--------------------------------------------------------------------------------
/frodo-runtime/src/main/java/com/fernandocejas/frodo/internal/observable/LoggableObservableFactory.java:
--------------------------------------------------------------------------------
1 | package com.fernandocejas.frodo.internal.observable;
2 |
3 | import com.fernandocejas.frodo.annotation.RxLogObservable;
4 | import com.fernandocejas.frodo.internal.MessageManager;
5 | import com.fernandocejas.frodo.joinpoint.FrodoProceedingJoinPoint;
6 | import java.lang.annotation.Annotation;
7 |
8 | @SuppressWarnings("unchecked") public class LoggableObservableFactory {
9 |
10 | private final FrodoProceedingJoinPoint joinPoint;
11 | private final MessageManager messageManager;
12 | private final ObservableInfo observableInfo;
13 |
14 | public LoggableObservableFactory(FrodoProceedingJoinPoint joinPoint,
15 | MessageManager messageManager, ObservableInfo observableInfo) {
16 | this.joinPoint = joinPoint;
17 | this.messageManager = messageManager;
18 | this.observableInfo = observableInfo;
19 | }
20 |
21 | LoggableObservable create(Annotation annotation) {
22 | final Class observableType = joinPoint.getGenericReturnTypes().get(0);
23 | if (annotation != null) {
24 | switch (((RxLogObservable) annotation).value()) {
25 | case NOTHING:
26 | return new LogNothingObservable(joinPoint, messageManager, observableInfo);
27 | case STREAM:
28 | return new LogStreamObservable(joinPoint, messageManager, observableInfo);
29 | case SCHEDULERS:
30 | return new LogSchedulersObservable(joinPoint, messageManager, observableInfo);
31 | case EVENTS:
32 | return new LogEventsObservable(joinPoint, messageManager, observableInfo);
33 | case EVERYTHING:
34 | default:
35 | return new LogEverythingObservable(joinPoint, messageManager, observableInfo);
36 | }
37 | } else {
38 | return new LogNothingObservable(joinPoint, messageManager, observableInfo);
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/frodo-runtime/src/test/java/com/fernandocejas/frodo/internal/CounterTest.java:
--------------------------------------------------------------------------------
1 | package com.fernandocejas.frodo.internal;
2 |
3 | import org.junit.Before;
4 | import org.junit.Test;
5 | import org.junit.runner.RunWith;
6 | import org.mockito.runners.MockitoJUnitRunner;
7 |
8 | import static org.assertj.core.api.Assertions.assertThat;
9 |
10 | @RunWith(MockitoJUnitRunner.class)
11 | public class CounterTest {
12 |
13 | private static final String COUNTER_NAME = "android10";
14 |
15 | private Counter counter;
16 |
17 | @Before
18 | public void setUp() {
19 | counter = new Counter(COUNTER_NAME);
20 | }
21 |
22 | @Test
23 | public void incrementsCounter() {
24 | counter.increment();
25 | counter.increment();
26 | counter.increment();
27 |
28 | assertThat(counter.tally()).isEqualTo(3);
29 | }
30 |
31 | @Test
32 | public void clearsCounter() {
33 | counter.increment();
34 | counter.increment();
35 | assertThat(counter.tally()).isEqualTo(2);
36 |
37 | counter.clear();
38 | assertThat(counter.tally()).isZero();
39 | }
40 |
41 | @Test
42 | public void decrementsCounter() {
43 | counter.increment();
44 | counter.increment();
45 | counter.increment();
46 | counter.decrement();
47 | counter.decrement();
48 |
49 | assertThat(counter.tally()).isEqualTo(1);
50 | }
51 |
52 | @Test
53 | public void comparesCounterValues() {
54 | final Counter anotherCounter = new Counter("myCounter");
55 |
56 | counter.increment();
57 | counter.increment();
58 | assertThat(counter.compareTo(anotherCounter)).isEqualTo(1);
59 |
60 | anotherCounter.increment();
61 | anotherCounter.increment();
62 | assertThat(counter.compareTo(anotherCounter)).isZero();
63 |
64 | counter.decrement();
65 | assertThat(counter.compareTo(anotherCounter)).isEqualTo(-1);
66 | }
67 |
68 | @Test
69 | public void countsNegatives() {
70 | counter.decrement();
71 | counter.decrement();
72 |
73 | assertThat(counter.tally()).isEqualTo(-2);
74 | }
75 | }
--------------------------------------------------------------------------------
/frodo-runtime/src/main/java/com/fernandocejas/frodo/internal/observable/ObservableInfo.java:
--------------------------------------------------------------------------------
1 | package com.fernandocejas.frodo.internal.observable;
2 |
3 | import com.fernandocejas.frodo.core.optional.Optional;
4 | import com.fernandocejas.frodo.joinpoint.FrodoJoinPoint;
5 |
6 | public class ObservableInfo {
7 | private final FrodoJoinPoint joinPoint;
8 |
9 | private String subscribeOnThread;
10 | private String observeOnThread;
11 | private long totalExecutionTime;
12 | private int totalEmittedItems;
13 |
14 | public ObservableInfo(FrodoJoinPoint joinPoint) {
15 | this.joinPoint = joinPoint;
16 | }
17 |
18 | public String getClassSimpleName() {
19 | return joinPoint.getClassSimpleName();
20 | }
21 |
22 | public String getMethodName() {
23 | return joinPoint.getMethodName();
24 | }
25 |
26 | public FrodoJoinPoint getJoinPoint() {
27 | return joinPoint;
28 | }
29 |
30 | public Optional getSubscribeOnThread() {
31 | return Optional.fromNullable(subscribeOnThread);
32 | }
33 |
34 | public Optional getObserveOnThread() {
35 | return Optional.fromNullable(observeOnThread);
36 | }
37 |
38 | public Optional getTotalExecutionTime() {
39 | if (totalExecutionTime == 0) {
40 | return Optional.absent();
41 | }
42 | return Optional.of(totalExecutionTime);
43 | }
44 |
45 | public Optional getTotalEmittedItems() {
46 | if (totalEmittedItems == 0) {
47 | return Optional.absent();
48 | }
49 | return Optional.of(totalEmittedItems);
50 | }
51 |
52 | void setSubscribeOnThread(String subscribeOnThread) {
53 | this.subscribeOnThread = subscribeOnThread;
54 | }
55 |
56 | void setObserveOnThread(String observeOnThread) {
57 | this.observeOnThread = observeOnThread;
58 | }
59 |
60 | void setTotalExecutionTime(long totalExecutionTime) {
61 | this.totalExecutionTime = totalExecutionTime;
62 | }
63 |
64 | void setTotalEmittedItems(int totalEmittedItems) {
65 | this.totalEmittedItems = totalEmittedItems;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/frodo-runtime/src/main/java/com/fernandocejas/frodo/internal/observable/LogEventsObservable.java:
--------------------------------------------------------------------------------
1 | package com.fernandocejas.frodo.internal.observable;
2 |
3 | import com.fernandocejas.frodo.internal.MessageManager;
4 | import com.fernandocejas.frodo.joinpoint.FrodoProceedingJoinPoint;
5 | import rx.Observable;
6 | import rx.functions.Action0;
7 | import rx.functions.Action1;
8 |
9 | @SuppressWarnings("unchecked") class LogEventsObservable extends LoggableObservable {
10 | LogEventsObservable(FrodoProceedingJoinPoint joinPoint,
11 | MessageManager messageManager, ObservableInfo observableInfo) {
12 | super(joinPoint, messageManager, observableInfo);
13 | }
14 |
15 | @Override Observable get(T type) throws Throwable {
16 | return ((Observable) joinPoint.proceed())
17 | .doOnSubscribe(new Action0() {
18 | @Override
19 | public void call() {
20 | messageManager.printObservableOnSubscribe(observableInfo);
21 | }
22 | })
23 | .doOnNext(new Action1() {
24 | @Override
25 | public void call(T value) {
26 | messageManager.printObservableOnNext(observableInfo);
27 | }
28 | })
29 | .doOnError(new Action1() {
30 | @Override
31 | public void call(Throwable throwable) {
32 | messageManager.printObservableOnError(observableInfo, throwable);
33 | }
34 | })
35 | .doOnCompleted(new Action0() {
36 | @Override
37 | public void call() {
38 | messageManager.printObservableOnCompleted(observableInfo);
39 | }
40 | })
41 | .doOnTerminate(new Action0() {
42 | @Override
43 | public void call() {
44 | messageManager.printObservableOnTerminate(observableInfo);
45 | }
46 | })
47 | .doOnUnsubscribe(new Action0() {
48 | @Override
49 | public void call() {
50 | messageManager.printObservableOnUnsubscribe(observableInfo);
51 | }
52 | });
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/frodo-api/src/main/java/com/fernandocejas/frodo/annotation/RxLogObservable.java:
--------------------------------------------------------------------------------
1 | package com.fernandocejas.frodo.annotation;
2 |
3 | import java.lang.annotation.Retention;
4 | import java.lang.annotation.Target;
5 |
6 | import static java.lang.annotation.ElementType.METHOD;
7 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
8 |
9 | /**
10 | * Annotated methods which return rx.Observables will print the following information on
11 | * android logcat when emitting items.
12 | * A {@link Scope} option can be passed to choose different logging scopes.
13 | *
14 | * OUTPUT EXAMPLE:
15 | *
16 | * Frodo => [@Observable :: @InClass -> ObservableSample :: @Method -> names()]
17 | * Frodo => [@Observable#names -> onSubscribe()]
18 | * Frodo => [@Observable#names -> onNext() -> Fernando]
19 | * Frodo => [@Observable#names -> onNext() -> Silvia]
20 | * Frodo => [@Observable#names -> onCompleted()]
21 | * Frodo => [@Observable#names -> onTerminate()]
22 | * Frodo => [@Observable#names -> @Emitted -> 2 elements :: @Time -> 1 ms]
23 | * Frodo => [@Observable#names -> @SubscribeOn -> RxNewThreadScheduler-8 :: @ObserveOn -> main]
24 | * Frodo => [@Observable#names -> onUnsubscribe()]
25 | *
26 | * @see Frodo Documentation
27 | */
28 | @Retention(RUNTIME)
29 | @Target({ METHOD })
30 | public @interface RxLogObservable {
31 | Scope value() default Scope.EVERYTHING;
32 |
33 | /**
34 | * Logging scope of the current annotated rx.Observable.
35 | *
36 | * {@link #EVERYTHING}: Logs stream data, schedulers and rx.Observable events. Default.
37 | * {@link #STREAM}: Logs rx.Observable emitted items plus total execution time.
38 | * {@link #SCHEDULERS}: Logs schedulers where the annotated rx.Observable operates on.
39 | * {@link #EVENTS}: Logs rx.Observable events only.
40 | * {@link #NOTHING}: Turns off logging for the annotated rx.Observable.
41 | */
42 | enum Scope { EVERYTHING, STREAM, SCHEDULERS, EVENTS, NOTHING }
43 | }
44 |
--------------------------------------------------------------------------------
/frodo-runtime/src/main/java/com/fernandocejas/frodo/internal/observable/LogStreamObservable.java:
--------------------------------------------------------------------------------
1 | package com.fernandocejas.frodo.internal.observable;
2 |
3 | import com.fernandocejas.frodo.internal.Counter;
4 | import com.fernandocejas.frodo.internal.MessageManager;
5 | import com.fernandocejas.frodo.internal.StopWatch;
6 | import com.fernandocejas.frodo.joinpoint.FrodoProceedingJoinPoint;
7 | import rx.Observable;
8 | import rx.functions.Action0;
9 | import rx.functions.Action1;
10 |
11 | @SuppressWarnings("unchecked") class LogStreamObservable extends LoggableObservable {
12 | LogStreamObservable(FrodoProceedingJoinPoint joinPoint,
13 | MessageManager messageManager, ObservableInfo observableInfo) {
14 | super(joinPoint, messageManager, observableInfo);
15 | }
16 |
17 | @Override Observable get(T type) throws Throwable {
18 | final StopWatch stopWatch = new StopWatch();
19 | final Counter emittedItems = new Counter(joinPoint.getMethodName());
20 | return ((Observable) joinPoint.proceed())
21 | .doOnSubscribe(new Action0() {
22 | @Override
23 | public void call() {
24 | stopWatch.start();
25 | }
26 | })
27 | .doOnNext(new Action1() {
28 | @Override
29 | public void call(T value) {
30 | emittedItems.increment();
31 | messageManager.printObservableOnNextWithValue(observableInfo, value);
32 | }
33 | })
34 | .doOnError(new Action1() {
35 | @Override
36 | public void call(Throwable throwable) {
37 | messageManager.printObservableOnError(observableInfo, throwable);
38 | }
39 | })
40 | .doOnTerminate(new Action0() {
41 | @Override
42 | public void call() {
43 | stopWatch.stop();
44 | observableInfo.setTotalExecutionTime(stopWatch.getTotalTimeMillis());
45 | observableInfo.setTotalEmittedItems(emittedItems.tally());
46 | messageManager.printObservableItemTimeInfo(observableInfo);
47 | }
48 | });
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/frodo-runtime/src/test/java/com/fernandocejas/frodo/internal/observable/ObservableRule.java:
--------------------------------------------------------------------------------
1 | package com.fernandocejas.frodo.internal.observable;
2 |
3 | import com.fernandocejas.frodo.core.strings.Strings;
4 | import com.fernandocejas.frodo.internal.MessageManager;
5 | import com.fernandocejas.frodo.joinpoint.FrodoProceedingJoinPoint;
6 | import com.fernandocejas.frodo.joinpoint.TestJoinPoint;
7 | import com.fernandocejas.frodo.joinpoint.TestProceedingJoinPoint;
8 | import org.junit.rules.TestRule;
9 | import org.junit.runner.Description;
10 | import org.junit.runners.model.Statement;
11 | import org.mockito.Mock;
12 | import org.mockito.MockitoAnnotations;
13 | import rx.Observable;
14 |
15 | class ObservableRule implements TestRule {
16 |
17 | static final String OBSERVABLE_STREAM_VALUE = "fernando";
18 |
19 | private final Class declaringType;
20 |
21 | private FrodoProceedingJoinPoint frodoProceedingJoinPoint;
22 | private ObservableInfo observableInfo;
23 |
24 | @Mock private MessageManager messageManager;
25 |
26 | ObservableRule(Class declaringType) {
27 | MockitoAnnotations.initMocks(this);
28 | this.declaringType = declaringType;
29 | }
30 |
31 | @Override public Statement apply(Statement statement, Description description) {
32 | final TestJoinPoint testJoinPoint =
33 | new TestJoinPoint.Builder(declaringType)
34 | .withReturnType(Observable.class)
35 | .withReturnValue(OBSERVABLE_STREAM_VALUE)
36 | .build();
37 | final TestProceedingJoinPoint testProceedingJoinPoint =
38 | new TestProceedingJoinPoint(testJoinPoint);
39 | frodoProceedingJoinPoint = new FrodoProceedingJoinPoint(testProceedingJoinPoint);
40 | observableInfo = new ObservableInfo(frodoProceedingJoinPoint);
41 | return statement;
42 | }
43 |
44 | FrodoProceedingJoinPoint joinPoint() {
45 | return frodoProceedingJoinPoint;
46 | }
47 |
48 | MessageManager messageManager() {
49 | return messageManager;
50 | }
51 |
52 | ObservableInfo info() {
53 | return observableInfo;
54 | }
55 |
56 | String stringType() {
57 | return Strings.EMPTY;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/frodo-runtime/src/main/java/com/fernandocejas/frodo/aspect/LogObservable.java:
--------------------------------------------------------------------------------
1 | package com.fernandocejas.frodo.aspect;
2 |
3 | import com.fernandocejas.frodo.annotation.RxLogObservable;
4 | import com.fernandocejas.frodo.internal.observable.FrodoObservable;
5 | import com.fernandocejas.frodo.internal.observable.LoggableObservableFactory;
6 | import com.fernandocejas.frodo.internal.MessageManager;
7 | import com.fernandocejas.frodo.internal.observable.ObservableInfo;
8 | import com.fernandocejas.frodo.joinpoint.FrodoProceedingJoinPoint;
9 | import java.lang.annotation.Annotation;
10 | import org.aspectj.lang.ProceedingJoinPoint;
11 | import org.aspectj.lang.annotation.Around;
12 | import org.aspectj.lang.annotation.Aspect;
13 | import org.aspectj.lang.annotation.Pointcut;
14 | import org.aspectj.lang.reflect.MethodSignature;
15 | import rx.Observable;
16 |
17 | @Aspect
18 | public class LogObservable {
19 | private static final String METHOD =
20 | "execution(@com.fernandocejas.frodo.annotation.RxLogObservable * *(..)) && if()";
21 |
22 | @Pointcut(METHOD)
23 | public static boolean methodAnnotatedWithRxLogObservable(ProceedingJoinPoint joinPoint) {
24 | final FrodoProceedingJoinPoint frodoJoinPoint = new FrodoProceedingJoinPoint(joinPoint);
25 | final Annotation annotation = frodoJoinPoint.getAnnotation(RxLogObservable.class);
26 | return ((MethodSignature) joinPoint.getSignature()).getReturnType() == Observable.class
27 | && ((RxLogObservable)annotation).value() != RxLogObservable.Scope.NOTHING;
28 | }
29 |
30 | @Around("methodAnnotatedWithRxLogObservable(joinPoint)")
31 | public Object weaveAroundJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
32 | final FrodoProceedingJoinPoint proceedingJoinPoint = new FrodoProceedingJoinPoint(joinPoint);
33 | final MessageManager messageManager = new MessageManager();
34 | final LoggableObservableFactory observableFactory =
35 | new LoggableObservableFactory(proceedingJoinPoint, messageManager,
36 | new ObservableInfo(proceedingJoinPoint));
37 |
38 | return new FrodoObservable(proceedingJoinPoint, messageManager,
39 | observableFactory).getObservable();
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/dev.txt:
--------------------------------------------------------------------------------
1 | ----------------------------------------------------------
2 | New Frodo version (TODO: automate these tasks)
3 | ----------------------------------------------------------
4 | 1. Publish through gradle task:
5 | - ./gradlew publishFrodo
6 |
7 | 2. Upload pom.xml manually (there is an issue with the publishing plugin) and publish on bintray:
8 | - https://github.com/novoda/bintray-release
9 | - https://github.com/novoda/bintray-release/issues/19
10 |
11 | 3. Generate and upload javadoc and sources manually on bintray for frodo-runtime:
12 | - ./gradlew -p frodo-runtime/ androidJavadocsJar
13 | - ./gradlew -p frodo-runtime/ androidSourcesJar
14 | - https://bintray.com/android10/maven
15 |
16 | 3. Delete any local frodo repository and try the plugin with a new clean android project.
17 | - cd ~/.m2/repository/com/fernandocejas
18 | - rm -rf frodo/
19 |
20 | 4. Create a tag on github:
21 | - git tag
22 | - git tag -a v0.8.4 -m "Release version 0.8.4"
23 | - git tag
24 | - git show v0.8.4
25 | - git push origin v0.8.4
26 |
27 | 5. Release Notes: and description in both Github and Bintray.
28 | - Frodo wiki on github: https://github.com/android10/frodo/wiki
29 | - Bintray description: https://bintray.com/android10/maven
30 |
31 | 6. Prepare for the next development version:
32 | - FrodoPlugin class project.dependencies
33 | - gradle.properties 'version' property
34 | - add new version CHANGELOG.txt file
35 |
36 | 7. Install new version by running either of these tasks:
37 | - ./install_frodo.sh
38 | - ./gradlew installFrodoAndroidSample
39 |
40 | 8. Promote new version released
41 | ----------------------------------------------------------
42 |
43 | ----------------------------------------------------------
44 | POSSIBLE ISSUE #1
45 | ----------------------------------------------------------
46 | - Installing frodo through './install_frodo.sh' can trigger this exception:
47 |
48 | FAILURE: Build failed with an exception.
49 |
50 | * What went wrong:
51 | Execution failed for task ':frodo-runtime:install'.
52 | > A problem occurred starting process 'command 'mvn''
53 |
54 | - Solution: Stop Gradle by executing: './gradlew --stop'
55 | ----------------------------------------------------------
56 |
--------------------------------------------------------------------------------
/frodo-runtime/src/test/java/com/fernandocejas/frodo/internal/observable/FrodoObservableTest.java:
--------------------------------------------------------------------------------
1 | package com.fernandocejas.frodo.internal.observable;
2 |
3 | import com.fernandocejas.frodo.internal.MessageManager;
4 | import java.lang.annotation.Annotation;
5 | import java.util.Collections;
6 | import org.junit.Before;
7 | import org.junit.Rule;
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 | import org.mockito.Mock;
11 | import org.mockito.runners.MockitoJUnitRunner;
12 | import rx.observers.TestSubscriber;
13 |
14 | import static org.mockito.BDDMockito.given;
15 | import static org.mockito.Matchers.any;
16 | import static org.mockito.Mockito.verify;
17 |
18 | @SuppressWarnings("unchecked")
19 | @RunWith(MockitoJUnitRunner.class)
20 | public class FrodoObservableTest {
21 |
22 | @Rule public ObservableRule observableRule = new ObservableRule(this.getClass());
23 |
24 | private FrodoObservable frodoObservable;
25 | private TestSubscriber subscriber;
26 |
27 | @Mock private MessageManager messageManager;
28 | @Mock private LoggableObservableFactory observableFactory;
29 |
30 | @Before
31 | public void setUp() {
32 | frodoObservable =
33 | new FrodoObservable(observableRule.joinPoint(), messageManager, observableFactory);
34 | subscriber = new TestSubscriber();
35 |
36 | given(observableFactory.create(any(Annotation.class))).willReturn(
37 | createLogEverythingObservable());
38 | }
39 |
40 | @Test
41 | public void shouldPrintObservableInfo() throws Throwable {
42 | frodoObservable.getObservable();
43 |
44 | verify(messageManager).printObservableInfo(any(ObservableInfo.class));
45 | }
46 |
47 | @Test
48 | public void shouldBuildObservable() throws Throwable {
49 | frodoObservable.getObservable().subscribe(subscriber);
50 |
51 | subscriber.assertReceivedOnNext(
52 | Collections.singletonList(observableRule.OBSERVABLE_STREAM_VALUE));
53 | subscriber.assertNoErrors();
54 | subscriber.assertCompleted();
55 | subscriber.assertUnsubscribed();
56 | }
57 |
58 | @Test
59 | public void shouldLogObservableInformation() throws Throwable {
60 | frodoObservable.getObservable().subscribe(subscriber);
61 |
62 | verify(messageManager).printObservableInfo(any(ObservableInfo.class));
63 | }
64 |
65 | private LogEverythingObservable createLogEverythingObservable() {
66 | return new LogEverythingObservable(observableRule.joinPoint(), messageManager,
67 | new ObservableInfo(observableRule.joinPoint()));
68 | }
69 | }
--------------------------------------------------------------------------------
/frodo-runtime/src/test/java/com/fernandocejas/frodo/internal/observable/LogSchedulersObservableTest.java:
--------------------------------------------------------------------------------
1 | package com.fernandocejas.frodo.internal.observable;
2 |
3 | import com.fernandocejas.frodo.core.optional.Optional;
4 | import com.fernandocejas.frodo.internal.MessageManager;
5 | import org.junit.Before;
6 | import org.junit.Rule;
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 | import org.mockito.Mock;
10 | import org.mockito.runners.MockitoJUnitRunner;
11 | import rx.observers.TestSubscriber;
12 | import rx.schedulers.Schedulers;
13 |
14 | import static org.assertj.core.api.Assertions.assertThat;
15 | import static org.mockito.Matchers.any;
16 | import static org.mockito.Mockito.verify;
17 | import static org.mockito.Mockito.verifyNoMoreInteractions;
18 |
19 | @SuppressWarnings("unchecked") @RunWith(MockitoJUnitRunner.class)
20 | public class LogSchedulersObservableTest {
21 |
22 | @Rule public ObservableRule observableRule = new ObservableRule(this.getClass());
23 |
24 | private LogSchedulersObservable loggableObservable;
25 | private TestSubscriber subscriber;
26 |
27 | @Mock private MessageManager messageManager;
28 |
29 | @Before
30 | public void setUp() {
31 | subscriber = new TestSubscriber();
32 | loggableObservable =
33 | new LogSchedulersObservable(observableRule.joinPoint(), messageManager,
34 | observableRule.info());
35 | }
36 |
37 | @Test
38 | public void shouldLogOnlyObservableSchedulers() throws Throwable {
39 | loggableObservable.get(observableRule.stringType()).subscribe(subscriber);
40 |
41 | verify(messageManager).printObservableThreadInfo(any(ObservableInfo.class));
42 | verifyNoMoreInteractions(messageManager);
43 | }
44 |
45 | @Test
46 | public void shouldFillInObservableThreadInfo() throws Throwable {
47 | loggableObservable.get(observableRule.stringType())
48 | .subscribeOn(Schedulers.immediate())
49 | .observeOn(Schedulers.immediate())
50 | .subscribe(subscriber);
51 |
52 | final ObservableInfo observableInfo = loggableObservable.getInfo();
53 | final Optional subscribeOnThread = observableInfo.getSubscribeOnThread();
54 | final Optional observeOnThread = observableInfo.getObserveOnThread();
55 | final String currentThreadName = Thread.currentThread().getName();
56 |
57 | assertThat(subscribeOnThread.isPresent()).isTrue();
58 | assertThat(observeOnThread.isPresent()).isTrue();
59 | assertThat(subscribeOnThread.get()).isEqualTo(currentThreadName);
60 | assertThat(observeOnThread.get()).isEqualTo(currentThreadName);
61 | }
62 | }
--------------------------------------------------------------------------------
/frodo-runtime/src/test/java/com/fernandocejas/frodo/internal/observable/LogStreamObservableTest.java:
--------------------------------------------------------------------------------
1 | package com.fernandocejas.frodo.internal.observable;
2 |
3 | import com.fernandocejas.frodo.core.optional.Optional;
4 | import com.fernandocejas.frodo.internal.MessageManager;
5 | import java.util.concurrent.TimeUnit;
6 | import org.junit.Before;
7 | import org.junit.Rule;
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 | import org.mockito.Mock;
11 | import org.mockito.runners.MockitoJUnitRunner;
12 | import rx.observers.TestSubscriber;
13 | import rx.schedulers.TestScheduler;
14 |
15 | import static org.assertj.core.api.Assertions.assertThat;
16 | import static org.mockito.Matchers.any;
17 | import static org.mockito.Matchers.anyString;
18 | import static org.mockito.Mockito.verify;
19 | import static org.mockito.Mockito.verifyNoMoreInteractions;
20 |
21 | @SuppressWarnings("unchecked") @RunWith(MockitoJUnitRunner.class)
22 | public class LogStreamObservableTest {
23 |
24 | @Rule public ObservableRule observableRule = new ObservableRule(this.getClass());
25 |
26 | private LogStreamObservable loggableObservable;
27 | private TestSubscriber subscriber;
28 |
29 | @Mock private MessageManager messageManager;
30 |
31 | @Before
32 | public void setUp() {
33 | subscriber = new TestSubscriber();
34 | loggableObservable =
35 | new LogStreamObservable(observableRule.joinPoint(), messageManager, observableRule.info());
36 | }
37 |
38 | @Test
39 | public void shouldLogOnlyStreamData() throws Throwable {
40 | loggableObservable.get(observableRule.stringType()).subscribe(subscriber);
41 |
42 | verify(messageManager).printObservableOnNextWithValue(any(ObservableInfo.class), anyString());
43 | verify(messageManager).printObservableItemTimeInfo(any(ObservableInfo.class));
44 | verifyNoMoreInteractions(messageManager);
45 | }
46 |
47 | @Test
48 | public void shouldFillInObservableItemsInfo() throws Throwable {
49 | final TestScheduler testScheduler = new TestScheduler();
50 | loggableObservable.get(observableRule.stringType())
51 | .delay(2, TimeUnit.SECONDS)
52 | .subscribeOn(testScheduler)
53 | .subscribe(subscriber);
54 |
55 | testScheduler.advanceTimeBy(1, TimeUnit.SECONDS);
56 | testScheduler.advanceTimeBy(2, TimeUnit.SECONDS);
57 |
58 | final ObservableInfo observableInfo = loggableObservable.getInfo();
59 | final Optional totalEmittedItems = observableInfo.getTotalEmittedItems();
60 | final Optional totalExecutionTime = observableInfo.getTotalExecutionTime();
61 |
62 | assertThat(totalEmittedItems.isPresent()).isTrue();
63 | assertThat(totalEmittedItems.get()).isEqualTo(1);
64 | }
65 | }
--------------------------------------------------------------------------------
/frodo-runtime/src/main/java/com/fernandocejas/frodo/core/functions/Predicate.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2007 The Guava Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.fernandocejas.frodo.core.functions;
18 |
19 | import org.jetbrains.annotations.Nullable;
20 |
21 | /**
22 | * Determines a true or false value for a given input.
23 | *
24 | * The {@link Predicates} class provides common predicates and related utilities.
25 | *
26 | *
See the Guava User Guide article on the use of {@code
28 | * Predicate} .
29 | *
30 | * @author Kevin Bourrillion
31 | * @since 2.0 (imported from Google Collections Library)
32 | */
33 | public interface Predicate {
34 | /**
35 | * Returns the result of applying this predicate to {@code input}. This method is generally
36 | * expected , but not absolutely required, to have the following properties:
37 | *
38 | *
39 | * Its execution does not cause any observable side effects.
40 | * The computation is consistent with equals .
41 | * Objects.equal}{@code (a, b)} implies that {@code predicate.apply(a) ==
42 | * predicate.apply(b))}.
43 | *
44 | *
45 | * @throws NullPointerException if {@code input} is null and this predicate does not accept null
46 | * arguments
47 | */
48 | boolean apply(@Nullable T input);
49 |
50 | /**
51 | * Indicates whether another object is equal to this predicate.
52 | *
53 | * Most implementations will have no reason to override the behavior of {@link Object#equals}.
54 | * However, an implementation may also choose to return {@code true} whenever {@code object} is a
55 | * {@link Predicate} that it considers interchangeable with this one. "Interchangeable"
56 | * typically means that {@code this.apply(t) == that.apply(t)} for all {@code t} of type
57 | * {@code T}). Note that a {@code false} result from this method does not imply that the
58 | * predicates are known not to be interchangeable.
59 | */
60 | @Override boolean equals(@Nullable Object object);
61 | }
62 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/frodo-runtime/src/main/java/com/fernandocejas/frodo/core/functions/Function.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2007 The Guava Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.fernandocejas.frodo.core.functions;
18 |
19 | import org.jetbrains.annotations.Nullable;
20 |
21 | /**
22 | * Determines an output value based on an input value.
23 | *
24 | *
The {@link Functions} class provides common functions and related utilites.
25 | *
26 | *
See the Guava User Guide article on the use of {@code
28 | * Function} .
29 | *
30 | * @author Kevin Bourrillion
31 | * @since 2.0 (imported from Google Collections Library)
32 | *
33 | *
This class contains code derived from Google
34 | * Guava
35 | */
36 | public interface Function {
37 | /**
38 | * Returns the result of applying this function to {@code input}. This method is generally
39 | * expected , but not absolutely required, to have the following properties:
40 | *
41 | *
42 | * Its execution does not cause any observable side effects.
43 | * The computation is consistent with equals .
44 | * Objects.equal}{@code (a, b)} implies that {@code Objects.equal(function.apply(a),
45 | * function.apply(b))}.
46 | *
47 | *
48 | * @throws NullPointerException if {@code input} is null
49 | */
50 | @Nullable T apply(F input);
51 |
52 | /**
53 | * Indicates whether another object is equal to this function.
54 | *
55 | * Most implementations will have no reason to override the behavior of {@link Object#equals}.
56 | * However, an implementation may also choose to return {@code true} whenever {@code object} is a
57 | * {@link Function} that it considers interchangeable with this one. "Interchangeable"
58 | * typically means that {@code Objects.equal(this.apply(f), that.apply(f))} is true for
59 | * all
60 | * {@code f} of type {@code F}. Note that a {@code false} result from this method does not imply
61 | * that the functions are known not to be interchangeable.
62 | */
63 | @Override boolean equals(@Nullable Object object);
64 | }
65 |
--------------------------------------------------------------------------------
/frodo-runtime/src/main/java/com/fernandocejas/frodo/core/strings/Strings.java:
--------------------------------------------------------------------------------
1 | package com.fernandocejas.frodo.core.strings;
2 |
3 | import java.math.BigInteger;
4 | import java.util.Locale;
5 | import org.jetbrains.annotations.Nullable;
6 |
7 | /**
8 | * General utility functions pertaining to {@link String}s.
9 | *
10 | *
This class contains code derived from Google
11 | * Guava
12 | */
13 | public final class Strings {
14 |
15 | public static final String EMPTY = "";
16 |
17 | /**
18 | * Returns the given string if it is non-null; the empty string otherwise.
19 | *
20 | * @param string the string to test and possibly return
21 | * @return {@code string} itself if it is non-null; {@code ""} if it is null
22 | */
23 | public static String nullToEmpty(@Nullable CharSequence string) {
24 | return string == null ? EMPTY : string.toString();
25 | }
26 |
27 | /**
28 | * Returns {@code true} if the given string is null or is the empty string.
29 | *
30 | * @param string a string reference to check
31 | * @return {@code true} if the string is null or is the empty string
32 | */
33 | public static boolean isNullOrEmpty(@Nullable CharSequence string) {
34 | return string == null || string.length() == 0;
35 | }
36 |
37 | /**
38 | * @return true if the string only contains white space or if {@link #isNullOrEmpty(CharSequence)}
39 | * would return true; false otherwise.
40 | */
41 | public static boolean isBlank(@Nullable CharSequence string) {
42 | return isNullOrEmpty(nullToEmpty(string).trim());
43 | }
44 |
45 | /**
46 | * The converse of {@link #isBlank(CharSequence)}
47 | */
48 | public static boolean isNotBlank(@Nullable CharSequence string) {
49 | return !isBlank(string);
50 | }
51 |
52 | public static String safeToString(@Nullable Object object) {
53 | return object == null ? EMPTY : object.toString();
54 | }
55 |
56 | /**
57 | * Converts the given bytes to a hexidecimal strings including leading zeros.
58 | * If you do not need leading zeros, you can use Java's toString(16) instead.
59 | */
60 | public static String toHexString(byte[] bytes) {
61 | return String.format(Locale.US, "%0" + (bytes.length << 1) + "x", new BigInteger(1, bytes));
62 | }
63 |
64 | /**
65 | * Returns a joiner which automatically places {@code separator} between consecutive elements.
66 | */
67 | public static Joiner joinOn(String separator) {
68 | return new Joiner(separator);
69 | }
70 |
71 | /**
72 | * Returns a joiner which automatically places {@code separator} between consecutive elements.
73 | */
74 | public static Joiner joinOn(char separator) {
75 | return new Joiner(String.valueOf(separator));
76 | }
77 |
78 | private Strings() {
79 | // no instances
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/frodo-runtime/src/main/java/com/fernandocejas/frodo/core/optional/Present.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 The Guava Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.fernandocejas.frodo.core.optional;
18 |
19 | import com.fernandocejas.frodo.core.functions.Function;
20 | import java.util.Collections;
21 | import java.util.Set;
22 | import org.jetbrains.annotations.Nullable;
23 |
24 | import static com.fernandocejas.frodo.core.checks.Preconditions.checkNotNull;
25 |
26 | /**
27 | * Implementation of an {@link Optional} containing a reference.
28 | *
29 | *
This class contains code derived from Google
30 | * Guava
31 | */
32 | final class Present extends Optional {
33 | private static final long serialVersionUID = 0;
34 |
35 | private final T reference;
36 |
37 | Present(T reference) {
38 | this.reference = reference;
39 | }
40 |
41 | @Override
42 | public boolean isPresent() {
43 | return true;
44 | }
45 |
46 | @Override
47 | public T get() {
48 | return reference;
49 | }
50 |
51 | @Override
52 | public T or(T defaultValue) {
53 | checkNotNull(defaultValue, "use Optional.orNull() instead of Optional.or(null)");
54 | return reference;
55 | }
56 |
57 | @Override
58 | public Optional or(Optional extends T> secondChoice) {
59 | checkNotNull(secondChoice);
60 | return this;
61 | }
62 |
63 | @Override
64 | public T orNull() {
65 | return reference;
66 | }
67 |
68 | @Override
69 | public Set asSet() {
70 | return Collections.singleton(reference);
71 | }
72 |
73 | @Override
74 | public Optional transform(Function super T, V> function) {
75 | return new Present(checkNotNull(function.apply(reference),
76 | "the Function passed to Optional.transform() must not return null."));
77 | }
78 |
79 | @Override
80 | public boolean equals(@Nullable Object object) {
81 | if (object instanceof Present) {
82 | Present> other = (Present>) object;
83 | return reference.equals(other.reference);
84 | }
85 | return false;
86 | }
87 |
88 | @Override
89 | public int hashCode() {
90 | return 0x598df91c + reference.hashCode();
91 | }
92 |
93 | @Override
94 | public String toString() {
95 | return "Optional.of(" + reference + ")";
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/frodo-runtime/src/test/java/com/fernandocejas/frodo/internal/observable/ObservableInfoTest.java:
--------------------------------------------------------------------------------
1 | package com.fernandocejas.frodo.internal.observable;
2 |
3 | import com.fernandocejas.frodo.core.optional.Optional;
4 | import com.fernandocejas.frodo.internal.observable.ObservableInfo;
5 | import com.fernandocejas.frodo.joinpoint.FrodoJoinPoint;
6 | import com.fernandocejas.frodo.joinpoint.TestJoinPoint;
7 | import org.junit.Before;
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 | import org.mockito.runners.MockitoJUnitRunner;
11 | import rx.Observable;
12 |
13 | import static org.assertj.core.api.Assertions.assertThat;
14 |
15 | @RunWith(MockitoJUnitRunner.class)
16 | public class ObservableInfoTest {
17 |
18 | private static final String OBSERVABLE_STREAM_VALUE = "fernando";
19 |
20 | private ObservableInfo observableInfo;
21 |
22 | @Before
23 | public void setUp() {
24 | final TestJoinPoint testJoinPoint = new TestJoinPoint.Builder(this.getClass())
25 | .withReturnType(Observable.class)
26 | .withReturnValue(OBSERVABLE_STREAM_VALUE)
27 | .build();
28 | final FrodoJoinPoint frodoJoinPoint = new FrodoJoinPoint(testJoinPoint);
29 | observableInfo = new ObservableInfo(frodoJoinPoint);
30 | }
31 |
32 | @Test
33 | public void shouldReturnAbsentValues() {
34 | final Optional optionalSubscribeOnThread = observableInfo.getSubscribeOnThread();
35 | final Optional optionalObserveOnThread = observableInfo.getObserveOnThread();
36 | final Optional optionalTotalExecutionTime = observableInfo.getTotalExecutionTime();
37 | final Optional optionalTotalEmittedItems = observableInfo.getTotalEmittedItems();
38 |
39 | assertThat(optionalSubscribeOnThread.isPresent()).isFalse();
40 | assertThat(optionalObserveOnThread.isPresent()).isFalse();
41 | assertThat(optionalTotalExecutionTime.isPresent()).isFalse();
42 | assertThat(optionalTotalEmittedItems.isPresent()).isFalse();
43 | }
44 |
45 | @Test
46 | public void shouldReturnPresentValues() {
47 | observableInfo.setSubscribeOnThread("thread");
48 | observableInfo.setObserveOnThread("thread");
49 | observableInfo.setTotalExecutionTime(1000);
50 | observableInfo.setTotalEmittedItems(5);
51 |
52 | final Optional optionalSubscribeOnThread = observableInfo.getSubscribeOnThread();
53 | final Optional optionalObserveOnThread = observableInfo.getObserveOnThread();
54 | final Optional optionalTotalExecutionTime = observableInfo.getTotalExecutionTime();
55 | final Optional optionalTotalEmittedItems = observableInfo.getTotalEmittedItems();
56 |
57 | assertThat(optionalSubscribeOnThread.isPresent()).isTrue();
58 | assertThat(optionalObserveOnThread.isPresent()).isTrue();
59 | assertThat(optionalTotalExecutionTime.isPresent()).isTrue();
60 | assertThat(optionalTotalEmittedItems.isPresent()).isTrue();
61 | }
62 | }
--------------------------------------------------------------------------------
/frodo-runtime/src/test/java/com/fernandocejas/frodo/internal/observable/LoggableObservableFactoryTest.java:
--------------------------------------------------------------------------------
1 | package com.fernandocejas.frodo.internal.observable;
2 |
3 | import com.fernandocejas.frodo.annotation.RxLogObservable;
4 | import org.junit.Before;
5 | import org.junit.Rule;
6 | import org.junit.Test;
7 | import org.junit.runner.RunWith;
8 | import org.mockito.runners.MockitoJUnitRunner;
9 |
10 | import static org.assertj.core.api.Assertions.assertThat;
11 | import static org.mockito.BDDMockito.given;
12 | import static org.mockito.Mockito.mock;
13 |
14 | @RunWith(MockitoJUnitRunner.class)
15 | public class LoggableObservableFactoryTest {
16 |
17 | @Rule public final ObservableRule observableRule = new ObservableRule(this.getClass());
18 |
19 | private LoggableObservableFactory observableFactory;
20 |
21 | @Before
22 | public void setUp() {
23 | observableFactory = new LoggableObservableFactory(observableRule.joinPoint(),
24 | observableRule.messageManager(), observableRule.info());
25 | }
26 |
27 | @Test
28 | public void shouldCreateLogEverythingObservable() {
29 | final RxLogObservable annotation = mock(RxLogObservable.class);
30 | given(annotation.value()).willReturn(RxLogObservable.Scope.EVERYTHING);
31 |
32 | final LoggableObservable loggableObservable = observableFactory.create(annotation);
33 |
34 | assertThat(loggableObservable).isInstanceOf(LogEverythingObservable.class);
35 | }
36 |
37 | @Test
38 | public void shouldCreateLogStreamObservable() {
39 | final RxLogObservable annotation = mock(RxLogObservable.class);
40 | given(annotation.value()).willReturn(RxLogObservable.Scope.STREAM);
41 |
42 | final LoggableObservable loggableObservable = observableFactory.create(annotation);
43 |
44 | assertThat(loggableObservable).isInstanceOf(LogStreamObservable.class);
45 | }
46 |
47 | @Test
48 | public void shouldCreateLogEventsObservable() {
49 | final RxLogObservable annotation = mock(RxLogObservable.class);
50 | given(annotation.value()).willReturn(RxLogObservable.Scope.EVENTS);
51 |
52 | final LoggableObservable loggableObservable = observableFactory.create(annotation);
53 |
54 | assertThat(loggableObservable).isInstanceOf(LogEventsObservable.class);
55 | }
56 |
57 | @Test
58 | public void shouldCreateLogSchedulersObservable() {
59 | final RxLogObservable annotation = mock(RxLogObservable.class);
60 | given(annotation.value()).willReturn(RxLogObservable.Scope.SCHEDULERS);
61 |
62 | final LoggableObservable loggableObservable = observableFactory.create(annotation);
63 |
64 | assertThat(loggableObservable).isInstanceOf(LogSchedulersObservable.class);
65 | }
66 |
67 | @Test
68 | public void shouldCreateLogNothingObservable() {
69 | final RxLogObservable annotation = mock(RxLogObservable.class);
70 | given(annotation.value()).willReturn(RxLogObservable.Scope.NOTHING);
71 |
72 | final LoggableObservable loggableObservable = observableFactory.create(annotation);
73 |
74 | assertThat(loggableObservable).isInstanceOf(LogNothingObservable.class);
75 | }
76 | }
--------------------------------------------------------------------------------
/frodo-runtime/src/main/java/com/fernandocejas/frodo/core/optional/Absent.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 The Guava Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.fernandocejas.frodo.core.optional;
18 |
19 | import com.fernandocejas.frodo.core.functions.Function;
20 | import java.util.Collections;
21 | import java.util.Set;
22 | import org.jetbrains.annotations.Nullable;
23 |
24 | import static com.fernandocejas.frodo.core.checks.Preconditions.checkNotNull;
25 |
26 | /**
27 | * Implementation of an {@link Optional} not containing a reference.
28 | *
29 | * This class contains code derived from Google
30 | * Guava
31 | */
32 | final class Absent extends Optional {
33 | private static final long serialVersionUID = 0;
34 |
35 | static final Absent INSTANCE = new Absent<>();
36 |
37 | @SuppressWarnings("unchecked") // implementation is "fully variant"
38 | static Optional withType() {
39 | return (Optional) INSTANCE;
40 | }
41 |
42 | private Absent() {
43 | }
44 |
45 | @Override
46 | public boolean isPresent() {
47 | return false;
48 | }
49 |
50 | @Override
51 | public T get() {
52 | throw new IllegalStateException("Optional.get() cannot be called on an absent value");
53 | }
54 |
55 | @Override
56 | public T or(T defaultValue) {
57 | return checkNotNull(defaultValue, "use Optional.orNull() instead of Optional.or(null)");
58 | }
59 |
60 | @SuppressWarnings("unchecked") // safe covariant cast
61 | @Override
62 | public Optional or(Optional extends T> secondChoice) {
63 | return (Optional) checkNotNull(secondChoice);
64 | }
65 |
66 | @Override
67 | @Nullable
68 | public T orNull() {
69 | return null;
70 | }
71 |
72 | @Override
73 | public Set asSet() {
74 | return Collections.emptySet();
75 | }
76 |
77 | @Override
78 | public Optional transform(Function super T, V> function) {
79 | checkNotNull(function);
80 | return absent();
81 | }
82 |
83 | @Override
84 | public boolean equals(@Nullable Object object) {
85 | return object == this;
86 | }
87 |
88 | @Override
89 | public int hashCode() {
90 | return 0x598df91c;
91 | }
92 |
93 | @Override
94 | public String toString() {
95 | return "Optional.absent()";
96 | }
97 |
98 | private Object readResolve() {
99 | return INSTANCE;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/frodo-runtime/src/main/java/com/fernandocejas/frodo/core/strings/Charsets.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2007 The Guava Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.fernandocejas.frodo.core.strings;
18 |
19 | import java.nio.charset.Charset;
20 |
21 | /**
22 | * Contains constant definitions for the six standard {@link Charset} instances, which are
23 | * guaranteed to be supported by all Java platform implementations.
24 | *
25 | * Assuming you're free to choose, note that {@link #UTF_8} is widely preferred .
26 | *
27 | *
See the Guava User Guide article on
29 | * {@code Charsets} .
30 | *
31 | * @author Mike Bostock
32 | * @since 1.0
33 | */
34 | public final class Charsets {
35 |
36 | /**
37 | * US-ASCII: seven-bit ASCII, the Basic Latin block of the Unicode character set (ISO646-US).
38 | *
39 | *
Note for Java 7 and later: this constant should be treated as deprecated; use
40 | * {@link java.nio.charset.StandardCharsets#US_ASCII} instead.
41 | */
42 | public static final Charset US_ASCII = Charset.forName("US-ASCII");
43 |
44 | /**
45 | * ISO-8859-1: ISO Latin Alphabet Number 1 (ISO-LATIN-1).
46 | *
47 | *
Note for Java 7 and later: this constant should be treated as deprecated; use
48 | * {@link java.nio.charset.StandardCharsets#ISO_8859_1} instead.
49 | */
50 | public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
51 |
52 | /**
53 | * UTF-8: eight-bit UCS Transformation Format.
54 | *
55 | *
Note for Java 7 and later: this constant should be treated as deprecated; use
56 | * {@link java.nio.charset.StandardCharsets#UTF_8} instead.
57 | */
58 | public static final Charset UTF_8 = Charset.forName("UTF-8");
59 |
60 | /**
61 | * UTF-16: sixteen-bit UCS Transformation Format, byte order identified by an optional byte-order
62 | * mark.
63 | *
64 | *
Note for Java 7 and later: this constant should be treated as deprecated; use
65 | * {@link java.nio.charset.StandardCharsets#UTF_16} instead.
66 | */
67 | public static final Charset UTF_16 = Charset.forName("UTF-16");
68 |
69 | /*
70 | * Please do not add new Charset references to this class, unless those character encodings are
71 | * part of the set required to be supported by all Java platform implementations! Any Charsets
72 | * initialized here may cause unexpected delays when this class is loaded. See the Charset
73 | * Javadocs for the list of built-in character encodings.
74 | */
75 | }
76 |
--------------------------------------------------------------------------------
/frodo-runtime/src/main/java/com/fernandocejas/frodo/internal/observable/LogEverythingObservable.java:
--------------------------------------------------------------------------------
1 | package com.fernandocejas.frodo.internal.observable;
2 |
3 | import com.fernandocejas.frodo.internal.Counter;
4 | import com.fernandocejas.frodo.internal.MessageManager;
5 | import com.fernandocejas.frodo.internal.StopWatch;
6 | import com.fernandocejas.frodo.joinpoint.FrodoProceedingJoinPoint;
7 | import rx.Notification;
8 | import rx.Observable;
9 | import rx.functions.Action0;
10 | import rx.functions.Action1;
11 |
12 | @SuppressWarnings("unchecked") class LogEverythingObservable extends LoggableObservable {
13 |
14 | LogEverythingObservable(FrodoProceedingJoinPoint joinPoint, MessageManager messageManager,
15 | ObservableInfo observableInfo) {
16 | super(joinPoint, messageManager, observableInfo);
17 | }
18 |
19 | @Override Observable get(T type) throws Throwable {
20 | final StopWatch stopWatch = new StopWatch();
21 | final Counter emittedItems = new Counter(joinPoint.getMethodName());
22 | return ((Observable) joinPoint.proceed())
23 | .doOnSubscribe(new Action0() {
24 | @Override
25 | public void call() {
26 | stopWatch.start();
27 | messageManager.printObservableOnSubscribe(observableInfo);
28 | }
29 | })
30 | .doOnEach(new Action1>() {
31 | @Override public void call(Notification super T> notification) {
32 | if (!observableInfo.getSubscribeOnThread().isPresent()
33 | && (notification.isOnNext() || notification.isOnError())) {
34 | observableInfo.setSubscribeOnThread(Thread.currentThread().getName());
35 | }
36 | }
37 | })
38 | .doOnNext(new Action1() {
39 | @Override
40 | public void call(T value) {
41 | emittedItems.increment();
42 | messageManager.printObservableOnNextWithValue(observableInfo, value);
43 | }
44 | })
45 | .doOnError(new Action1() {
46 | @Override
47 | public void call(Throwable throwable) {
48 | messageManager.printObservableOnError(observableInfo, throwable);
49 | }
50 | })
51 | .doOnCompleted(new Action0() {
52 | @Override
53 | public void call() {
54 | messageManager.printObservableOnCompleted(observableInfo);
55 | }
56 | })
57 | .doOnTerminate(new Action0() {
58 | @Override
59 | public void call() {
60 | stopWatch.stop();
61 | observableInfo.setTotalExecutionTime(stopWatch.getTotalTimeMillis());
62 | observableInfo.setTotalEmittedItems(emittedItems.tally());
63 | messageManager.printObservableOnTerminate(observableInfo);
64 | messageManager.printObservableItemTimeInfo(observableInfo);
65 | }
66 | })
67 | .doOnUnsubscribe(new Action0() {
68 | @Override
69 | public void call() {
70 | if (!observableInfo.getObserveOnThread().isPresent()) {
71 | observableInfo.setObserveOnThread(Thread.currentThread().getName());
72 | }
73 | messageManager.printObservableThreadInfo(observableInfo);
74 | messageManager.printObservableOnUnsubscribe(observableInfo);
75 | }
76 | });
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/frodo-runtime/src/test/java/com/fernandocejas/frodo/joinpoint/TestProceedingJoinPoint.java:
--------------------------------------------------------------------------------
1 | package com.fernandocejas.frodo.joinpoint;
2 |
3 | import org.aspectj.lang.ProceedingJoinPoint;
4 | import org.aspectj.lang.Signature;
5 | import org.aspectj.lang.reflect.MethodSignature;
6 | import org.aspectj.lang.reflect.SourceLocation;
7 | import org.aspectj.runtime.internal.AroundClosure;
8 | import rx.Observable;
9 |
10 | import static org.assertj.core.api.Assertions.assertThat;
11 |
12 | public class TestProceedingJoinPoint implements ProceedingJoinPoint {
13 |
14 | private final TestJoinPoint testJoinPoint;
15 |
16 | //Used for assertions
17 | private boolean proceedMethodCalled;
18 | private boolean proceedMethodCalledWithArgs;
19 | private Object[] proceedMethodArgs;
20 |
21 | public TestProceedingJoinPoint(TestJoinPoint testJoinPoint) {
22 | this.testJoinPoint = testJoinPoint;
23 |
24 | proceedMethodCalled = false;
25 | proceedMethodCalledWithArgs = false;
26 | proceedMethodArgs = new Object[] {};
27 | }
28 |
29 | private Object buildReturnType() throws InstantiationException, IllegalAccessException {
30 | final Class returnType = ((MethodSignature) testJoinPoint.getSignature()).getReturnType();
31 | if (returnType == Observable.class) {
32 | return Observable.just(testJoinPoint.getMethodReturnValue());
33 | }
34 | return returnType.newInstance();
35 | }
36 |
37 | @Override public void set$AroundClosure(AroundClosure arc) {
38 | //do nothing
39 | }
40 |
41 | @Override public Object proceed() throws Throwable {
42 | proceedMethodCalled = true;
43 | return buildReturnType();
44 | }
45 |
46 | @Override public Object proceed(Object[] args) throws Throwable {
47 | proceedMethodCalledWithArgs = true;
48 | proceedMethodArgs = args;
49 | return buildReturnType();
50 | }
51 |
52 | @Override public String toShortString() {
53 | return testJoinPoint.toShortString();
54 | }
55 |
56 | @Override public String toLongString() {
57 | return testJoinPoint.toLongString();
58 | }
59 |
60 | @Override public Object getThis() {
61 | return this;
62 | }
63 |
64 | @Override public Object getTarget() {
65 | return testJoinPoint.getTarget();
66 | }
67 |
68 | @Override public Object[] getArgs() {
69 | return testJoinPoint.getArgs();
70 | }
71 |
72 | @Override public Signature getSignature() {
73 | return testJoinPoint.getSignature();
74 | }
75 |
76 | @Override public SourceLocation getSourceLocation() {
77 | return testJoinPoint.getSourceLocation();
78 | }
79 |
80 | @Override public String getKind() {
81 | return testJoinPoint.getKind();
82 | }
83 |
84 | @Override public StaticPart getStaticPart() {
85 | return testJoinPoint.getStaticPart();
86 | }
87 |
88 | public Class getMethodReturnType() {
89 | return testJoinPoint.getMethodReturnType();
90 | }
91 |
92 | public String getMethodReturnValue() {
93 | return testJoinPoint.getMethodReturnValue();
94 | }
95 |
96 | public void assertProceedMethodCalled() {
97 | assertThat(proceedMethodCalled).isTrue();
98 | proceedMethodCalled = false;
99 | }
100 |
101 | public void assertProceedMethodCalledWithArgs(Object[] args) {
102 | assertThat(proceedMethodCalledWithArgs).isTrue();
103 | assertThat(proceedMethodArgs).isEqualTo(args);
104 | proceedMethodCalledWithArgs = false;
105 | proceedMethodArgs = new Object[] {};
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/frodo-plugin/src/main/groovy/com/fernandocejas/frodo/plugin/FrodoPlugin.groovy:
--------------------------------------------------------------------------------
1 | package com.fernandocejas.frodo.plugin
2 |
3 | import com.android.build.gradle.AppPlugin
4 | import com.android.build.gradle.LibraryPlugin
5 | import org.aspectj.bridge.IMessage
6 | import org.aspectj.bridge.MessageHandler
7 | import org.aspectj.tools.ajc.Main
8 | import org.gradle.api.Plugin
9 | import org.gradle.api.Project
10 | import org.gradle.api.tasks.TaskInstantiationException
11 | import org.gradle.api.tasks.compile.JavaCompile
12 |
13 | class FrodoPlugin implements Plugin {
14 | @Override
15 | void apply(Project project) {
16 | verifyRequiredPlugins project
17 |
18 | project.buildscript.repositories.maven {
19 | url "http://dl.bintray.com/android10/maven"
20 | }
21 |
22 | project.repositories.maven {
23 | url "http://dl.bintray.com/android10/maven"
24 | }
25 |
26 | project.dependencies {
27 | compile "com.fernandocejas.frodo:frodo-api:0.8.4"
28 | debugCompile "org.aspectj:aspectjrt:1.8.6"
29 | debugCompile "com.fernandocejas.frodo:frodo-runtime:0.8.4"
30 | }
31 |
32 | project.extensions.create('frodo', FrodoEnablerExtension)
33 | final def log = project.logger
34 |
35 | def variants = getProjectVariants project
36 | variants.all { variant ->
37 | if (!variant.buildType.isDebuggable()) {
38 | log.debug("Skipping non-debuggable build type '${variant.buildType.name}'.")
39 | return; //Only weaving on Debug version of the app/library.
40 | } else if (!project.frodo.enabled) {
41 | log.debug("Frodo is not enabled.")
42 | return;
43 | }
44 |
45 | JavaCompile javaCompile = variant.javaCompile
46 | javaCompile.doLast {
47 | String[] args = ["-showWeaveInfo",
48 | "-1.5",
49 | "-inpath", javaCompile.destinationDir.toString(),
50 | "-aspectpath", javaCompile.classpath.asPath,
51 | "-d", javaCompile.destinationDir.toString(),
52 | "-classpath", javaCompile.classpath.asPath,
53 | "-bootclasspath", project.android.bootClasspath.join(File.pathSeparator)]
54 |
55 | final MessageHandler handler = new MessageHandler(true);
56 | new Main().run(args, handler);
57 | for (IMessage message : handler.getMessages(null, true)) {
58 | switch (message.getKind()) {
59 | case IMessage.ABORT:
60 | case IMessage.ERROR:
61 | case IMessage.FAIL:
62 | log.error message.message, message.thrown
63 | break;
64 | case IMessage.WARNING:
65 | log.warn message.message, message.thrown
66 | break;
67 | case IMessage.INFO:
68 | log.info message.message, message.thrown
69 | break;
70 | case IMessage.DEBUG:
71 | log.debug message.message, message.thrown
72 | break;
73 | }
74 | }
75 | }
76 | }
77 | }
78 |
79 | def verifyRequiredPlugins(Project project) {
80 | def hasAppPlugin = project.plugins.hasPlugin(AppPlugin)
81 | def hasLibraryPlugin = project.plugins.hasPlugin(LibraryPlugin)
82 | if (!hasAppPlugin && !hasLibraryPlugin) {
83 | throw new TaskInstantiationException("Plugins required: 'android' or 'android-library'.")
84 | }
85 | }
86 |
87 | def getProjectVariants(Project project) {
88 | def variants
89 | if (project.plugins.hasPlugin(AppPlugin)) {
90 | variants = project.android.applicationVariants
91 | } else {
92 | variants = project.android.libraryVariants
93 | }
94 | return variants
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/frodo-android-sample/src/main/java/com/fernandocejas/example/frodo/sample/ObservableSample.java:
--------------------------------------------------------------------------------
1 | package com.fernandocejas.example.frodo.sample;
2 |
3 | import android.view.View;
4 | import com.fernandocejas.frodo.annotation.RxLogObservable;
5 | import java.util.Arrays;
6 | import java.util.List;
7 | import rx.Observable;
8 | import rx.Subscriber;
9 | import rx.functions.Func0;
10 | import rx.schedulers.Schedulers;
11 |
12 | import static com.fernandocejas.frodo.annotation.RxLogObservable.Scope.EVENTS;
13 | import static com.fernandocejas.frodo.annotation.RxLogObservable.Scope.EVERYTHING;
14 | import static com.fernandocejas.frodo.annotation.RxLogObservable.Scope.NOTHING;
15 | import static com.fernandocejas.frodo.annotation.RxLogObservable.Scope.SCHEDULERS;
16 | import static com.fernandocejas.frodo.annotation.RxLogObservable.Scope.STREAM;
17 |
18 | public class ObservableSample {
19 | public ObservableSample() {
20 | }
21 |
22 | @RxLogObservable(EVERYTHING)
23 | public Observable numbers() {
24 | return Observable.just(1, 2);
25 | }
26 |
27 | @RxLogObservable
28 | public Observable moreNumbers() {
29 | return Observable.just(1, 2, 3, 4);
30 | }
31 |
32 | @RxLogObservable(STREAM)
33 | public Observable names() {
34 | return Observable.just("Fernando", "Silvia");
35 | }
36 |
37 | @RxLogObservable
38 | public Observable error() {
39 | return Observable.error(new IllegalArgumentException("My error"));
40 | }
41 |
42 | @RxLogObservable(SCHEDULERS)
43 | public Observable> list() {
44 | return Observable.just(buildDummyList());
45 | }
46 |
47 | @RxLogObservable(EVENTS)
48 | public Observable stringItemWithDefer() {
49 | return Observable.defer(new Func0>() {
50 | @Override public Observable call() {
51 | return Observable.create(new Observable.OnSubscribe() {
52 | @Override public void call(Subscriber super String> subscriber) {
53 | try {
54 | subscriber.onNext("String item Three");
55 | subscriber.onCompleted();
56 | } catch (Exception e) {
57 | subscriber.onError(e);
58 | }
59 | }
60 | }).subscribeOn(Schedulers.computation());
61 | }
62 | });
63 | }
64 |
65 | /**
66 | * Nothing should happen here when annotating this method with {@link RxLogObservable}
67 | * because it does not returns an {@link Observable}.
68 | */
69 | @RxLogObservable(NOTHING)
70 | public List buildDummyList() {
71 | return Arrays.asList(new MyDummyClass("Batman"), new MyDummyClass("Superman"));
72 | }
73 |
74 | @RxLogObservable
75 | public Observable strings() {
76 | return Observable.just("Hello", "My", "Name", "Is", "Fernando");
77 | }
78 |
79 | public Observable stringsWithError() {
80 | return Observable.error(new IllegalArgumentException("My Subscriber error"));
81 | }
82 |
83 | @RxLogObservable
84 | public Observable doSomething(View view) {
85 | return Observable.just(null);
86 | }
87 |
88 | @RxLogObservable
89 | public Observable sendNull() {
90 | return Observable.just(null);
91 | }
92 |
93 | @RxLogObservable
94 | public Observable doNothing() {
95 | return Observable.empty();
96 | }
97 |
98 | public Observable numbersBackpressure() {
99 | return Observable.create(new Observable.OnSubscribe() {
100 | @Override
101 | public void call(Subscriber super Integer> subscriber) {
102 | try {
103 | if (!subscriber.isUnsubscribed()) {
104 | for (int i = 1; i < 10000; i++) {
105 | subscriber.onNext(i);
106 | }
107 | subscriber.onCompleted();
108 | }
109 | } catch (Exception e) {
110 | subscriber.onError(e);
111 | }
112 | }
113 | });
114 | }
115 |
116 | public static final class MyDummyClass {
117 | private final String name;
118 |
119 | MyDummyClass(String name) {
120 | this.name = name;
121 | }
122 |
123 | @Override
124 | public String toString() {
125 | return "Name: " + name;
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/frodo-runtime/src/test/java/com/fernandocejas/frodo/internal/observable/LogEverythingObservableTest.java:
--------------------------------------------------------------------------------
1 | package com.fernandocejas.frodo.internal.observable;
2 |
3 | import com.fernandocejas.frodo.core.optional.Optional;
4 | import com.fernandocejas.frodo.internal.MessageManager;
5 | import com.fernandocejas.frodo.joinpoint.FrodoProceedingJoinPoint;
6 | import java.util.concurrent.TimeUnit;
7 | import org.junit.Before;
8 | import org.junit.Rule;
9 | import org.junit.Test;
10 | import org.junit.runner.RunWith;
11 | import org.mockito.Mock;
12 | import org.mockito.runners.MockitoJUnitRunner;
13 | import rx.observers.TestSubscriber;
14 | import rx.schedulers.Schedulers;
15 |
16 | import static org.assertj.core.api.Assertions.assertThat;
17 | import static org.mockito.Matchers.any;
18 | import static org.mockito.Matchers.anyString;
19 | import static org.mockito.Mockito.verify;
20 |
21 | @SuppressWarnings("unchecked") @RunWith(MockitoJUnitRunner.class)
22 | public class LogEverythingObservableTest {
23 |
24 | @Rule public ObservableRule observableRule = new ObservableRule(this.getClass());
25 |
26 | private LogEverythingObservable loggableObservable;
27 | private TestSubscriber subscriber;
28 |
29 | @Mock private MessageManager messageManager;
30 |
31 | @Before
32 | public void setUp() {
33 | subscriber = new TestSubscriber();
34 | loggableObservable = new LogEverythingObservable(observableRule.joinPoint(), messageManager,
35 | observableRule.info());
36 | }
37 |
38 | @Test
39 | public void shouldLogEverythingObservable() throws Throwable {
40 | loggableObservable.get(observableRule.stringType()).subscribe(subscriber);
41 |
42 | verify(messageManager).printObservableOnSubscribe(any(ObservableInfo.class));
43 | verify(messageManager).printObservableOnNextWithValue(any(ObservableInfo.class), anyString());
44 | verify(messageManager).printObservableOnCompleted(any(ObservableInfo.class));
45 | verify(messageManager).printObservableOnTerminate(any(ObservableInfo.class));
46 | verify(messageManager).printObservableItemTimeInfo(any(ObservableInfo.class));
47 | verify(messageManager).printObservableOnUnsubscribe(any(ObservableInfo.class));
48 | verify(messageManager).printObservableThreadInfo(any(ObservableInfo.class));
49 | }
50 |
51 | @Test
52 | public void shouldFillInObservableBasicInfo() throws Throwable {
53 | loggableObservable.get(observableRule.stringType()).subscribe(subscriber);
54 | final ObservableInfo observableInfo = loggableObservable.getInfo();
55 | final FrodoProceedingJoinPoint frodoProceedingJoinPoint = observableRule.joinPoint();
56 |
57 | assertThat(observableInfo.getClassSimpleName()).isEqualTo(
58 | frodoProceedingJoinPoint.getClassSimpleName());
59 | assertThat(observableInfo.getJoinPoint()).isEqualTo(frodoProceedingJoinPoint);
60 | assertThat(observableInfo.getMethodName()).isEqualTo(frodoProceedingJoinPoint.getMethodName());
61 | }
62 |
63 | @Test
64 | public void shouldFillInObservableThreadInfo() throws Throwable {
65 | loggableObservable.get(observableRule.stringType())
66 | .subscribeOn(Schedulers.immediate())
67 | .observeOn(Schedulers.immediate())
68 | .subscribe(subscriber);
69 |
70 | final ObservableInfo observableInfo = loggableObservable.getInfo();
71 | final Optional subscribeOnThread = observableInfo.getSubscribeOnThread();
72 | final Optional observeOnThread = observableInfo.getObserveOnThread();
73 | final String currentThreadName = Thread.currentThread().getName();
74 |
75 | assertThat(subscribeOnThread.isPresent()).isTrue();
76 | assertThat(observeOnThread.isPresent()).isTrue();
77 | assertThat(subscribeOnThread.get()).isEqualTo(currentThreadName);
78 | assertThat(observeOnThread.get()).isEqualTo(currentThreadName);
79 | }
80 |
81 | @Test
82 | public void shouldFillInObservableItemsInfo() throws Throwable {
83 | loggableObservable.get(observableRule.stringType())
84 | .delay(2, TimeUnit.SECONDS)
85 | .subscribe(subscriber);
86 |
87 | final ObservableInfo observableInfo = loggableObservable.getInfo();
88 | final Optional totalEmittedItems = observableInfo.getTotalEmittedItems();
89 | final Optional totalExecutionTime = observableInfo.getTotalExecutionTime();
90 |
91 | assertThat(totalEmittedItems.isPresent()).isTrue();
92 | assertThat(totalExecutionTime.isPresent()).isTrue();
93 | assertThat(totalEmittedItems.get()).isEqualTo(1);
94 | }
95 | }
--------------------------------------------------------------------------------
/frodo-runtime/src/test/java/com/fernandocejas/frodo/joinpoint/FrodoJoinPointTest.java:
--------------------------------------------------------------------------------
1 | package com.fernandocejas.frodo.joinpoint;
2 |
3 | import java.util.Arrays;
4 | import java.util.Collections;
5 | import java.util.List;
6 | import org.junit.Test;
7 | import org.junit.runner.RunWith;
8 | import org.mockito.runners.MockitoJUnitRunner;
9 | import rx.Observable;
10 |
11 | import static org.assertj.core.api.Assertions.assertThat;
12 |
13 | @RunWith(MockitoJUnitRunner.class)
14 | public class FrodoJoinPointTest {
15 |
16 | @Test
17 | public void shouldReturnCorrectParametersList() {
18 | final Class[] paramTypes = { String.class, String.class };
19 | final String[] paramNames = { "paramOne", "paramTwo" };
20 | final Object[] paramValues = { "ValueOne", "ValueTwo" };
21 |
22 | final TestJoinPoint testJoinPoint = new TestJoinPoint.Builder(this.getClass())
23 | .withParamTypes(paramTypes)
24 | .withParamNames(paramNames)
25 | .withParamValues(paramValues)
26 | .build();
27 | final FrodoJoinPoint frodoJoinPoint = new FrodoJoinPoint(testJoinPoint);
28 |
29 | final List paramTypeList = frodoJoinPoint.getMethodParamTypesList();
30 | final List paramNamesList = frodoJoinPoint.getMethodParamNamesList();
31 | final List paramValuesList = frodoJoinPoint.getMethodParamValuesList();
32 |
33 | assertThat(paramTypeList).isEqualTo(Arrays.asList(paramTypes));
34 | assertThat(paramNamesList).isEqualTo(Arrays.asList(paramNames));
35 | assertThat(paramValuesList).isEqualTo(Arrays.asList(paramValues));
36 | }
37 |
38 | @Test
39 | public void shouldHaveReturnType() {
40 | final TestJoinPoint testJoinPoint = new TestJoinPoint.Builder(this.getClass())
41 | .withReturnType(Observable.class)
42 | .build();
43 | final FrodoJoinPoint frodoJoinPoint = new FrodoJoinPoint(testJoinPoint);
44 |
45 | assertThat(frodoJoinPoint.hasReturnType(testJoinPoint)).isTrue();
46 | assertThat(frodoJoinPoint.getReturnType()).isEqualTo(Observable.class);
47 | }
48 |
49 | @Test
50 | public void shouldNotHaveReturnType() {
51 | final TestJoinPoint testJoinPoint = new TestJoinPoint.Builder(this.getClass())
52 | .withReturnType(void.class)
53 | .build();
54 | final FrodoJoinPoint frodoJoinPoint = new FrodoJoinPoint(testJoinPoint);
55 |
56 | assertThat(frodoJoinPoint.hasReturnType(testJoinPoint)).isFalse();
57 | assertThat(frodoJoinPoint.getReturnType()).isEqualTo(void.class);
58 | }
59 |
60 | @Test
61 | public void shouldReturnCorrectGenericReturnType() {
62 | final TestJoinPoint testJoinPoint = new TestJoinPoint.Builder(MyDummyClass.class, "buildDummyObservable")
63 | .withReturnType(Observable.class)
64 | .build();
65 | final FrodoJoinPoint frodoJoinPoint = new FrodoJoinPoint(testJoinPoint);
66 |
67 | assertThat(frodoJoinPoint.getGenericReturnTypes()).isEqualTo(
68 | Collections.singletonList(Observable.class));
69 | }
70 |
71 | @Test
72 | public void shouldReturnCorrectGenericParameterizedReturnType() {
73 | final TestJoinPoint testJoinPoint = new TestJoinPoint.Builder(MyDummyClass.class, "toString")
74 | .withReturnType(Observable.class)
75 | .build();
76 | final FrodoJoinPoint frodoJoinPoint = new FrodoJoinPoint(testJoinPoint);
77 |
78 | assertThat(frodoJoinPoint.getGenericReturnTypes()).isEqualTo(
79 | Collections.singletonList(String.class));
80 | }
81 |
82 | @Test
83 | public void mustGenerateJoinPointUniqueNameForEqualityComparison() {
84 | final Class[] paramTypes = { String.class, String.class };
85 | final String[] paramNames = { "paramOne", "paramTwo" };
86 | final Object[] paramValues = { "ValueOne", "ValueTwo" };
87 |
88 | final TestJoinPoint testJoinPointOne = new TestJoinPoint.Builder(this.getClass())
89 | .withParamTypes(paramTypes)
90 | .withParamNames(paramNames)
91 | .withParamValues(paramValues)
92 | .build();
93 | final TestJoinPoint testJoinPointTwo = new TestJoinPoint.Builder(this.getClass()).build();
94 |
95 | final FrodoJoinPoint frodoJoinPointOne = new FrodoJoinPoint(testJoinPointOne);
96 | final FrodoJoinPoint frodoJoinPointTwo = new FrodoJoinPoint(testJoinPointTwo);
97 |
98 | assertThat(frodoJoinPointOne).isNotEqualTo(frodoJoinPointTwo);
99 | assertThat(frodoJoinPointOne.hashCode()).isNotEqualTo(frodoJoinPointTwo.hashCode());
100 | }
101 |
102 | private static class MyDummyClass {
103 | public MyDummyClass() {
104 | }
105 |
106 | public Observable buildDummyObservable() {
107 | return Observable.empty();
108 | }
109 |
110 | @Override public String toString() {
111 | return this.getClass().getSimpleName();
112 | }
113 | }
114 | }
--------------------------------------------------------------------------------
/frodo-runtime/src/main/java/com/fernandocejas/frodo/core/functions/Functions.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2007 The Guava Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.fernandocejas.frodo.core.functions;
18 |
19 | import com.fernandocejas.frodo.core.objects.MoreObjects;
20 | import java.io.Serializable;
21 | import org.jetbrains.annotations.Nullable;
22 |
23 | import static com.fernandocejas.frodo.core.checks.Preconditions.checkNotNull;
24 |
25 | /**
26 | * Static utility methods pertaining to {@code Function} instances.
27 | *
28 | * All methods return serializable functions as long as they're given serializable parameters.
29 | *
30 | * See the Guava User Guide article on the use of {@code
32 | * Function} .
33 | *
34 | * @author Mike Bostock
35 | * @author Jared Levy
36 | * @since 2.0 (imported from Google Collections Library)
37 | *
38 | *
This class contains code derived from Google
39 | * Guava
40 | */
41 | public final class Functions {
42 |
43 | /**
44 | * Returns a function that calls {@code toString()} on its argument. The function does not accept
45 | * nulls; it will throw a {@link NullPointerException} when applied to {@code null}.
46 | *
47 | * Warning: The returned function may not be consistent with equals (as
48 | * documented at {@link Function#apply}). For example, this function yields different results for
49 | * the two equal instances {@code ImmutableSet.of(1, 2)} and {@code ImmutableSet.of(2, 1)}.
50 | */
51 | public static Function toStringFunction() {
52 | return ToStringFunction.INSTANCE;
53 | }
54 |
55 | // enum singleton pattern
56 | private enum ToStringFunction implements Function {
57 | INSTANCE;
58 |
59 | @Override
60 | public String apply(Object o) {
61 | if (o != null) {
62 | return o.toString();
63 | } else {
64 | throw new NullPointerException();
65 | }
66 | }
67 |
68 | @Override
69 | public String toString() {
70 | return "toString";
71 | }
72 | }
73 |
74 | /**
75 | * Returns the identity function.
76 | */
77 | // implementation is "fully variant"; E has become a "pass-through" type
78 | @SuppressWarnings("unchecked")
79 | public static Function identity() {
80 | return (Function) IdentityFunction.INSTANCE;
81 | }
82 |
83 | private enum IdentityFunction implements Function {
84 | INSTANCE;
85 |
86 | @Override
87 | public Object apply(Object o) {
88 | checkNotNull(o);
89 | return o;
90 | }
91 |
92 | @Override
93 | public String toString() {
94 | return "identity";
95 | }
96 | }
97 |
98 | /**
99 | * Creates a function that returns {@code value} for any input.
100 | *
101 | * @param value the constant value for the function to return
102 | * @return a function that always returns {@code value}
103 | */
104 | public static Function constant(@Nullable E value) {
105 | return new ConstantFunction<>(value);
106 | }
107 |
108 | private static class ConstantFunction implements Function, Serializable {
109 | private static final long serialVersionUID = 0;
110 |
111 | private final E value;
112 |
113 | public ConstantFunction(@Nullable E value) {
114 | this.value = value;
115 | }
116 |
117 | @Override
118 | public E apply(Object from) {
119 | checkNotNull(from);
120 | return value;
121 | }
122 |
123 | @Override
124 | public boolean equals(@Nullable Object obj) {
125 | if (obj instanceof ConstantFunction) {
126 | ConstantFunction> that = (ConstantFunction>) obj;
127 | return MoreObjects.equal(value, that.value);
128 | }
129 | return false;
130 | }
131 |
132 | @Override
133 | public int hashCode() {
134 | return (value == null) ? 0 : value.hashCode();
135 | }
136 |
137 | @Override
138 | public String toString() {
139 | return "constant(" + value + ")";
140 | }
141 | }
142 |
143 | private Functions() {
144 | // no instances
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/frodo-runtime/src/test/java/com/fernandocejas/frodo/joinpoint/TestJoinPoint.java:
--------------------------------------------------------------------------------
1 | package com.fernandocejas.frodo.joinpoint;
2 |
3 | import java.lang.reflect.Method;
4 | import java.util.Observable;
5 | import org.aspectj.lang.JoinPoint;
6 | import org.aspectj.lang.Signature;
7 | import org.aspectj.lang.reflect.MethodSignature;
8 | import org.aspectj.lang.reflect.SourceLocation;
9 | import org.mockito.Mock;
10 | import org.mockito.MockitoAnnotations;
11 |
12 | import static org.mockito.BDDMockito.given;
13 |
14 | @SuppressWarnings("unchecked")
15 | public class TestJoinPoint implements JoinPoint {
16 |
17 | @Mock private MethodSignature methodSignature;
18 |
19 | private Method method;
20 |
21 | private final Class declaringType;
22 | private final String methodName;
23 | private final Class methodReturnType;
24 | private final String methodReturnValue;
25 | private final Class[] methodParameterTypes;
26 | private final String[] methodParameterNames;
27 | private final Object[] methodParameterValues;
28 |
29 | private TestJoinPoint(Builder builder) {
30 | this.declaringType = builder.declaringType;
31 | this.methodName = builder.methodName;
32 | this.methodReturnType = builder.methodReturnType;
33 | this.methodReturnValue = builder.methodReturnValue;
34 | this.methodParameterTypes = builder.methodParameterTypes;
35 | this.methodParameterNames = builder.methodParameterNames;
36 | this.methodParameterValues = builder.methodParameterValues;
37 |
38 | MockitoAnnotations.initMocks(this);
39 | try {
40 | given(methodSignature.getDeclaringType()).willReturn(declaringType);
41 | given(methodSignature.getName()).willReturn(methodName);
42 | given(methodSignature.getParameterTypes()).willReturn(methodParameterTypes);
43 | given(methodSignature.getParameterNames()).willReturn(methodParameterNames);
44 | given(methodSignature.getReturnType()).willReturn(methodReturnType);
45 | given(methodSignature.getMethod()).willReturn(declaringType.getMethod(methodName));
46 | } catch (NoSuchMethodException e) {
47 | e.printStackTrace();
48 | }
49 | }
50 |
51 | @Override public String toShortString() {
52 | return methodName;
53 | }
54 |
55 | @Override public String toLongString() {
56 | return declaringType.getSimpleName() + "#" + methodName;
57 | }
58 |
59 | @Override public Object getThis() {
60 | return this;
61 | }
62 |
63 | @Override public Object getTarget() {
64 | try {
65 | return declaringType.newInstance();
66 | } catch (InstantiationException | IllegalAccessException e) {
67 | e.printStackTrace();
68 | }
69 | return null;
70 | }
71 |
72 | @Override public Object[] getArgs() {
73 | return methodParameterValues;
74 | }
75 |
76 | @Override public Signature getSignature() {
77 | return methodSignature;
78 | }
79 |
80 | @Override public SourceLocation getSourceLocation() {
81 | return null;
82 | }
83 |
84 | @Override public String getKind() {
85 | return null;
86 | }
87 |
88 | @Override public StaticPart getStaticPart() {
89 | return null;
90 | }
91 |
92 | public Class getMethodReturnType() {
93 | return methodReturnType;
94 | }
95 |
96 | public String getMethodReturnValue() {
97 | return methodReturnValue;
98 | }
99 |
100 | public static class Builder {
101 | private final Class declaringType;
102 | private final String methodName;
103 |
104 | private Class methodReturnType = Observable.class;
105 | private String methodReturnValue = "android10";
106 | private Class[] methodParameterTypes = new Class[] {};
107 | private String[] methodParameterNames = new String[] {};
108 | private Object[] methodParameterValues = new Object[] {};
109 |
110 | public Builder(Class declaringType) {
111 | this(declaringType, "toString");
112 | }
113 |
114 | public Builder(Class declaringType, String methodName) {
115 | this.declaringType = declaringType;
116 | this.methodName = methodName;
117 | }
118 |
119 | public Builder withReturnType(Class returnType) {
120 | this.methodReturnType = returnType;
121 | return this;
122 | }
123 |
124 | public Builder withReturnValue(String returnValue) {
125 | this.methodReturnValue = returnValue;
126 | return this;
127 | }
128 |
129 | public Builder withParamTypes(Class... paramTypes) {
130 | this.methodParameterTypes = paramTypes;
131 | return this;
132 | }
133 |
134 | public Builder withParamNames(String... paramNames) {
135 | this.methodParameterNames = paramNames;
136 | return this;
137 | }
138 |
139 | public Builder withParamValues(Object... paramValues) {
140 | this.methodParameterValues = paramValues;
141 | return this;
142 | }
143 |
144 | public TestJoinPoint build() {
145 | return new TestJoinPoint(this);
146 | }
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/frodo-runtime/build.gradle:
--------------------------------------------------------------------------------
1 | import com.android.build.gradle.LibraryPlugin
2 | import org.aspectj.bridge.IMessage
3 | import org.aspectj.bridge.MessageHandler
4 | import org.aspectj.tools.ajc.Main
5 |
6 | buildscript {
7 | repositories {
8 | jcenter()
9 | maven {
10 | url "http://dl.bintray.com/android10/maven"
11 | }
12 | }
13 | dependencies {
14 | classpath "com.android.tools.build:gradle:$project.androidBuildToolsVersion"
15 | classpath "org.aspectj:aspectjtools:$project.aspectjVersion"
16 | }
17 | }
18 |
19 | repositories {
20 | maven {
21 | url "http://dl.bintray.com/android10/maven"
22 | }
23 | }
24 |
25 | apply plugin: 'com.android.library'
26 | apply plugin: 'com.novoda.bintray-release'
27 |
28 | dependencies {
29 | compile "org.jetbrains:annotations:$project.annotationsVersion"
30 | compile "org.aspectj:aspectjrt:$project.aspectjVersion"
31 | compile "io.reactivex:rxjava:$project.rxJavaVersion"
32 | compile project(':frodo-api')
33 |
34 | testCompile "junit:junit:$project.junitVersion"
35 | testCompile "org.assertj:assertj-core:$project.assertJVersion"
36 | testCompile "org.mockito:mockito-core:$project.mockitoVersion"
37 | testCompile "org.robolectric:robolectric:$project.robolectricVersion"
38 | }
39 |
40 | android {
41 | buildToolsVersion project.buildToolsVersion
42 | compileSdkVersion Integer.parseInt(project.compileSdkVersion)
43 |
44 | defaultConfig {
45 | versionName project.version
46 | minSdkVersion Integer.parseInt(project.minSdkVersion)
47 | targetSdkVersion Integer.parseInt(project.targetSdkVersion)
48 | }
49 |
50 | lintOptions {
51 | abortOnError false
52 | }
53 |
54 | compileOptions {
55 | targetCompatibility JavaVersion.VERSION_1_7
56 | sourceCompatibility JavaVersion.VERSION_1_7
57 | }
58 | }
59 |
60 | android.libraryVariants.all { variant ->
61 | LibraryPlugin plugin = project.plugins.getPlugin(LibraryPlugin)
62 |
63 | //Compile aspects
64 | JavaCompile javaCompile = variant.javaCompile
65 | javaCompile.doLast {
66 | String[] args = ["-showWeaveInfo",
67 | "-1.5",
68 | "-inpath", javaCompile.destinationDir.toString(),
69 | "-aspectpath", javaCompile.classpath.asPath,
70 | "-d", javaCompile.destinationDir.toString(),
71 | "-classpath", javaCompile.classpath.asPath,
72 | "-bootclasspath", plugin.project.android.bootClasspath.join(
73 | File.pathSeparator)]
74 |
75 | final MessageHandler handler = new MessageHandler(true);
76 | final def log = project.logger
77 | new Main().run(args, handler)
78 | for (IMessage message : handler.getMessages(null, true)) {
79 | switch (message.getKind()) {
80 | case IMessage.ABORT:
81 | case IMessage.ERROR:
82 | case IMessage.FAIL:
83 | log.error message.message, message.thrown
84 | break;
85 | case IMessage.INFO:
86 | log.info message.message, message.thrown
87 | break;
88 | case IMessage.DEBUG:
89 | log.debug message.message, message.thrown
90 | break;
91 | }
92 | }
93 | }
94 |
95 | //Change aar file name: add version name only for debug version (development)
96 | if (variant.name == 'debug') {
97 | variant.outputs.each { output ->
98 | if (output.outputFile != null && output.outputFile.name.endsWith('.aar')) {
99 | def file = output.outputFile
100 | def fileName = file.name.replace("${variant.buildType.name}.aar", "${project.version}.aar")
101 | output.outputFile = new File(file.parent, fileName)
102 | }
103 | }
104 | }
105 | }
106 |
107 | //Install in maven local repository
108 | task install(type: Exec, dependsOn: assemble) {
109 | description = "Installs Frodo generated artifact in the local maven repository."
110 | executable = 'mvn'
111 | args = ["install:install-file",
112 | "-DgroupId=${project.group}",
113 | "-DartifactId=${project.name}",
114 | "-Dversion=${project.version}",
115 | "-Dpackaging=aar",
116 | "-Dfile=build/outputs/aar/frodo-runtime-${project.version}.aar"]
117 | }
118 |
119 | publish {
120 | userOrg = project.user
121 | groupId = project.group
122 | artifactId = project.name
123 | publishVersion = project.version
124 | desc = 'Library based on Aspects for Logging RxJava Objects based on Gradle Plugin.'
125 | website = project.website
126 | }
127 |
128 | task androidJavadocs(type: Javadoc) {
129 | source = android.sourceSets.main.java.srcDirs
130 | //noinspection GroovyAssignabilityCheck
131 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
132 | }
133 |
134 | task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
135 | classifier = 'javadoc'
136 | //noinspection GroovyAssignabilityCheck
137 | from androidJavadocs.destinationDir
138 | }
139 |
140 | task androidSourcesJar(type: Jar) {
141 | classifier = 'sources'
142 | //noinspection GroovyAssignabilityCheck
143 | from android.sourceSets.main.java.srcDirs
144 | }
145 |
--------------------------------------------------------------------------------
/frodo-runtime/src/main/java/com/fernandocejas/frodo/aspect/LogSubscriber.java:
--------------------------------------------------------------------------------
1 | package com.fernandocejas.frodo.aspect;
2 |
3 | import com.fernandocejas.frodo.internal.Counter;
4 | import com.fernandocejas.frodo.internal.MessageManager;
5 | import com.fernandocejas.frodo.internal.StopWatch;
6 | import com.fernandocejas.frodo.joinpoint.FrodoJoinPoint;
7 | import org.aspectj.lang.JoinPoint;
8 | import org.aspectj.lang.annotation.After;
9 | import org.aspectj.lang.annotation.Aspect;
10 | import org.aspectj.lang.annotation.Before;
11 | import org.aspectj.lang.annotation.Pointcut;
12 | import rx.Subscriber;
13 |
14 | @Aspect
15 | public class LogSubscriber {
16 | private static final String CLASS =
17 | "within(@com.fernandocejas.frodo.annotation.RxLogSubscriber *) && if()";
18 |
19 | private static final String METHOD_ON_START = "execution(void *.onStart())";
20 | private static final String METHOD_ON_NEXT = "execution(void *.onNext(..))";
21 | private static final String METHOD_ON_ERROR =
22 | "execution(void *.onError(java.lang.Throwable)) && args(throwable)";
23 | private static final String METHOD_ON_COMPLETED = "execution(void *.onCompleted())";
24 |
25 | private static final String METHOD_REQUEST = "call(void *.request(long)) && args(numberOfItems)";
26 | private static final String METHOD_UN_SUBSCRIBE = "call(void *.unsubscribe())";
27 |
28 | private final Counter counter;
29 | private final StopWatch stopWatch;
30 | private final MessageManager messageManager;
31 | private boolean isFirstElementEmitted = true;
32 |
33 | public LogSubscriber() {
34 | this(new Counter(), new StopWatch(), new MessageManager());
35 | }
36 |
37 | public LogSubscriber(Counter counter, StopWatch stopWatch, MessageManager messageManager) {
38 | this.counter = counter;
39 | this.stopWatch = stopWatch;
40 | this.messageManager = messageManager;
41 | }
42 |
43 | @Pointcut(CLASS)
44 | public static boolean classAnnotatedWithRxLogSubscriber(JoinPoint joinPoint) {
45 | return joinPoint.getTarget() instanceof Subscriber;
46 | }
47 |
48 | @Pointcut(METHOD_ON_START)
49 | public void onStartMethodExecution() {
50 | }
51 |
52 | @Pointcut(METHOD_ON_NEXT)
53 | public void onNextMethodExecution() {
54 | }
55 |
56 | @Pointcut(METHOD_ON_ERROR)
57 | public void onErrorMethodExecution(Throwable throwable) {
58 | }
59 |
60 | @Pointcut(METHOD_ON_COMPLETED)
61 | public void onCompletedMethodExecution() {
62 | }
63 |
64 | @Pointcut(METHOD_REQUEST)
65 | public void onRequestMethodCall(long numberOfItems) {
66 | }
67 |
68 | @Pointcut(METHOD_UN_SUBSCRIBE)
69 | public void onUnsubscribeMethodCall() {
70 | }
71 |
72 | @Before("classAnnotatedWithRxLogSubscriber(joinPoint) && onStartMethodExecution()")
73 | public void beforeOnStartExecution(JoinPoint joinPoint) {
74 | messageManager.printSubscriberOnStart(joinPoint.getTarget().getClass().getSimpleName());
75 | }
76 |
77 | @Before("classAnnotatedWithRxLogSubscriber(joinPoint) && onNextMethodExecution()")
78 | public void beforeOnNextExecution(JoinPoint joinPoint) {
79 | countAndMeasureTime();
80 | final FrodoJoinPoint frodoJoinPoint = new FrodoJoinPoint(joinPoint);
81 | final Object value = frodoJoinPoint.getMethodParamValuesList().isEmpty() ? null
82 | : frodoJoinPoint.getMethodParamValuesList().get(0);
83 | messageManager.printSubscriberOnNext(joinPoint.getTarget().getClass().getSimpleName(), value,
84 | Thread.currentThread().getName());
85 | }
86 |
87 | @After(value = "classAnnotatedWithRxLogSubscriber(joinPoint) && onErrorMethodExecution(throwable)",
88 | argNames = "joinPoint,throwable")
89 | public void afterOnErrorExecution(JoinPoint joinPoint, Throwable throwable) {
90 | stopWatch.stop();
91 | messageManager.printSubscriberOnError(joinPoint.getTarget().getClass().getSimpleName(),
92 | throwable.toString(),
93 | stopWatch.getTotalTimeMillis(), counter.tally());
94 | resetCounters();
95 | }
96 |
97 | @Before("classAnnotatedWithRxLogSubscriber(joinPoint) && onCompletedMethodExecution()")
98 | public void beforeOnCompletedExecution(JoinPoint joinPoint) {
99 | stopWatch.stop();
100 | messageManager.printSubscriberOnCompleted(joinPoint.getTarget().getClass().getSimpleName(),
101 | stopWatch.getTotalTimeMillis(), counter.tally());
102 | resetCounters();
103 | }
104 |
105 | @After("classAnnotatedWithRxLogSubscriber(joinPoint) && onUnsubscribeMethodCall()")
106 | public void afterUnsubscribeMethodCall(JoinPoint joinPoint) {
107 | messageManager.printSubscriberUnsubscribe(joinPoint.getTarget().getClass().getSimpleName());
108 | }
109 |
110 | @After("classAnnotatedWithRxLogSubscriber(joinPoint) && onRequestMethodCall(numberOfItems)")
111 | public void afterRequestMethodCall(JoinPoint joinPoint, long numberOfItems) {
112 | messageManager.printSubscriberRequestedItems(joinPoint.getTarget().getClass().getSimpleName(),
113 | numberOfItems);
114 | }
115 |
116 | private void countAndMeasureTime() {
117 | counter.increment();
118 | if (isFirstElementEmitted) {
119 | stopWatch.start();
120 | }
121 | isFirstElementEmitted = false;
122 | }
123 |
124 | private void resetCounters() {
125 | isFirstElementEmitted = true;
126 | counter.clear();
127 | stopWatch.reset();
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/frodo-runtime/src/main/java/com/fernandocejas/frodo/internal/MessageManager.java:
--------------------------------------------------------------------------------
1 | package com.fernandocejas.frodo.internal;
2 |
3 | import com.fernandocejas.frodo.internal.observable.ObservableInfo;
4 |
5 | public class MessageManager {
6 |
7 | private final MessageBuilder messageBuilder;
8 | private final DebugLog debugLog;
9 |
10 | public MessageManager() {
11 | this(new MessageBuilder(), new DebugLog());
12 | }
13 |
14 | public MessageManager(MessageBuilder messageBuilder, DebugLog debugLog) {
15 | this.messageBuilder = messageBuilder;
16 | this.debugLog = debugLog;
17 | }
18 |
19 | private void printMessage(String tag, String message) {
20 | debugLog.log(tag, message);
21 | }
22 |
23 | public void printObservableInfo(ObservableInfo observableInfo) {
24 | final String message = messageBuilder.buildObservableInfoMessage(observableInfo);
25 | this.printMessage(observableInfo.getClassSimpleName(), message);
26 | }
27 |
28 | public void printObservableOnSubscribe(ObservableInfo observableInfo) {
29 | final String message = messageBuilder.buildObservableOnSubscribeMessage(observableInfo);
30 | this.printMessage(observableInfo.getClassSimpleName(), message);
31 | }
32 |
33 | public void printObservableOnNextWithValue(ObservableInfo observableInfo, T value) {
34 | final String message =
35 | messageBuilder.buildObservableOnNextWithValueMessage(observableInfo, value);
36 | this.printMessage(observableInfo.getClassSimpleName(), message);
37 | }
38 |
39 | public void printObservableOnNext(ObservableInfo observableInfo) {
40 | final String message = messageBuilder.buildObservableOnNextMessage(observableInfo);
41 | this.printMessage(observableInfo.getClassSimpleName(), message);
42 | }
43 |
44 | public void printObservableOnError(ObservableInfo observableInfo,
45 | Throwable throwable) {
46 | final String message =
47 | messageBuilder.buildObservableOnErrorMessage(observableInfo, throwable.getMessage());
48 | this.printMessage(observableInfo.getClassSimpleName(), message);
49 | }
50 |
51 | public void printObservableOnCompleted(ObservableInfo observableInfo) {
52 | final String message = messageBuilder.buildObservableOnCompletedMessage(observableInfo);
53 | this.printMessage(observableInfo.getClassSimpleName(), message);
54 | }
55 |
56 | public void printObservableOnTerminate(ObservableInfo observableInfo) {
57 | final String message = messageBuilder.buildObservableOnTerminateMessage(observableInfo);
58 | this.printMessage(observableInfo.getClassSimpleName(), message);
59 | }
60 |
61 | public void printObservableOnUnsubscribe(ObservableInfo observableInfo) {
62 | final String message = messageBuilder.buildObservableOnUnsubscribeMessage(observableInfo);
63 | this.printMessage(observableInfo.getClassSimpleName(), message);
64 | }
65 |
66 | public void printSubscriberOnStart(String subscriberName) {
67 | final String message = messageBuilder.buildSubscriberOnStartMessage(subscriberName);
68 | this.printMessage(subscriberName, message);
69 | }
70 |
71 | public void printSubscriberOnNext(String subscriberName, Object value, String threadName) {
72 | final String message =
73 | messageBuilder.buildSubscriberOnNextMessage(subscriberName, value, threadName);
74 | this.printMessage(subscriberName, message);
75 | }
76 |
77 | public void printSubscriberOnError(String subscriberName, String error, long executionTimeMillis,
78 | int receivedItems) {
79 | final String itemTimeMessage =
80 | messageBuilder.buildSubscriberItemTimeMessage(subscriberName, executionTimeMillis,
81 | receivedItems);
82 | final String onErrorMessage =
83 | messageBuilder.buildSubscriberOnErrorMessage(subscriberName, error);
84 | this.printMessage(subscriberName, itemTimeMessage);
85 | this.printMessage(subscriberName, onErrorMessage);
86 | }
87 |
88 | public void printSubscriberOnCompleted(String subscriberName, long executionTimeMillis,
89 | int receivedItems) {
90 | final String itemTimeMessage =
91 | messageBuilder.buildSubscriberItemTimeMessage(subscriberName, executionTimeMillis,
92 | receivedItems);
93 | final String onCompleteMessage =
94 | messageBuilder.buildSubscriberOnCompletedMessage(subscriberName);
95 | this.printMessage(subscriberName, itemTimeMessage);
96 | this.printMessage(subscriberName, onCompleteMessage);
97 | }
98 |
99 | public void printSubscriberRequestedItems(String subscriberName, long requestedItems) {
100 | final String message =
101 | messageBuilder.buildSubscriberRequestedItemsMessage(subscriberName, requestedItems);
102 | this.printMessage(subscriberName, message);
103 | }
104 |
105 | public void printSubscriberUnsubscribe(String subscriberName) {
106 | final String message = messageBuilder.buildSubscriberUnsubscribeMessage(subscriberName);
107 | this.printMessage(subscriberName, message);
108 | }
109 |
110 | public void printObservableItemTimeInfo(ObservableInfo observableInfo) {
111 | final String message = messageBuilder.buildObservableItemTimeInfoMessage(observableInfo);
112 | this.printMessage(observableInfo.getClassSimpleName(), message);
113 | }
114 |
115 | public void printObservableThreadInfo(ObservableInfo observableInfo) {
116 | if (observableInfo.getSubscribeOnThread().isPresent() ||
117 | observableInfo.getObserveOnThread().isPresent()) {
118 | final String message = messageBuilder.buildObservableThreadInfoMessage(observableInfo);
119 | this.printMessage(observableInfo.getClassSimpleName(), message);
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/frodo-runtime/src/test/java/com/fernandocejas/frodo/aspect/LogSubscriberTest.java:
--------------------------------------------------------------------------------
1 | package com.fernandocejas.frodo.aspect;
2 |
3 | import com.fernandocejas.frodo.internal.Counter;
4 | import com.fernandocejas.frodo.internal.MessageManager;
5 | import com.fernandocejas.frodo.internal.StopWatch;
6 | import com.fernandocejas.frodo.joinpoint.TestJoinPoint;
7 | import com.fernandocejas.frodo.joinpoint.TestProceedingJoinPoint;
8 | import org.aspectj.lang.JoinPoint;
9 | import org.junit.Before;
10 | import org.junit.Test;
11 | import org.junit.runner.RunWith;
12 | import org.mockito.Mock;
13 | import org.mockito.runners.MockitoJUnitRunner;
14 | import rx.observers.TestSubscriber;
15 |
16 | import static org.assertj.core.api.Assertions.assertThat;
17 | import static org.mockito.BDDMockito.given;
18 | import static org.mockito.Matchers.anyInt;
19 | import static org.mockito.Matchers.anyLong;
20 | import static org.mockito.Matchers.anyObject;
21 | import static org.mockito.Matchers.anyString;
22 | import static org.mockito.Matchers.eq;
23 | import static org.mockito.Mockito.mock;
24 | import static org.mockito.Mockito.verify;
25 | import static org.mockito.Mockito.verifyNoMoreInteractions;
26 |
27 | @RunWith(MockitoJUnitRunner.class)
28 | public class LogSubscriberTest {
29 |
30 | private LogSubscriber logSubscriber;
31 |
32 | @Mock private Counter counter;
33 | @Mock private StopWatch stopWatch;
34 | @Mock private MessageManager messageManager;
35 |
36 | private TestSubscriber subscriber;
37 | private TestJoinPoint joinPoint;
38 |
39 | @Before
40 | public void setUp() {
41 | logSubscriber = new LogSubscriber(counter, stopWatch, messageManager);
42 | subscriber = new TestSubscriber();
43 | joinPoint = new TestJoinPoint.Builder(subscriber.getClass())
44 | .withParamTypes(String.class)
45 | .withParamNames("param")
46 | .withParamValues("value")
47 | .build();
48 | }
49 |
50 | @Test
51 | public void annotatedClassMustCheckTargetType() {
52 | final JoinPoint joinPoint = mock(JoinPoint.class);
53 | given(joinPoint.getTarget()).willReturn(subscriber);
54 |
55 | assertThat(LogSubscriber.classAnnotatedWithRxLogSubscriber(joinPoint)).isTrue();
56 | verify(joinPoint).getTarget();
57 | verifyNoMoreInteractions(joinPoint);
58 | }
59 |
60 | @Test
61 | public void shouldWeaveClassOfTypeSubscriber() {
62 | final TestJoinPoint joinPoint = new TestJoinPoint.Builder(subscriber.getClass()).build();
63 | final TestProceedingJoinPoint proceedingJoinPoint = new TestProceedingJoinPoint(joinPoint);
64 |
65 | assertThat(LogSubscriber.classAnnotatedWithRxLogSubscriber(proceedingJoinPoint)).isTrue();
66 | }
67 |
68 | @Test
69 | public void shouldNotWeaveClassOfOtherTypeThanSubscriber() {
70 | final TestJoinPoint joinPoint = new TestJoinPoint.Builder(this.getClass()).build();
71 | final TestProceedingJoinPoint proceedingJoinPoint = new TestProceedingJoinPoint(joinPoint);
72 |
73 | assertThat(LogSubscriber.classAnnotatedWithRxLogSubscriber(proceedingJoinPoint)).isFalse();
74 | }
75 |
76 | @Test
77 | public void printOnStartMessageBeforeSubscriberOnStartExecution() {
78 | logSubscriber.beforeOnStartExecution(joinPoint);
79 |
80 | verify(messageManager).printSubscriberOnStart(subscriber.getClass().getSimpleName());
81 | }
82 |
83 | @Test
84 | public void printOnNextMessageBeforeSubscriberOnNextExecution() {
85 | logSubscriber.beforeOnNextExecution(joinPoint);
86 |
87 | verify(counter).increment();
88 | verify(stopWatch).start();
89 | verify(messageManager).printSubscriberOnNext(eq(subscriber.getClass().getSimpleName()),
90 | eq("value"), anyString());
91 | }
92 |
93 | @Test public void printOnNextMessageBeforeSubscriberOnNextExecutionWithEmptyValues() {
94 | final TestJoinPoint joinPointTest =
95 | new TestJoinPoint.Builder(subscriber.getClass()).withParamTypes(String.class)
96 | .withParamNames("param")
97 | .withParamValues()
98 | .build();
99 | logSubscriber.beforeOnNextExecution(joinPointTest);
100 |
101 | verify(counter).increment();
102 | verify(stopWatch).start();
103 | verify(messageManager).printSubscriberOnNext(eq(subscriber.getClass().getSimpleName()),
104 | anyObject(), anyString());
105 | }
106 |
107 | @Test
108 | public void printOnErrorMessageAfterSubscriberOnErrorExecution() {
109 | logSubscriber.afterOnErrorExecution(joinPoint, new IllegalStateException());
110 |
111 | verify(stopWatch).stop();
112 | verify(counter).tally();
113 | verify(messageManager).printSubscriberOnError(eq(subscriber.getClass().getSimpleName()),
114 | anyString(), anyLong(), anyInt());
115 | verify(counter).clear();
116 | verify(stopWatch).reset();
117 | }
118 |
119 | @Test
120 | public void printOnCompleteMessageBeforeSubscriberOnCompleteExecution() {
121 | logSubscriber.beforeOnCompletedExecution(joinPoint);
122 |
123 | verify(stopWatch).stop();
124 | verify(messageManager).printSubscriberOnCompleted(eq(subscriber.getClass().getSimpleName()),
125 | anyLong(), anyInt());
126 | verify(counter).tally();
127 | verify(counter).clear();
128 | verify(stopWatch).getTotalTimeMillis();
129 | verify(stopWatch).reset();
130 | }
131 |
132 | @Test
133 | public void printUnsubscribeMessageAfterSubscriberUnsubscribeMethodCall() {
134 | logSubscriber.afterUnsubscribeMethodCall(joinPoint);
135 |
136 | verify(messageManager).printSubscriberUnsubscribe(subscriber.getClass().getSimpleName());
137 | }
138 |
139 | @Test
140 | public void printRequestedItemsAfterSubscriberRequestMethodCall() {
141 | logSubscriber.afterRequestMethodCall(joinPoint, 10);
142 |
143 | verify(messageManager).printSubscriberRequestedItems(subscriber.getClass().getSimpleName(), 10);
144 | }
145 | }
--------------------------------------------------------------------------------
/frodo-android-sample/src/main/java/com/fernandocejas/example/frodo/SamplesActivity.java:
--------------------------------------------------------------------------------
1 | package com.fernandocejas.example.frodo;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.view.View;
6 | import android.widget.Button;
7 | import android.widget.Toast;
8 | import com.fernandocejas.example.frodo.sample.MySubscriber;
9 | import com.fernandocejas.example.frodo.sample.MySubscriberBackpressure;
10 | import com.fernandocejas.example.frodo.sample.MySubscriberVoid;
11 | import com.fernandocejas.example.frodo.sample.ObservableSample;
12 | import java.util.List;
13 | import rx.Subscriber;
14 | import rx.android.schedulers.AndroidSchedulers;
15 | import rx.functions.Action1;
16 | import rx.schedulers.Schedulers;
17 |
18 | public class SamplesActivity extends Activity {
19 |
20 | private Button btnRxLogObservable;
21 | private Button btnRxLogSubscriber;
22 |
23 | private View.OnClickListener rxLogObservableListener = new View.OnClickListener() {
24 | @Override
25 | public void onClick(View v) {
26 | ObservableSample observableSample = new ObservableSample();
27 |
28 | observableSample.stringItemWithDefer()
29 | .observeOn(AndroidSchedulers.mainThread())
30 | .subscribe();
31 |
32 | observableSample.numbers()
33 | .subscribeOn(Schedulers.newThread())
34 | .observeOn(AndroidSchedulers.mainThread())
35 | .subscribe(new Action1() {
36 | @Override
37 | public void call(Integer integer) {
38 | toastMessage("onNext() Integer--> " + String.valueOf(integer));
39 | }
40 | });
41 |
42 | observableSample.moreNumbers().toList().toBlocking().single();
43 |
44 | observableSample.names()
45 | .subscribeOn(Schedulers.newThread())
46 | .observeOn(AndroidSchedulers.mainThread())
47 | .subscribe(new Action1() {
48 | @Override
49 | public void call(String string) {
50 | toastMessage("onNext() String--> " + string);
51 | }
52 | });
53 |
54 | observableSample.error()
55 | .observeOn(AndroidSchedulers.mainThread())
56 | .subscribe(new Subscriber() {
57 | @Override
58 | public void onCompleted() {
59 | //nothing here
60 | }
61 |
62 | @Override
63 | public void onError(Throwable e) {
64 | toastMessage("onError() --> " + e.getMessage());
65 | }
66 |
67 | @Override
68 | public void onNext(String s) {
69 | //nothing here
70 | }
71 | });
72 |
73 | observableSample.list()
74 | .subscribeOn(Schedulers.newThread())
75 | .observeOn(AndroidSchedulers.mainThread())
76 | .subscribe(new Action1>() {
77 | @Override
78 | public void call(List myDummyClasses) {
79 | toastMessage("onNext() List--> " + myDummyClasses.toString());
80 | }
81 | });
82 |
83 | observableSample.doNothing()
84 | .subscribeOn(Schedulers.newThread())
85 | .observeOn(AndroidSchedulers.mainThread())
86 | .subscribe();
87 |
88 | observableSample.doSomething(v)
89 | .subscribeOn(Schedulers.newThread())
90 | .observeOn(AndroidSchedulers.mainThread())
91 | .subscribe();
92 |
93 | observableSample.sendNull()
94 | .subscribeOn(Schedulers.newThread())
95 | .observeOn(AndroidSchedulers.mainThread())
96 | .subscribe();
97 | }
98 | };
99 |
100 | private View.OnClickListener rxLogSubscriberListener = new View.OnClickListener() {
101 | @Override
102 | public void onClick(View v) {
103 | final ObservableSample observableSample = new ObservableSample();
104 | toastMessage("Subscribing to observables...Check logcat output...");
105 |
106 | observableSample.strings()
107 | .subscribeOn(Schedulers.newThread())
108 | .observeOn(AndroidSchedulers.mainThread())
109 | .subscribe(new MySubscriber());
110 |
111 | observableSample.stringsWithError()
112 | .subscribeOn(Schedulers.newThread())
113 | .observeOn(AndroidSchedulers.mainThread())
114 | .subscribe(new MySubscriber());
115 |
116 | observableSample.numbersBackpressure()
117 | .onBackpressureDrop()
118 | .subscribeOn(Schedulers.newThread())
119 | .observeOn(AndroidSchedulers.mainThread())
120 | .subscribe(new MySubscriberBackpressure());
121 |
122 | observableSample.doNothing()
123 | .subscribeOn(Schedulers.newThread())
124 | .observeOn(AndroidSchedulers.mainThread())
125 | .subscribe(new MySubscriberVoid());
126 |
127 | observableSample.doSomething(v)
128 | .subscribeOn(Schedulers.newThread())
129 | .observeOn(AndroidSchedulers.mainThread())
130 | .subscribe(new MySubscriberVoid());
131 |
132 | observableSample.sendNull()
133 | .subscribeOn(Schedulers.newThread())
134 | .observeOn(AndroidSchedulers.mainThread())
135 | .subscribe(new MySubscriber());
136 | }
137 | };
138 |
139 | @Override
140 | protected void onCreate(Bundle savedInstanceState) {
141 | super.onCreate(savedInstanceState);
142 | setContentView(R.layout.activity_samples);
143 | this.mapGUI();
144 | }
145 |
146 | private void mapGUI() {
147 | this.btnRxLogObservable = (Button) findViewById(R.id.btnRxLogObservable);
148 | this.btnRxLogSubscriber = (Button) findViewById(R.id.btnRxLogSubscriber);
149 |
150 | this.btnRxLogObservable.setOnClickListener(rxLogObservableListener);
151 | this.btnRxLogSubscriber.setOnClickListener(rxLogSubscriberListener);
152 | }
153 |
154 | private void toastMessage(String message) {
155 | Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | Frodo
4 | =========================
5 |
6 | [](http://www.apache.org/licenses/LICENSE-2.0) [](http://developer.android.com/index.html)
7 | [ ](https://bintray.com/android10/maven/frodo-plugin/_latestVersion)
8 |
9 | ```Frodo``` is an android library inspired by Jake Wharton's [Hugo](https://github.com/JakeWharton/hugo), mainly used for logging [RxJava](https://github.com/ReactiveX/RxJava) [Observables](http://reactivex.io/documentation/observable.html) and [Subscribers](http://reactivex.io/RxJava/javadoc/rx/Subscriber.html) outputs on the logcat.
10 | It generates and weaves code based on annotations only on ```debug``` versions of the application where the plugin is applied, for instance, it is safe to persist any ```Frodo``` annotation in the codebase.
11 |
12 | 
13 |
14 | Main Features
15 | =========================
16 |
17 | - **@RxLogObservable:** Annotated methods which return ```rx.Observables``` will print the following information when emitting items:
18 |
19 |
20 |
21 | ```java
22 | @RxLogObservable
23 | public Observable> list() {
24 | return Observable.just(buildDummyList());
25 | }
26 | ```
27 |
28 | - **@RxLogObservable.Scope Options:** It is possible to narrow down the debug information shown by adding a debugging scope to @RxLogObservable annotation.
29 |
30 | - **Scope.EVERYTHING:** Logs stream data, schedulers and rx.Observable events. Default.
31 | - **Scope.STREAM:** Logs rx.Observable emitted items plus total execution time.
32 | - **Scope.SCHEDULERS:** Logs schedulers where the annotated rx.Observable operates on.
33 | - **Scope.EVENTS:** Logs rx.Observable events only.
34 | - **Scope.NOTHING:** Turns off logging for the annotated rx.Observable.
35 |
36 | ```java
37 | @RxLogObservable(Scope.STREAM)
38 | public Observable> list() {
39 | return Observable.just(buildDummyList());
40 | }
41 | ```
42 |
43 | - **@RxLogSubscriber:** Annotated classes which are of type ```rx.Subscriber``` will print the following information when receiving items from an ```rx.Observable```:
44 |
45 |
46 |
47 | ```java
48 | @RxLogSubscriber
49 | public class MySubscriberBackpressure extends Subscriber {
50 |
51 | @Override
52 | public void onStart() {
53 | request(40);
54 | }
55 |
56 | @Override
57 | public void onNext(Integer value) {
58 | //empty
59 | }
60 |
61 | @Override
62 | public void onError(Throwable throwable) {
63 | //empty
64 | }
65 |
66 | @Override
67 | public void onCompleted() {
68 | if (!isUnsubscribed()) {
69 | unsubscribe();
70 | }
71 | }
72 | }
73 | ```
74 |
75 | Enabling Frodo
76 | =========================
77 | To enable Frodo, a gradle plugin must be applied in your ```build.gradle```:
78 |
79 | ```java
80 | buildscript {
81 | repositories {
82 | jcenter()
83 | }
84 | dependencies {
85 | classpath "com.fernandocejas.frodo:frodo-plugin:${latest_version}"
86 | }
87 | }
88 |
89 | apply plugin: 'com.android.application'
90 | apply plugin: 'com.fernandocejas.frodo'
91 |
92 | //By default frodo is ON on debug build variants, although
93 | //we can enable-disable it with this configuration.
94 | frodo {
95 | enabled = true
96 | }
97 | ```
98 |
99 | Known issues
100 | =========================
101 |
102 | 1 - Multi module setup (application + android library) will not log annotated methods/classes from Android Library Module but will do it on Android Application Module. The reason behind this, is that the Android Gradle Plugin will build all Android Libraries as release versions, for instance, Frodo is not able to weave any code on the annotated methods/classes (Remember that only weaves in debug versions). There is a workaround for forcing debug versions of your Android Libraries (just be careful in case this is forgotten and you end up shipping a version of your app with RxJava Logging enabled) by adding this line in your ```build.gradle``` file:
103 |
104 | ```java
105 | android {
106 | defaultPublishConfig "debug"
107 | }
108 | ```
109 |
110 | Frodo WIKI
111 | =========================
112 | For complete information, features and usage, refer to the [WIKI](https://github.com/android10/frodo/wiki):
113 | - [@RxLogObservable](https://github.com/android10/frodo/wiki/@RxLogObservable)
114 | - [@RxLogSubscriber](https://github.com/android10/frodo/wiki/@RxLogSubscriber)
115 | - [Release Notes](https://github.com/android10/frodo/wiki/Release-Notes)
116 | - [Development](https://github.com/android10/frodo/wiki/Development)
117 | - [Frodo under the hoods](https://github.com/android10/frodo/wiki/Under-the-hoods)
118 |
119 | License
120 | =========================
121 |
122 | Copyright 2015 Fernando Cejas
123 |
124 | Licensed under the Apache License, Version 2.0 (the "License");
125 | you may not use this file except in compliance with the License.
126 | You may obtain a copy of the License at
127 |
128 | http://www.apache.org/licenses/LICENSE-2.0
129 |
130 | Unless required by applicable law or agreed to in writing, software
131 | distributed under the License is distributed on an "AS IS" BASIS,
132 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
133 | See the License for the specific language governing permissions and
134 | limitations under the License.
135 |
136 |
137 | 
138 |
139 |
140 |
--------------------------------------------------------------------------------
/frodo-runtime/src/main/java/com/fernandocejas/frodo/core/collections/AbstractIterator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2007 The Guava Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.fernandocejas.frodo.core.collections;
18 |
19 | import java.util.NoSuchElementException;
20 |
21 | import static com.fernandocejas.frodo.core.checks.Preconditions.checkState;
22 |
23 | /**
24 | * This class provides a skeletal implementation of the {@code Iterator}
25 | * interface, to make this interface easier to implement for certain types of
26 | * data sources.
27 | *
28 | * {@code Iterator} requires its implementations to support querying the
29 | * end-of-data status without changing the iterator's state, using the {@link
30 | * #hasNext} method. But many data sources, such as {@link
31 | * java.io.Reader#read()}, do not expose this information; the only way to
32 | * discover whether there is any data left is by trying to retrieve it. These
33 | * types of data sources are ordinarily difficult to write iterators for. But
34 | * using this class, one must implement only the {@link #computeNext} method,
35 | * and invoke the {@link #endOfData} method when appropriate.
36 | *
37 | *
Another example is an iterator that skips over null elements in a backing
38 | * iterator. This could be implemented as:
{@code
39 | *
40 | * public static Iterator skipNulls(final Iterator in) {
41 | * return new AbstractIterator() {
42 | * protected String computeNext() {
43 | * while (in.hasNext()) {
44 | * String s = in.next();
45 | * if (s != null) {
46 | * return s;
47 | * }
48 | * }
49 | * return endOfData();
50 | * }
51 | * };
52 | * }}
53 | *
54 | * This class supports iterators that include null elements.
55 | *
56 | * @author Kevin Bourrillion
57 | * @since 2.0 (imported from Google Collections Library)
58 | *
59 | *
This class contains code derived from Google
60 | * Guava
61 | */
62 | public abstract class AbstractIterator extends UnmodifiableIterator {
63 | private State state = State.NOT_READY;
64 | private T next;
65 |
66 | /**
67 | * Constructor for use by subclasses.
68 | */
69 | protected AbstractIterator() {
70 | }
71 |
72 | private enum State {
73 | /**
74 | * We have computed the next element and haven't returned it yet.
75 | */
76 | READY,
77 |
78 | /**
79 | * We haven't yet computed or have already returned the element.
80 | */
81 | NOT_READY,
82 |
83 | /**
84 | * We have reached the end of the data and are finished.
85 | */
86 | DONE,
87 |
88 | /**
89 | * We've suffered an exception and are kaput.
90 | */
91 | FAILED,
92 | }
93 |
94 | /**
95 | * Returns the next element. Note: the implementation must call {@link
96 | * #endOfData()} when there are no elements left in the iteration. Failure to
97 | * do so could result in an infinite loop.
98 | *
99 | * The initial invocation of {@link #hasNext()} or {@link #next()} calls
100 | * this method, as does the first invocation of {@code hasNext} or {@code
101 | * next} following each successful call to {@code next}. Once the
102 | * implementation either invokes {@code endOfData} or throws an exception,
103 | * {@code computeNext} is guaranteed to never be called again.
104 | *
105 | *
If this method throws an exception, it will propagate outward to the
106 | * {@code hasNext} or {@code next} invocation that invoked this method. Any
107 | * further attempts to use the iterator will result in an {@link
108 | * IllegalStateException}.
109 | *
110 | *
The implementation of this method may not invoke the {@code hasNext},
111 | * {@code next}, or {@link #peek()} methods on this instance; if it does, an
112 | * {@code IllegalStateException} will result.
113 | *
114 | * @return the next element if there was one. If {@code endOfData} was called
115 | * during execution, the return value will be ignored.
116 | * @throws RuntimeException if any unrecoverable error happens. This exception
117 | * will propagate outward to the {@code hasNext()}, {@code next()}, or
118 | * {@code peek()} invocation that invoked this method. Any further
119 | * attempts to use the iterator will result in an
120 | * {@link IllegalStateException}.
121 | */
122 | protected abstract T computeNext();
123 |
124 | /**
125 | * Implementations of {@link #computeNext} must invoke this method when
126 | * there are no elements left in the iteration.
127 | *
128 | * @return {@code null}; a convenience so your {@code computeNext}
129 | * implementation can use the simple statement {@code return endOfData();}
130 | */
131 | protected final T endOfData() {
132 | state = State.DONE;
133 | return null;
134 | }
135 |
136 | @Override
137 | public final boolean hasNext() {
138 | checkState(state != State.FAILED);
139 | switch (state) {
140 | case DONE:
141 | return false;
142 | case READY:
143 | return true;
144 | default:
145 | }
146 | return tryToComputeNext();
147 | }
148 |
149 | private boolean tryToComputeNext() {
150 | state = State.FAILED; // temporary pessimism
151 | next = computeNext();
152 | if (state != State.DONE) {
153 | state = State.READY;
154 | return true;
155 | }
156 | return false;
157 | }
158 |
159 | @Override
160 | public final T next() {
161 | if (!hasNext()) {
162 | throw new NoSuchElementException();
163 | }
164 | state = State.NOT_READY;
165 | T result = next;
166 | next = null;
167 | return result;
168 | }
169 |
170 | /**
171 | * Returns the next element in the iteration without advancing the iteration.
172 | *
173 | *
Implementations of {@code AbstractIterator} that wish to expose this
174 | * functionality should implement {@code PeekingIterator}.
175 | */
176 | public final T peek() {
177 | if (!hasNext()) {
178 | throw new NoSuchElementException();
179 | }
180 | return next;
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/frodo-runtime/src/main/java/com/fernandocejas/frodo/joinpoint/FrodoJoinPoint.java:
--------------------------------------------------------------------------------
1 | package com.fernandocejas.frodo.joinpoint;
2 |
3 | import android.os.Looper;
4 | import java.lang.annotation.Annotation;
5 | import java.lang.reflect.Method;
6 | import java.lang.reflect.ParameterizedType;
7 | import java.lang.reflect.Type;
8 | import java.util.ArrayList;
9 | import java.util.Arrays;
10 | import java.util.Collections;
11 | import java.util.List;
12 | import org.aspectj.lang.JoinPoint;
13 | import org.aspectj.lang.Signature;
14 | import org.aspectj.lang.reflect.MethodSignature;
15 |
16 | /**
17 | * Wrapper around {@link org.aspectj.lang.JoinPoint} to make easy retrieve data from a certain
18 | * {@link org.aspectj.lang.JoinPoint} passed as a parameter in the constructor.
19 | */
20 | public class FrodoJoinPoint {
21 |
22 | private final JoinPoint joinPoint;
23 | private final MethodSignature methodSignature;
24 | private final String classCanonicalName;
25 | private final String classSimpleName;
26 | private final String methodName;
27 | private final List methodParamTypesList;
28 | private final List methodParamNamesList;
29 | private final List methodParamValuesList;
30 | private final String executionThreadName;
31 | private final String joinPointUniqueName;
32 |
33 | /**
34 | * Constructor of the class
35 | *
36 | * @param joinPoint object to wrap around.
37 | */
38 | public FrodoJoinPoint(JoinPoint joinPoint) {
39 | if (joinPoint == null) {
40 | throw new IllegalArgumentException("Constructor parameters cannot be null!!!");
41 | }
42 |
43 | this.joinPoint = joinPoint;
44 | this.methodSignature = (MethodSignature) this.joinPoint.getSignature();
45 | this.classCanonicalName = this.methodSignature.getDeclaringType().getCanonicalName();
46 | this.classSimpleName = this.methodSignature.getDeclaringType().getSimpleName();
47 | this.methodName = this.methodSignature.getName();
48 | this.executionThreadName = Thread.currentThread().getName();
49 | final Class[] parameterTypes = this.methodSignature.getParameterTypes();
50 | final String[] parameterNames = this.methodSignature.getParameterNames();
51 | final Object[] args = this.joinPoint.getArgs();
52 | this.methodParamTypesList = parameterTypes != null ?
53 | Arrays.asList(parameterTypes) : Collections.emptyList();
54 | this.methodParamNamesList = parameterNames != null ?
55 | Arrays.asList(parameterNames) : Collections.emptyList();
56 | this.methodParamValuesList = args != null ? Arrays.asList(args) : Collections.emptyList();
57 | this.joinPointUniqueName = this.generateJoinPointUniqueName();
58 | }
59 |
60 | public String getClassSimpleName() {
61 | return classSimpleName;
62 | }
63 |
64 | public String getMethodName() {
65 | return methodName;
66 | }
67 |
68 | public List getMethodParamTypesList() {
69 | return methodParamTypesList;
70 | }
71 |
72 | public List getMethodParamNamesList() {
73 | return methodParamNamesList;
74 | }
75 |
76 | public List getMethodParamValuesList() {
77 | return methodParamValuesList;
78 | }
79 |
80 | public String getExecutionThreadName() {
81 | return executionThreadName;
82 | }
83 |
84 | /**
85 | * Gets an annotation from a method, will return null in case it does not exist.
86 | * It is important to have a retention policy of
87 | * {@link java.lang.annotation.RetentionPolicy#RUNTIME} to avoid null values when reading them.
88 | *
89 | * @param annotation The {@link java.lang.annotation.Annotation} to get.
90 | * @return The annotation if exists, otherwise null.
91 | */
92 | public Annotation getAnnotation(Class extends Annotation> annotation) {
93 | return methodSignature.getMethod().getAnnotation(annotation);
94 | }
95 |
96 | /**
97 | * Check if the {@link JoinPoint} is being executed in the main thread.
98 | *
99 | * @return true: main thread, otherwise false.
100 | */
101 | public boolean isMainThread() {
102 | return (Looper.getMainLooper() == Looper.myLooper());
103 | }
104 |
105 | /**
106 | * Check if the {@link JoinPoint} has a return type.
107 | *
108 | * @param joinPoint the {@link JoinPoint} to check.
109 | * @return true if there is a return type, false if it is void.
110 | */
111 | public boolean hasReturnType(JoinPoint joinPoint) {
112 | Signature signature = joinPoint.getSignature();
113 |
114 | return (signature instanceof MethodSignature
115 | && ((MethodSignature) signature).getReturnType() != void.class);
116 | }
117 |
118 | /**
119 | * {@link MethodSignature#getReturnType()}.
120 | */
121 | public Class getReturnType() {
122 | return methodSignature.getReturnType();
123 | }
124 |
125 | /**
126 | * {@link Method#getGenericReturnType()}.
127 | */
128 | public List getGenericReturnTypes() {
129 | final Type returnType = methodSignature.getMethod().getGenericReturnType();
130 | if (returnType instanceof ParameterizedType) {
131 | final Type[] typeArguments = ((ParameterizedType) returnType).getActualTypeArguments();
132 | final List genericReturnTypes = new ArrayList<>(typeArguments.length);
133 | for (Type typeArgument : typeArguments) {
134 | genericReturnTypes.add(typeArgument.getClass());
135 | }
136 | return genericReturnTypes;
137 | }
138 | final Class clazz = (Class>) returnType;
139 | return Collections.singletonList(clazz);
140 | }
141 |
142 | /**
143 | * {@link JoinPoint#getTarget()}.
144 | */
145 | public Object getTarget() {
146 | return joinPoint.getTarget();
147 | }
148 |
149 | /**
150 | * Get wrapped {@link JoinPoint}.
151 | */
152 | public JoinPoint getJoinPoint() {
153 | return joinPoint;
154 | }
155 |
156 | @Override
157 | public boolean equals(Object object) {
158 | if (object == this) {
159 | return true;
160 | }
161 |
162 | if ((object == null) || (object.getClass() != this.getClass())) {
163 | return false;
164 | }
165 |
166 | FrodoJoinPoint joinPoint = (FrodoJoinPoint) object;
167 | return this.joinPointUniqueName.equals(joinPoint.joinPointUniqueName);
168 | }
169 |
170 | @Override
171 | public int hashCode() {
172 | return this.joinPointUniqueName.hashCode();
173 | }
174 |
175 | private String generateJoinPointUniqueName() {
176 | StringBuilder stringBuilder = new StringBuilder(256);
177 | stringBuilder.append(this.classCanonicalName);
178 | stringBuilder.append(".");
179 | stringBuilder.append(this.methodName);
180 | stringBuilder.append("(");
181 | if (!this.methodParamNamesList.isEmpty()) {
182 | for (int i = 0; i < this.methodParamNamesList.size(); i++) {
183 | stringBuilder.append(this.methodParamTypesList.get(i).getSimpleName());
184 | stringBuilder.append(" ");
185 | stringBuilder.append(this.methodParamNamesList.get(i));
186 | if ((i != this.methodParamNamesList.size() - 1)) {
187 | stringBuilder.append(", ");
188 | }
189 | }
190 | }
191 | stringBuilder.append(")");
192 |
193 | return stringBuilder.toString();
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/frodo-runtime/src/main/java/com/fernandocejas/frodo/core/optional/Optional.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2011 The Guava Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.fernandocejas.frodo.core.optional;
18 |
19 | import com.fernandocejas.frodo.core.collections.AbstractIterator;
20 | import com.fernandocejas.frodo.core.functions.Function;
21 | import java.io.Serializable;
22 | import java.util.Iterator;
23 | import java.util.Set;
24 | import org.jetbrains.annotations.Nullable;
25 |
26 | import static com.fernandocejas.frodo.core.checks.Preconditions.checkNotNull;
27 |
28 | /**
29 | * An immutable object that may contain a non-null reference to another object. Each
30 | * instance of this type either contains a non-null reference, or contains nothing (in
31 | * which case we say that the reference is "absent"); it is never said to "contain {@code
32 | * null}".
33 | *
34 | * A non-null {@code Optional} reference can be used as a replacement for a nullable
35 | * {@code T} reference. It allows you to represent "a {@code T} that must be present" and
36 | * a "a {@code T} that might be absent" as two distinct types in your program, which can
37 | * aid clarity.
38 | *
39 | * Some uses of this class include
40 | *
41 | *
42 | * As a method return type, as an alternative to returning {@code null} to indicate
43 | * that no value was available
44 | * To distinguish between "unknown" (for example, not present in a map) and "known to
45 | * have no value" (present in the map, with value {@code Optional.absent()})
46 | * To wrap nullable references for storage in a collection that does not support
47 | * {@code null} (though there are
48 | *
49 | * several other approaches to this that should be considered first)
50 | *
51 | *
52 | * A common alternative to using this class is to find or create a suitable
53 | * null object for the
54 | * type in question.
55 | *
56 | *
This class is not intended as a direct analogue of any existing "option" or "maybe"
57 | * construct from other programming environments, though it may bear some similarities.
58 | *
59 | *
See the Guava User Guide article on
61 | * using {@code Optional} .
62 | *
63 | * @param the type of instance that can be contained. {@code Optional} is naturally
64 | * covariant on this type, so it is safe to cast an {@code Optional} to {@code
65 | * Optional} for any supertype {@code S} of {@code T}.
66 | * @author Kurt Alfred Kluever
67 | * @author Kevin Bourrillion
68 | * @since 10.0
69 | *
70 | * This class contains code derived from Google
71 | * Guava
72 | */
73 | public abstract class Optional implements Serializable {
74 | private static final long serialVersionUID = 0;
75 |
76 | /**
77 | * Returns an {@code Optional} instance with no contained reference.
78 | */
79 | public static Optional absent() {
80 | return Absent.withType();
81 | }
82 |
83 | /**
84 | * Returns an {@code Optional} instance containing the given non-null reference.
85 | */
86 | public static Optional of(T reference) {
87 | return new Present<>(checkNotNull(reference));
88 | }
89 |
90 | /**
91 | * If {@code nullableReference} is non-null, returns an {@code Optional} instance containing that
92 | * reference; otherwise returns {@link Optional#absent}.
93 | */
94 | public static Optional fromNullable(@Nullable T nullableReference) {
95 | return (nullableReference == null)
96 | ? Optional.absent()
97 | : new Present<>(nullableReference);
98 | }
99 |
100 | Optional() {
101 | }
102 |
103 | /**
104 | * Returns {@code true} if this holder contains a (non-null) instance.
105 | */
106 | public abstract boolean isPresent();
107 |
108 | /**
109 | * Returns the contained instance, which must be present. If the instance might be
110 | * absent, use {@link #or(Object)} or {@link #orNull} instead.
111 | *
112 | * @throws IllegalStateException if the instance is absent ({@link #isPresent} returns
113 | * {@code false})
114 | */
115 | public abstract T get();
116 |
117 | /**
118 | * Returns the contained instance if it is present; {@code defaultValue} otherwise. If
119 | * no default value should be required because the instance is known to be present, use
120 | * {@link #get()} instead. For a default value of {@code null}, use {@link #orNull}.
121 | *
122 | * Note about generics: The signature {@code public T or(T defaultValue)} is overly
123 | * restrictive. However, the ideal signature, {@code public S or(S)}, is not legal
124 | * Java. As a result, some sensible operations involving subtypes are compile errors:
125 | * {@code
126 | *
127 | * Optional optionalInt = getSomeOptionalInt();
128 | * Number value = optionalInt.or(0.5); // error
129 | *
130 | * FluentIterable extends Number> numbers = getSomeNumbers();
131 | * Optional extends Number> first = numbers.first();
132 | * Number value = first.or(0.5); // error}
133 | *
134 | *
As a workaround, it is always safe to cast an {@code Optional extends T>} to {@code
135 | * Optional}. Casting either of the above example {@code Optional} instances to {@code
136 | * Optional} (where {@code Number} is the desired output type) solves the problem:
137 | * {@code
138 | *
139 | * Optional optionalInt = (Optional) getSomeOptionalInt();
140 | * Number value = optionalInt.or(0.5); // fine
141 | *
142 | * FluentIterable extends Number> numbers = getSomeNumbers();
143 | * Optional first = (Optional) numbers.first();
144 | * Number value = first.or(0.5); // fine}
145 | */
146 | public abstract T or(T defaultValue);
147 |
148 | /**
149 | * Returns this {@code Optional} if it has a value present; {@code secondChoice}
150 | * otherwise.
151 | */
152 | public abstract Optional or(Optional extends T> secondChoice);
153 |
154 | /**
155 | * Returns the contained instance if it is present; {@code null} otherwise. If the
156 | * instance is known to be present, use {@link #get()} instead.
157 | */
158 | @Nullable
159 | public abstract T orNull();
160 |
161 | /**
162 | * Returns an immutable singleton {@link Set} whose only element is the contained instance
163 | * if it is present; an empty immutable {@link Set} otherwise.
164 | *
165 | * @since 11.0
166 | */
167 | public abstract Set asSet();
168 |
169 | /**
170 | * If the instance is present, it is transformed with the given {@link Function}; otherwise,
171 | * {@link Optional#absent} is returned. If the function returns {@code null}, a
172 | * {@link NullPointerException} is thrown.
173 | *
174 | * @throws NullPointerException if the function returns {@code null}
175 | * @since 12.0
176 | */
177 | public abstract Optional transform(Function super T, V> function);
178 |
179 | /**
180 | * Returns {@code true} if {@code object} is an {@code Optional} instance, and either
181 | * the contained references are {@linkplain Object#equals equal} to each other or both
182 | * are absent. Note that {@code Optional} instances of differing parameterized types can
183 | * be equal.
184 | */
185 | @Override
186 | public abstract boolean equals(@Nullable Object object);
187 |
188 | /**
189 | * Returns a hash code for this instance.
190 | */
191 | @Override
192 | public abstract int hashCode();
193 |
194 | /**
195 | * Returns a string representation for this instance. The form of this string
196 | * representation is unspecified.
197 | */
198 | @Override
199 | public abstract String toString();
200 |
201 | /**
202 | * Returns the value of each present instance from the supplied {@code optionals}, in order,
203 | * skipping over occurrences of {@link Optional#absent}. Iterators are unmodifiable and are
204 | * evaluated lazily.
205 | *
206 | * @since 11.0 (generics widened in 13.0)
207 | */
208 | public static Iterable presentInstances(
209 | final Iterable extends Optional extends T>> optionals) {
210 | checkNotNull(optionals);
211 | return new Iterable() {
212 | @Override
213 | public Iterator iterator() {
214 | return new AbstractIterator() {
215 | private final Iterator extends Optional extends T>> iterator =
216 | checkNotNull(optionals.iterator());
217 |
218 | @Override
219 | protected T computeNext() {
220 | while (iterator.hasNext()) {
221 | Optional extends T> optional = iterator.next();
222 | if (optional.isPresent()) {
223 | return optional.get();
224 | }
225 | }
226 | return endOfData();
227 | }
228 | };
229 | }
230 | };
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
/frodo-runtime/src/main/java/com/fernandocejas/frodo/core/strings/Joiner.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2008 The Guava Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.fernandocejas.frodo.core.strings;
18 |
19 | import java.io.IOException;
20 | import java.util.AbstractList;
21 | import java.util.Arrays;
22 | import java.util.Iterator;
23 | import java.util.Map;
24 | import org.jetbrains.annotations.Nullable;
25 |
26 | import static com.fernandocejas.frodo.core.checks.Preconditions.checkNotNull;
27 |
28 | /**
29 | * An object which joins pieces of text (specified as an array, {@link Iterable}, varargs or even a
30 | * {@link Map}) with a separator. It either appends the results to an {@link Appendable} or returns
31 | * them as a {@link String}. Example: {@code
32 | *
33 | * Joiner joiner = Joiner.on("; ").skipNulls();
34 | * . . .
35 | * return joiner.join("Harry", null, "Ron", "Hermione");}
36 | *
37 | * This returns the string {@code "Harry; Ron; Hermione"}. Note that all input elements are
38 | * converted to strings using {@link Object#toString()} before being appended.
39 | *
40 | * If neither {@link #skipNulls()} nor {@link #useForNull(String)} is specified, the joining
41 | * methods will throw {@link NullPointerException} if any given element is null.
42 | *
43 | * Warning: joiner instances are always immutable ; a configuration method such as {@code
44 | * useForNull} has no effect on the instance it is invoked on! You must store and use the new
45 | * joiner
46 | * instance returned by the method. This makes joiners thread-safe, and safe to store as {@code
47 | * static final} constants.
{@code
48 | *
49 | * // Bad! Do not do this!
50 | * Joiner joiner = Joiner.on(',');
51 | * joiner.skipNulls(); // does nothing!
52 | * return joiner.join("wrong", null, "wrong");}
53 | *
54 | * See the Guava User Guide article on {@code Joiner} .
56 | *
57 | *
This class contains code derived from Google
58 | * Guava
59 | *
60 | * @author Kevin Bourrillion
61 | */
62 | public class Joiner {
63 |
64 | private final String separator;
65 |
66 | Joiner(String separator) {
67 | this.separator = checkNotNull(separator);
68 | }
69 |
70 | private Joiner(Joiner prototype) {
71 | this.separator = prototype.separator;
72 | }
73 |
74 | /**
75 | * Appends the string representation of each of {@code parts}, using the previously configured
76 | * separator between each, to {@code appendable}.
77 | */
78 | public A appendTo(A appendable, Iterable> parts) throws IOException {
79 | return appendTo(appendable, parts.iterator());
80 | }
81 |
82 | /**
83 | * Appends the string representation of each of {@code parts}, using the previously configured
84 | * separator between each, to {@code appendable}.
85 | *
86 | * @since 11.0
87 | */
88 | public A appendTo(A appendable, Iterator> parts) throws IOException {
89 | checkNotNull(appendable);
90 | if (parts.hasNext()) {
91 | appendable.append(toString(parts.next()));
92 | while (parts.hasNext()) {
93 | appendable.append(separator);
94 | appendable.append(toString(parts.next()));
95 | }
96 | }
97 | return appendable;
98 | }
99 |
100 | /**
101 | * Appends the string representation of each of {@code parts}, using the previously configured
102 | * separator between each, to {@code appendable}.
103 | */
104 | public final A appendTo(A appendable, Object[] parts) throws IOException {
105 | return appendTo(appendable, Arrays.asList(parts));
106 | }
107 |
108 | /**
109 | * Appends to {@code appendable} the string representation of each of the remaining arguments.
110 | */
111 | public final A appendTo(
112 | A appendable, @Nullable Object first, @Nullable Object second, Object... rest)
113 | throws IOException {
114 | return appendTo(appendable, iterable(first, second, rest));
115 | }
116 |
117 | /**
118 | * Appends the string representation of each of {@code parts}, using the previously configured
119 | * separator between each, to {@code builder}. Identical to {@link #appendTo(Appendable,
120 | * Iterable)}, except that it does not throw {@link IOException}.
121 | */
122 | public final StringBuilder appendTo(StringBuilder builder, Iterable> parts) {
123 | return appendTo(builder, parts.iterator());
124 | }
125 |
126 | /**
127 | * Appends the string representation of each of {@code parts}, using the previously configured
128 | * separator between each, to {@code builder}. Identical to {@link #appendTo(Appendable,
129 | * Iterable)}, except that it does not throw {@link IOException}.
130 | *
131 | * @since 11.0
132 | */
133 | public final StringBuilder appendTo(StringBuilder builder, Iterator> parts) {
134 | try {
135 | appendTo((Appendable) builder, parts);
136 | } catch (IOException impossible) {
137 | throw new AssertionError(impossible);
138 | }
139 | return builder;
140 | }
141 |
142 | /**
143 | * Appends the string representation of each of {@code parts}, using the previously configured
144 | * separator between each, to {@code builder}. Identical to {@link #appendTo(Appendable,
145 | * Iterable)}, except that it does not throw {@link IOException}.
146 | */
147 | public final StringBuilder appendTo(StringBuilder builder, Object[] parts) {
148 | return appendTo(builder, Arrays.asList(parts));
149 | }
150 |
151 | /**
152 | * Appends to {@code builder} the string representation of each of the remaining arguments.
153 | * Identical to {@link #appendTo(Appendable, Object, Object, Object...)}, except that it does not
154 | * throw {@link IOException}.
155 | */
156 | public final StringBuilder appendTo(
157 | StringBuilder builder, @Nullable Object first, @Nullable Object second, Object... rest) {
158 | return appendTo(builder, iterable(first, second, rest));
159 | }
160 |
161 | /**
162 | * Returns a string containing the string representation of each of {@code parts}, using the
163 | * previously configured separator between each.
164 | */
165 | public final String join(Iterable> parts) {
166 | return join(parts.iterator());
167 | }
168 |
169 | /**
170 | * Returns a string containing the string representation of each of {@code parts}, using the
171 | * previously configured separator between each.
172 | *
173 | * @since 11.0
174 | */
175 | public final String join(Iterator> parts) {
176 | return appendTo(new StringBuilder(), parts).toString();
177 | }
178 |
179 | /**
180 | * Returns a string containing the string representation of each of {@code parts}, using the
181 | * previously configured separator between each.
182 | */
183 | public final String join(Object[] parts) {
184 | return join(Arrays.asList(parts));
185 | }
186 |
187 | /**
188 | * Returns a string containing the string representation of each argument, using the previously
189 | * configured separator between each.
190 | */
191 | public final String join(@Nullable Object first, @Nullable Object second, Object... rest) {
192 | return join(iterable(first, second, rest));
193 | }
194 |
195 | /**
196 | * Returns a joiner with the same behavior as this one, except automatically substituting {@code
197 | * nullText} for any provided null elements.
198 | */
199 | public Joiner useForNull(final String nullText) {
200 | checkNotNull(nullText);
201 | return new Joiner(this) {
202 | @Override CharSequence toString(@Nullable Object part) {
203 | return (part == null) ? nullText : Joiner.this.toString(part);
204 | }
205 |
206 | @Override
207 | public Joiner useForNull(String nullText) {
208 | throw new UnsupportedOperationException("already specified useForNull");
209 | }
210 |
211 | @Override
212 | public Joiner skipNulls() {
213 | throw new UnsupportedOperationException("already specified useForNull");
214 | }
215 | };
216 | }
217 |
218 | /**
219 | * Returns a joiner with the same behavior as this joiner, except automatically skipping over any
220 | * provided null elements.
221 | */
222 | public Joiner skipNulls() {
223 | return new Joiner(this) {
224 | @Override
225 | public A appendTo(A appendable, Iterator> parts)
226 | throws IOException {
227 | checkNotNull(appendable, "appendable");
228 | checkNotNull(parts, "parts");
229 | while (parts.hasNext()) {
230 | Object part = parts.next();
231 | if (part != null) {
232 | appendable.append(Joiner.this.toString(part));
233 | break;
234 | }
235 | }
236 | while (parts.hasNext()) {
237 | Object part = parts.next();
238 | if (part != null) {
239 | appendable.append(separator);
240 | appendable.append(Joiner.this.toString(part));
241 | }
242 | }
243 | return appendable;
244 | }
245 |
246 | @Override
247 | public Joiner useForNull(String nullText) {
248 | throw new UnsupportedOperationException("already specified skipNulls");
249 | }
250 | };
251 | }
252 |
253 | CharSequence toString(Object part) {
254 | checkNotNull(part); // checkNotNull for GWT (do not optimize).
255 | return part instanceof CharSequence ? (CharSequence) part : part.toString();
256 | }
257 |
258 | private static Iterable iterable(
259 | final Object first, final Object second, final Object[] rest) {
260 | checkNotNull(rest);
261 | return new AbstractList() {
262 | @Override
263 | public int size() {
264 | return rest.length + 2;
265 | }
266 |
267 | @Override
268 | public Object get(int index) {
269 | switch (index) {
270 | case 0:
271 | return first;
272 | case 1:
273 | return second;
274 | default:
275 | return rest[index - 2];
276 | }
277 | }
278 | };
279 | }
280 | }
281 |
--------------------------------------------------------------------------------
/frodo-runtime/src/main/java/com/fernandocejas/frodo/internal/MessageBuilder.java:
--------------------------------------------------------------------------------
1 | package com.fernandocejas.frodo.internal;
2 |
3 | import com.fernandocejas.frodo.core.optional.Optional;
4 | import com.fernandocejas.frodo.internal.observable.ObservableInfo;
5 | import com.fernandocejas.frodo.joinpoint.FrodoJoinPoint;
6 | import java.util.List;
7 |
8 | /**
9 | * Class used to build different messages that will be shown in debug mode
10 | */
11 | class MessageBuilder {
12 |
13 | private static final String LOG_START = "@";
14 | private static final String SEPARATOR = " :: ";
15 | private static final String METHOD_SEPARATOR = "#";
16 | private static final String VALUE_SEPARATOR = " -> ";
17 | private static final String TEXT_ENCLOSING_SYMBOL = "'";
18 | private static final String LOG_ENCLOSING_OPEN = "[";
19 | private static final String LOG_ENCLOSING_CLOSE = "]";
20 | private static final String LIBRARY_LABEL = "Frodo => ";
21 | private static final String CLASS_LABEL = LOG_START + "InClass" + VALUE_SEPARATOR;
22 | private static final String METHOD_LABEL = LOG_START + "Method" + VALUE_SEPARATOR;
23 | private static final String TIME_LABEL = LOG_START + "Time" + VALUE_SEPARATOR;
24 | private static final String TIME_MILLIS = " ms";
25 | private static final String OBSERVABLE_LABEL = LOG_START + "Observable";
26 | private static final String EMITTED_ELEMENTS_LABEL = LOG_START + "Emitted" + VALUE_SEPARATOR;
27 | private static final String LABEL_OBSERVABLE_ON_SUBSCRIBE = "onSubscribe()";
28 | private static final String LABEL_OBSERVABLE_ON_NEXT = "onNext()";
29 | private static final String LABEL_OBSERVABLE_ON_ERROR = "onError()";
30 | private static final String LABEL_OBSERVABLE_ON_COMPLETED = "onCompleted()";
31 | private static final String LABEL_OBSERVABLE_ON_TERMINATE = "onTerminate()";
32 | private static final String LABEL_OBSERVABLE_ON_UNSUBSCRIBE = "onUnsubscribe()";
33 | private static final String LABEL_OBSERVABLE_SUBSCRIBE_ON =
34 | LOG_START + "SubscribeOn" + VALUE_SEPARATOR;
35 | private static final String SUBSCRIBER_LABEL = LOG_START + "Subscriber";
36 | private static final String LABEL_SUBSCRIBER_ON_START = "onStart()";
37 | private static final String LABEL_SUBSCRIBER_ON_NEXT = "onNext()";
38 | private static final String LABEL_SUBSCRIBER_ON_ERROR = "onError()";
39 | private static final String LABEL_SUBSCRIBER_ON_COMPLETED = "onCompleted()";
40 | private static final String LABEL_SUBSCRIBER_UN_SUBSCRIBE = "unSubscribe()";
41 | private static final String LABEL_SUBSCRIBER_OBSERVE_ON =
42 | LOG_START + "ObserveOn" + VALUE_SEPARATOR;
43 | private static final String REQUESTED_ELEMENTS_LABEL = LOG_START + "Requested" + VALUE_SEPARATOR;
44 | private static final String RECEIVED_ELEMENTS_LABEL = LOG_START + "Received" + VALUE_SEPARATOR;
45 | private static final String LABEL_ELEMENT_SINGULAR = " element";
46 | private static final String LABEL_ELEMENT_PLURAL = " elements";
47 | private static final String LABEL_MESSAGE_NULL_OBSERVABLES = "You received a null observable";
48 |
49 | MessageBuilder() {}
50 |
51 | String buildObservableInfoMessage(ObservableInfo observableInfo) {
52 | final FrodoJoinPoint joinPoint = observableInfo.getJoinPoint();
53 | final StringBuilder message = buildObservableSB();
54 | message.append(SEPARATOR);
55 | message.append(CLASS_LABEL);
56 | message.append(observableInfo.getClassSimpleName());
57 | message.append(SEPARATOR);
58 | message.append(METHOD_LABEL);
59 | message.append(observableInfo.getMethodName());
60 | message.append(buildMethodSignatureWithValues(joinPoint));
61 | message.append(LOG_ENCLOSING_CLOSE);
62 |
63 | return message.toString();
64 | }
65 |
66 | String buildObservableOnSubscribeMessage(ObservableInfo observableInfo) {
67 | final StringBuilder message = buildObservableSB();
68 | message.append(METHOD_SEPARATOR);
69 | message.append(observableInfo.getMethodName());
70 | message.append(VALUE_SEPARATOR);
71 | message.append(LABEL_OBSERVABLE_ON_SUBSCRIBE);
72 | message.append(LOG_ENCLOSING_CLOSE);
73 |
74 | return message.toString();
75 | }
76 |
77 | String buildObservableOnNextWithValueMessage(ObservableInfo observableInfo, T value) {
78 | final StringBuilder message = buildObservableSB();
79 | message.append(METHOD_SEPARATOR);
80 | message.append(observableInfo.getMethodName());
81 | message.append(VALUE_SEPARATOR);
82 | message.append(LABEL_OBSERVABLE_ON_NEXT);
83 | message.append(VALUE_SEPARATOR);
84 | message.append(String.valueOf(value));
85 | message.append(LOG_ENCLOSING_CLOSE);
86 |
87 | return message.toString();
88 | }
89 |
90 | String buildObservableOnNextMessage(ObservableInfo observableInfo) {
91 | final StringBuilder message = buildObservableSB();
92 | message.append(METHOD_SEPARATOR);
93 | message.append(observableInfo.getMethodName());
94 | message.append(VALUE_SEPARATOR);
95 | message.append(LABEL_OBSERVABLE_ON_NEXT);
96 | message.append(LOG_ENCLOSING_CLOSE);
97 |
98 | return message.toString();
99 | }
100 |
101 | String buildObservableOnErrorMessage(ObservableInfo observableInfo, String errorMessage) {
102 | final StringBuilder message = buildObservableSB();
103 | message.append(METHOD_SEPARATOR);
104 | message.append(observableInfo.getMethodName());
105 | message.append(VALUE_SEPARATOR);
106 | message.append(LABEL_OBSERVABLE_ON_ERROR);
107 | message.append(VALUE_SEPARATOR);
108 | message.append(TEXT_ENCLOSING_SYMBOL);
109 | message.append(errorMessage);
110 | message.append(TEXT_ENCLOSING_SYMBOL);
111 | message.append(LOG_ENCLOSING_CLOSE);
112 |
113 | return message.toString();
114 | }
115 |
116 | String buildObservableOnCompletedMessage(ObservableInfo observableInfo) {
117 | final StringBuilder message = buildObservableSB();
118 | message.append(METHOD_SEPARATOR);
119 | message.append(observableInfo.getMethodName());
120 | message.append(VALUE_SEPARATOR);
121 | message.append(LABEL_OBSERVABLE_ON_COMPLETED);
122 | message.append(LOG_ENCLOSING_CLOSE);
123 |
124 | return message.toString();
125 | }
126 |
127 | String buildObservableOnTerminateMessage(ObservableInfo observableInfo) {
128 | final StringBuilder message = buildObservableSB();
129 | message.append(METHOD_SEPARATOR);
130 | message.append(observableInfo.getMethodName());
131 | message.append(VALUE_SEPARATOR);
132 | message.append(LABEL_OBSERVABLE_ON_TERMINATE);
133 | message.append(LOG_ENCLOSING_CLOSE);
134 |
135 | return message.toString();
136 | }
137 |
138 | String buildObservableOnUnsubscribeMessage(ObservableInfo observableInfo) {
139 | final StringBuilder message = buildObservableSB();
140 | message.append(METHOD_SEPARATOR);
141 | message.append(observableInfo.getMethodName());
142 | message.append(VALUE_SEPARATOR);
143 | message.append(LABEL_OBSERVABLE_ON_UNSUBSCRIBE);
144 | message.append(LOG_ENCLOSING_CLOSE);
145 |
146 | return message.toString();
147 | }
148 |
149 | String buildSubscriberOnStartMessage(String subscriberName) {
150 | final StringBuilder message = buildSubscriberSB();
151 | message.append(SEPARATOR);
152 | message.append(subscriberName);
153 | message.append(VALUE_SEPARATOR);
154 | message.append(LABEL_SUBSCRIBER_ON_START);
155 | message.append(LOG_ENCLOSING_CLOSE);
156 |
157 | return message.toString();
158 | }
159 |
160 | String buildSubscriberOnNextMessage(String subscriberName, Object value, String threadName) {
161 | final StringBuilder message = buildSubscriberSB();
162 | message.append(SEPARATOR);
163 | message.append(subscriberName);
164 | message.append(VALUE_SEPARATOR);
165 | message.append(LABEL_SUBSCRIBER_ON_NEXT);
166 | message.append(VALUE_SEPARATOR);
167 | message.append(value != null ? value.toString() : LABEL_MESSAGE_NULL_OBSERVABLES);
168 | message.append(SEPARATOR);
169 | message.append(LABEL_SUBSCRIBER_OBSERVE_ON);
170 | message.append(threadName);
171 | message.append(LOG_ENCLOSING_CLOSE);
172 |
173 | return message.toString();
174 | }
175 |
176 | String buildSubscriberOnErrorMessage(String subscriberName, String error) {
177 | final StringBuilder message = buildSubscriberSB();
178 | message.append(SEPARATOR);
179 | message.append(subscriberName);
180 | message.append(VALUE_SEPARATOR);
181 | message.append(LABEL_SUBSCRIBER_ON_ERROR);
182 | message.append(VALUE_SEPARATOR);
183 | message.append(error);
184 | message.append(LOG_ENCLOSING_CLOSE);
185 |
186 | return message.toString();
187 | }
188 |
189 | String buildSubscriberOnCompletedMessage(String subscriberName) {
190 | final StringBuilder message = buildSubscriberSB();
191 | message.append(SEPARATOR);
192 | message.append(subscriberName);
193 | message.append(VALUE_SEPARATOR);
194 | message.append(LABEL_SUBSCRIBER_ON_COMPLETED);
195 | message.append(LOG_ENCLOSING_CLOSE);
196 |
197 | return message.toString();
198 | }
199 |
200 | String buildSubscriberItemTimeMessage(String subscriberName, long executionTimeMillis,
201 | int receivedItems) {
202 | final StringBuilder message = buildSubscriberSB();
203 | message.append(SEPARATOR);
204 | message.append(subscriberName);
205 | message.append(VALUE_SEPARATOR);
206 | message.append(RECEIVED_ELEMENTS_LABEL);
207 | message.append(receivedItems);
208 | message.append(receivedItems == 1 ? LABEL_ELEMENT_SINGULAR : LABEL_ELEMENT_PLURAL);
209 | message.append(SEPARATOR);
210 | message.append(TIME_LABEL);
211 | message.append(executionTimeMillis);
212 | message.append(TIME_MILLIS);
213 | message.append(LOG_ENCLOSING_CLOSE);
214 |
215 | return message.toString();
216 | }
217 |
218 | String buildSubscriberRequestedItemsMessage(String subscriberName, long requestedItems) {
219 | final StringBuilder message = buildSubscriberSB();
220 | message.append(SEPARATOR);
221 | message.append(subscriberName);
222 | message.append(VALUE_SEPARATOR);
223 | message.append(REQUESTED_ELEMENTS_LABEL);
224 | message.append(requestedItems);
225 | message.append(requestedItems == 1 ? LABEL_ELEMENT_SINGULAR : LABEL_ELEMENT_PLURAL);
226 | message.append(LOG_ENCLOSING_CLOSE);
227 |
228 | return message.toString();
229 | }
230 |
231 | String buildSubscriberUnsubscribeMessage(String subscriberName) {
232 | final StringBuilder message = buildSubscriberSB();
233 | message.append(SEPARATOR);
234 | message.append(subscriberName);
235 | message.append(VALUE_SEPARATOR);
236 | message.append(LABEL_SUBSCRIBER_UN_SUBSCRIBE);
237 | message.append(LOG_ENCLOSING_CLOSE);
238 |
239 | return message.toString();
240 | }
241 |
242 | String buildObservableItemTimeInfoMessage(ObservableInfo observableInfo) {
243 | final int totalEmittedItems = observableInfo.getTotalEmittedItems().or(0);
244 | final long totalExecutionTime = observableInfo.getTotalExecutionTime().or(0L);
245 | final StringBuilder message = buildObservableSB();
246 | message.append(METHOD_SEPARATOR);
247 | message.append(observableInfo.getMethodName());
248 | message.append(VALUE_SEPARATOR);
249 | message.append(EMITTED_ELEMENTS_LABEL);
250 | message.append(totalEmittedItems);
251 | message.append(totalEmittedItems == 1 ? LABEL_ELEMENT_SINGULAR : LABEL_ELEMENT_PLURAL);
252 | message.append(SEPARATOR);
253 | message.append(TIME_LABEL);
254 | message.append(totalExecutionTime);
255 | message.append(TIME_MILLIS);
256 | message.append(LOG_ENCLOSING_CLOSE);
257 |
258 | return message.toString();
259 | }
260 |
261 | String buildObservableThreadInfoMessage(ObservableInfo observableInfo) {
262 | final Optional subscribeOnThread = observableInfo.getSubscribeOnThread();
263 | final Optional observeOnThread = observableInfo.getObserveOnThread();
264 | final StringBuilder message = buildObservableSB();
265 | message.append(METHOD_SEPARATOR);
266 | message.append(observableInfo.getMethodName());
267 | message.append(VALUE_SEPARATOR);
268 | if (subscribeOnThread.isPresent()) {
269 | message.append(LABEL_OBSERVABLE_SUBSCRIBE_ON);
270 | message.append(subscribeOnThread.get());
271 | }
272 | if (observeOnThread.isPresent()) {
273 | message.append(SEPARATOR);
274 | message.append(LABEL_SUBSCRIBER_OBSERVE_ON);
275 | message.append(observeOnThread.get());
276 | }
277 | message.append(LOG_ENCLOSING_CLOSE);
278 |
279 | return message.toString();
280 | }
281 |
282 | private StringBuilder buildSubscriberSB() {
283 | final int avgStringSize = 75;
284 | final StringBuilder message = new StringBuilder(avgStringSize + LIBRARY_LABEL.length());
285 | message.append(LIBRARY_LABEL);
286 | message.append(LOG_ENCLOSING_OPEN);
287 | message.append(SUBSCRIBER_LABEL);
288 | return message;
289 | }
290 |
291 | private StringBuilder buildObservableSB() {
292 | final int avgStringSize = 75;
293 | final StringBuilder message = new StringBuilder(avgStringSize + LIBRARY_LABEL.length());
294 | message.append(LIBRARY_LABEL);
295 | message.append(LOG_ENCLOSING_OPEN);
296 | message.append(OBSERVABLE_LABEL);
297 | return message;
298 | }
299 |
300 | private String buildMethodSignatureWithValues(FrodoJoinPoint joinPoint) {
301 | final int avg = 30;
302 | final StringBuilder stringBuilder = new StringBuilder(avg + joinPoint.getMethodName().length());
303 | stringBuilder.append("(");
304 | List methodParamNames = joinPoint.getMethodParamNamesList();
305 | if (methodParamNames != null && !methodParamNames.isEmpty()) {
306 | for (int i = 0; i < joinPoint.getMethodParamNamesList().size(); i++) {
307 | stringBuilder.append(methodParamNames.get(i));
308 | stringBuilder.append("=");
309 | stringBuilder.append("'");
310 | stringBuilder.append(String.valueOf(joinPoint.getMethodParamValuesList().get(i)));
311 | stringBuilder.append("'");
312 | if ((i != methodParamNames.size() - 1)) {
313 | stringBuilder.append(", ");
314 | }
315 | }
316 | }
317 | stringBuilder.append(")");
318 |
319 | return stringBuilder.toString();
320 | }
321 | }
322 |
--------------------------------------------------------------------------------