From d61584e0e8227ae77bc9778d0c73dfdd0b5f7030 Mon Sep 17 00:00:00 2001 From: Matt Jacobs Date: Tue, 2 Jul 2013 22:04:09 -0700 Subject: [PATCH 1/8] Make rxjava-core typesafe Conflicts: rxjava-core/src/main/java/rx/Observable.java rxjava-core/src/main/java/rx/observables/BlockingObservable.java rxjava-core/src/main/java/rx/subjects/PublishSubject.java --- .../java/rx/observables/SwingObservable.java | 2 - .../swing/sources/AbstractButtonSource.java | 6 +- .../java/rx/swing/sources/KeyEventSource.java | 14 +- rxjava-core/src/main/java/rx/Observable.java | 1247 +---------------- .../rx/observables/BlockingObservable.java | 207 --- .../main/java/rx/subjects/AsyncSubject.java | 11 +- .../main/java/rx/subjects/PublishSubject.java | 27 +- .../main/java/rx/subjects/ReplaySubject.java | 8 +- 8 files changed, 46 insertions(+), 1476 deletions(-) diff --git a/rxjava-contrib/rxjava-swing/src/main/java/rx/observables/SwingObservable.java b/rxjava-contrib/rxjava-swing/src/main/java/rx/observables/SwingObservable.java index fa9ecd3095..a2facde800 100644 --- a/rxjava-contrib/rxjava-swing/src/main/java/rx/observables/SwingObservable.java +++ b/rxjava-contrib/rxjava-swing/src/main/java/rx/observables/SwingObservable.java @@ -15,8 +15,6 @@ */ package rx.observables; -import static rx.Observable.filter; - import java.awt.Component; import java.awt.Dimension; import java.awt.event.ActionEvent; diff --git a/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/AbstractButtonSource.java b/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/AbstractButtonSource.java index 22a2ce78fc..ef3612b080 100644 --- a/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/AbstractButtonSource.java +++ b/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/AbstractButtonSource.java @@ -69,7 +69,7 @@ public void testObservingActionEvents() { @SuppressWarnings("unchecked") Action1 action = mock(Action1.class); @SuppressWarnings("unchecked") - Action1 error = mock(Action1.class); + Action1 error = mock(Action1.class); Action0 complete = mock(Action0.class); final ActionEvent event = new ActionEvent(this, 1, "command"); @@ -85,7 +85,7 @@ void testAction() { Subscription sub = fromActionOf(button).subscribe(action, error, complete); verify(action, never()).call(Matchers.any()); - verify(error, never()).call(Matchers.any()); + verify(error, never()).call(Matchers.any()); verify(complete, never()).call(); button.testAction(); @@ -97,7 +97,7 @@ void testAction() { sub.unsubscribe(); button.testAction(); verify(action, times(2)).call(Matchers.any()); - verify(error, never()).call(Matchers.any()); + verify(error, never()).call(Matchers.any()); verify(complete, never()).call(); } } diff --git a/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/KeyEventSource.java b/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/KeyEventSource.java index d6f294cebe..3716b599f9 100644 --- a/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/KeyEventSource.java +++ b/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/KeyEventSource.java @@ -113,7 +113,7 @@ public void testObservingKeyEvents() { @SuppressWarnings("unchecked") Action1 action = mock(Action1.class); @SuppressWarnings("unchecked") - Action1 error = mock(Action1.class); + Action1 error = mock(Action1.class); Action0 complete = mock(Action0.class); final KeyEvent event = mock(KeyEvent.class); @@ -121,7 +121,7 @@ public void testObservingKeyEvents() { Subscription sub = fromKeyEventsOf(comp).subscribe(action, error, complete); verify(action, never()).call(Matchers.any()); - verify(error, never()).call(Matchers.any()); + verify(error, never()).call(Matchers.any()); verify(complete, never()).call(); fireKeyEvent(event); @@ -133,7 +133,7 @@ public void testObservingKeyEvents() { sub.unsubscribe(); fireKeyEvent(event); verify(action, times(2)).call(Matchers.any()); - verify(error, never()).call(Matchers.any()); + verify(error, never()).call(Matchers.any()); verify(complete, never()).call(); } @@ -142,19 +142,19 @@ public void testObservingPressedKeys() { @SuppressWarnings("unchecked") Action1> action = mock(Action1.class); @SuppressWarnings("unchecked") - Action1 error = mock(Action1.class); + Action1 error = mock(Action1.class); Action0 complete = mock(Action0.class); Subscription sub = currentlyPressedKeysOf(comp).subscribe(action, error, complete); InOrder inOrder = inOrder(action); inOrder.verify(action, times(1)).call(Collections.emptySet()); - verify(error, never()).call(Matchers.any()); + verify(error, never()).call(Matchers.any()); verify(complete, never()).call(); fireKeyEvent(keyEvent(1, KeyEvent.KEY_PRESSED)); inOrder.verify(action, times(1)).call(new HashSet(asList(1))); - verify(error, never()).call(Matchers.any()); + verify(error, never()).call(Matchers.any()); verify(complete, never()).call(); fireKeyEvent(keyEvent(2, KeyEvent.KEY_PRESSED)); @@ -173,7 +173,7 @@ public void testObservingPressedKeys() { fireKeyEvent(keyEvent(1, KeyEvent.KEY_PRESSED)); inOrder.verify(action, never()).call(Matchers.>any()); - verify(error, never()).call(Matchers.any()); + verify(error, never()).call(Matchers.any()); verify(complete, never()).call(); } diff --git a/rxjava-core/src/main/java/rx/Observable.java b/rxjava-core/src/main/java/rx/Observable.java index f6431926e9..16d9bd4c53 100644 --- a/rxjava-core/src/main/java/rx/Observable.java +++ b/rxjava-core/src/main/java/rx/Observable.java @@ -288,7 +288,7 @@ public Subscription subscribe(final Map callbacks) { return protectivelyWrapAndSubscribe(new Observer() { @Override - public void onCompleted() { + public void onCompleted() { Object onComplete = callbacks.get("onCompleted"); if (onComplete != null) { Functions.from(onComplete).call(); @@ -307,61 +307,17 @@ public void onError(Throwable e) { } @Override - public void onNext(Object args) { + public void onNext(Object args) { onNext.call(args); } - }); + }); } public Subscription subscribe(final Map callbacks, Scheduler scheduler) { return subscribeOn(scheduler).subscribe(callbacks); } - @SuppressWarnings({ "rawtypes", "unchecked" }) - public Subscription subscribe(final Object o) { - if (o instanceof Observer) { - // in case a dynamic language is not correctly handling the overloaded methods and we receive an Observer just forward to the correct method. - return subscribe((Observer) o); - } - - if (o == null) { - throw new IllegalArgumentException("onNext can not be null"); - } - - // lookup and memoize onNext - final FuncN onNext = Functions.from(o); - - /** - * Wrapping since raw functions provided by the user are being invoked. - * - * See https://github.com/Netflix/RxJava/issues/216 for discussion on "Guideline 6.4: Protect calls to user code from within an operator" - */ - return protectivelyWrapAndSubscribe(new Observer() { - - @Override - public void onCompleted() { - // do nothing - } - - @Override - public void onError(Throwable e) { - handleError(e); - throw new OnErrorNotImplementedException(e); - } - - @Override - public void onNext(Object args) { - onNext.call(args); - } - - }); - } - - public Subscription subscribe(final Object o, Scheduler scheduler) { - return subscribeOn(scheduler).subscribe(o); - } - public Subscription subscribe(final Action1 onNext) { if (onNext == null) { throw new IllegalArgumentException("onNext can not be null"); @@ -397,48 +353,6 @@ public Subscription subscribe(final Action1 onNext, Scheduler scheduler) { return subscribeOn(scheduler).subscribe(onNext); } - @SuppressWarnings({ "rawtypes", "unchecked" }) - public Subscription subscribe(final Object onNext, final Object onError) { - if (onNext == null) { - throw new IllegalArgumentException("onNext can not be null"); - } - if (onError == null) { - throw new IllegalArgumentException("onError can not be null"); - } - - // lookup and memoize onNext - final FuncN onNextFunction = Functions.from(onNext); - - /** - * Wrapping since raw functions provided by the user are being invoked. - * - * See https://github.com/Netflix/RxJava/issues/216 for discussion on "Guideline 6.4: Protect calls to user code from within an operator" - */ - return protectivelyWrapAndSubscribe(new Observer() { - - @Override - public void onCompleted() { - // do nothing - } - - @Override - public void onError(Throwable e) { - handleError(e); - Functions.from(onError).call(e); - } - - @Override - public void onNext(Object args) { - onNextFunction.call(args); - } - - }); - } - - public Subscription subscribe(final Object onNext, final Object onError, Scheduler scheduler) { - return subscribeOn(scheduler).subscribe(onNext, onError); - } - public Subscription subscribe(final Action1 onNext, final Action1 onError) { if (onNext == null) { throw new IllegalArgumentException("onNext can not be null"); @@ -477,51 +391,6 @@ public Subscription subscribe(final Action1 onNext, final Action1 return subscribeOn(scheduler).subscribe(onNext, onError); } - @SuppressWarnings({ "rawtypes", "unchecked" }) - public Subscription subscribe(final Object onNext, final Object onError, final Object onComplete) { - if (onNext == null) { - throw new IllegalArgumentException("onNext can not be null"); - } - if (onError == null) { - throw new IllegalArgumentException("onError can not be null"); - } - if (onComplete == null) { - throw new IllegalArgumentException("onComplete can not be null"); - } - - // lookup and memoize onNext - final FuncN onNextFunction = Functions.from(onNext); - - /** - * Wrapping since raw functions provided by the user are being invoked. - * - * See https://github.com/Netflix/RxJava/issues/216 for discussion on "Guideline 6.4: Protect calls to user code from within an operator" - */ - return protectivelyWrapAndSubscribe(new Observer() { - - @Override - public void onCompleted() { - Functions.from(onComplete).call(); - } - - @Override - public void onError(Throwable e) { - handleError(e); - Functions.from(onError).call(e); - } - - @Override - public void onNext(Object args) { - onNextFunction.call(args); - } - - }); - } - - public Subscription subscribe(final Object onNext, final Object onError, final Object onComplete, Scheduler scheduler) { - return subscribeOn(scheduler).subscribe(onNext, onError, onComplete); - } - public Subscription subscribe(final Action1 onNext, final Action1 onError, final Action0 onComplete) { if (onNext == null) { throw new IllegalArgumentException("onNext can not be null"); @@ -886,49 +755,6 @@ public static Observable create(Func1, Subscription> func) { return new Observable(func); } - /** - * Creates an Observable that will execute the given function when an {@link Observer} - * subscribes to it. - *

- * - *

- * This method accepts {@link Object} to allow different languages to pass in methods using - * {@link FunctionLanguageAdaptor}. - *

- * Write the function you pass to create so that it behaves as an Observable: It - * should invoke the Observer's {@link Observer#onNext onNext}, - * {@link Observer#onError onError}, and {@link Observer#onCompleted onCompleted} methods - * appropriately. - *

- * A well-formed Observable must invoke either the Observer's onCompleted method - * exactly once or its onError method exactly once. - *

- * See Rx Design Guidelines (PDF) - * for detailed information. - * - * @param - * the type of the items that this Observable emits - * @param func - * a function that accepts an {@code Observer}, invokes its - * {@code onNext}, {@code onError}, and {@code onCompleted} methods - * as appropriate, and returns a {@link Subscription} that allows the Observer to - * cancel the subscription - * @return an Observable that, when an {@link Observer} subscribes to it, will execute the given - * function - */ - public static Observable create(final Object func) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(func); - return create(new Func1, Subscription>() { - - @Override - public Subscription call(Observer t1) { - return (Subscription) _f.call(t1); - } - - }); - } - /** * Returns an Observable that emits no data to the {@link Observer} and immediately invokes * its {@link Observer#onCompleted onCompleted} method. @@ -981,33 +807,7 @@ public static Observable filter(Observable that, Func1 pre /** * Filters an Observable by discarding any items it emits that do not satisfy some predicate *

- * - * - * @param that - * the Observable to filter - * @param function - * a function that evaluates an item emitted by the source Observable, and - * returns {@code true} if it passes the filter - * @return an Observable that emits only those items emitted by the source Observable for which the - * predicate function evaluates to {@code true} - */ - public static Observable filter(Observable that, final Object function) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(function); - return filter(that, new Func1() { - - @Override - public Boolean call(T t1) { - return (Boolean) _f.call(t1); - - } - - }); - } - - /** - * Filters an Observable by discarding any items it emits that do not satisfy some predicate - *

+ * * * * @param that @@ -1146,39 +946,6 @@ public static Observable defer(Func0> observableFactory) { return create(OperationDefer.defer(observableFactory)); } - /** - * Returns an Observable that calls an Observable factory to create its Observable for each - * new Observer that subscribes. - *

- * - *

- * The defer operator allows you to defer or delay emitting items from an Observable - * until such time as an {@link Observer} subscribes to the Observable. This allows an Observer - * to easily obtain an updates or refreshed version of the sequence. - * - * @param observableFactory - * the Observable factory function to invoke for each {@link Observer} that - * subscribes to the resulting Observable - * @param - * the type of the items emitted by the Observable - * @return an Observable whose {@link Observer}s trigger an invocation of the given Observable - * factory function - */ - public static Observable defer(Object observableFactory) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(observableFactory); - - return create(OperationDefer.defer(new Func0>() { - - @Override - @SuppressWarnings("unchecked") - public Observable call() { - return (Observable) _f.call(); - } - - })); - } - /** * Returns an Observable that emits a single item and then completes. *

@@ -1226,37 +993,6 @@ public static Observable map(Observable sequence, Func1 func) return create(OperationMap.map(sequence, func)); } - /** - * Returns an Observable that applies the given function to each item emitted by an - * Observable and emits the result. - *

- * - * - * @param sequence - * the source Observable - * @param func - * a function to apply to each item emitted by the source Observable - * @param - * the type of items emitted by the the source Observable - * @param - * the type of items to be emitted by the resulting Observable - * @return an Observable that emits the items from the source Observable as transformed by the - * given function - */ - public static Observable map(Observable sequence, final Object func) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(func); - return map(sequence, new Func1() { - - @SuppressWarnings("unchecked") - @Override - public R call(T t1) { - return (R) _f.call(t1); - } - - }); - } - /** * Creates a new Observable by applying a function that you supply to each item emitted by * the source Observable, where that function returns an Observable, and then merging those @@ -1285,43 +1021,6 @@ public static Observable mapMany(Observable sequence, Func1 - * - *

- * Note: {@code mapMany} and {@code flatMap} are equivalent. - * - * @param sequence - * the source Observable - * @param func - * a function that, when applied to each item emitted by the source Observable, - * generates an Observable - * @param - * the type of items emitted by the source Observable - * @param - * the type of items emitted by the Observables that are returned from - * {@code func} - * @return an Observable that emits the result of applying the transformation function to each - * item emitted by the source Observable and merging the results of the Observables - * obtained from this transformation - */ - public static Observable mapMany(Observable sequence, final Object func) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(func); - return mapMany(sequence, new Func1() { - - @SuppressWarnings("unchecked") - @Override - public R call(T t1) { - return (R) _f.call(t1); - } - - }); - } - /** * Turns all of the notifications from a source Observable into {@link Observer#onNext onNext} * emissions, and marks them with their original notification types within {@link Notification} @@ -1498,38 +1197,8 @@ public static Observable flatMap(Observable sequence, Func1 - * *

- * Note: {@code mapMany} and {@code flatMap} are equivalent. - * - * @param sequence - * the source Observable - * @param func - * a function that, when applied to each item emitted by the source Observable, - * generates an Observable - * @param - * the type of items emitted by the source Observable - * @param - * the type of items emitted by the Observables that are returned from - * {@code func} - * @return an Observable that emits the result of applying the transformation function to each - * item emitted by the source Observable and merging the results of the Observables - * obtained from this transformation - * @see #mapMany(Observable, Func1) - */ - public static Observable flatMap(Observable sequence, final Object func) { - return mapMany(sequence, func); - } - - /** - * Groups the items emitted by an Observable according to a specified criterion, and emits these - * grouped items as {@link GroupedObservable}s, one GroupedObservable per group. - *

- * + * * * @param source * an Observable whose items you want to group @@ -1552,51 +1221,6 @@ public static Observable> groupBy(Observable - * - * - * @param source - * an Observable whose items you want to group - * @param keySelector - * a function that extracts the key for each item omitted by the source Observable - * @param elementSelector - * a function to map each item emitted by the source Observable to an item emitted - * by a {@link GroupedObservable} - * @param - * the key type - * @param - * the type of items emitted by the source Observable - * @param - * the type of items to be emitted by the resulting {@link GroupedObservable}s - * @return an Observable that emits {@link GroupedObservable}s, each of which corresponds to a - * unique key value and emits items representing items from the source Observable that - * share that key value - */ - @SuppressWarnings("rawtypes") - public static Observable> groupBy(Observable source, final Object keySelector, final Object elementSelector) { - final FuncN _k = Functions.from(keySelector); - final FuncN _e = Functions.from(elementSelector); - - return groupBy(source, new Func1() { - - @SuppressWarnings("unchecked") - @Override - public K call(T t1) { - return (K) _k.call(t1); - } - }, new Func1() { - - @SuppressWarnings("unchecked") - @Override - public R call(T t1) { - return (R) _e.call(t1); - } - }); - } - /** * Groups the items emitted by an Observable according to a specified criterion, and emits these * grouped items as {@link GroupedObservable}s, one GroupedObservable per group. @@ -1619,38 +1243,6 @@ public static Observable> groupBy(Observable s return create(OperationGroupBy.groupBy(source, keySelector)); } - /** - * Groups the items emitted by an Observable according to a specified criterion, and emits these - * grouped items as {@link GroupedObservable}s, one GroupedObservable per group. - *

- * - * - * @param source - * an Observable whose items you want to group - * @param keySelector - * a function that extracts the key for each item emitted by the source Observable - * @param - * the key type - * @param - * the type of items to be emitted by the resulting {@link GroupedObservable}s - * @return an Observable that emits {@link GroupedObservable}s, each of which corresponds to a - * unique key value and emits items representing items from the source Observable that - * share that key value - */ - @SuppressWarnings("rawtypes") - public static Observable> groupBy(Observable source, final Object keySelector) { - final FuncN _k = Functions.from(keySelector); - - return groupBy(source, new Func1() { - - @SuppressWarnings("unchecked") - @Override - public K call(T t1) { - return (K) _k.call(t1); - } - }); - } - /** * This behaves like {@link #merge(java.util.List)} except that if any of the merged Observables * notify of an error via {@link Observer#onError onError}, {@code mergeDelayError} will @@ -1772,45 +1364,6 @@ public static Observable onErrorResumeNext(final Observable that, fina return create(OperationOnErrorResumeNextViaFunction.onErrorResumeNextViaFunction(that, resumeFunction)); } - /** - * Instruct an Observable to pass control to another Observable (the return value of a function) - * rather than invoking {@link Observer#onError onError} if it encounters an error. - *

- * - *

- * By default, when an Observable encounters an error that prevents it from emitting the - * expected item to its Observer, the Observable invokes its {@link Observer}'s - * methods. The {@code onErrorResumeNext} method changes this behavior. If you pass a - * function that returns an Observable ({@code resumeFunction}) to - * {@code onErrorResumeNext}, if the source Observable encounters an error, instead of - * invoking its Observer's {@code onError} function, it will instead relinquish control to - * this new Observable, which will invoke the Observer's {@link Observer#onNext onNext} method - * if it is able to do so. In such a case, because no Observable necessarily invokes - * {@code onError}, the Observer may never know that an error happened. - *

- * You can use this to prevent errors from propagating or to supply fallback data should errors - * be encountered. - * - * @param that - * the source Observable - * @param resumeFunction - * a function that returns an Observable that will take over if the source Observable - * encounters an error - * @return an Observable, identical to the source Observable with its behavior modified as described - */ - public static Observable onErrorResumeNext(final Observable that, final Object resumeFunction) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(resumeFunction); - return onErrorResumeNext(that, new Func1>() { - - @SuppressWarnings("unchecked") - @Override - public Observable call(Throwable e) { - return (Observable) _f.call(e); - } - }); - } - /** * Instruct an Observable to pass control to another Observable rather than invoking * {@link Observer#onError onError} if it encounters an error. @@ -1982,28 +1535,6 @@ public static Observable reduce(Observable sequence, Func2 ac } /** - * A version of {@code reduce()} for use by dynamic languages. - *

- * - * - * @see #reduce(Observable, Func2) - */ - public static Observable reduce(final Observable sequence, final Object accumulator) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(accumulator); - return reduce(sequence, new Func2() { - - @SuppressWarnings("unchecked") - @Override - public T call(T t1, T t2) { - return (T) _f.call(t1, t2); - } - - }); - } - - /** - * Synonymous with {@code reduce()} *

* * @@ -2013,17 +1544,6 @@ public static Observable aggregate(Observable sequence, Func2 return reduce(sequence, accumulator); } - /** - * A version of {@code aggregate()} for use by dynamic languages. - *

- * - * - * @see #reduce(Observable, Func2) - */ - public static Observable aggregate(Observable sequence, Object accumulator) { - return reduce(sequence, accumulator); - } - /** * Returns an Observable that applies a function of your choosing to the first item emitted by a * source Observable, then feeds the result of that function along with the second item emitted @@ -2058,26 +1578,6 @@ public static Observable reduce(Observable sequence, R initialValue } /** - * A version of {@code reduce()} for use by dynamic languages. - *

- * - * - * @see #reduce(Observable, Object, Func2) - */ - public static Observable reduce(final Observable sequence, final R initialValue, final Object accumulator) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(accumulator); - return reduce(sequence, initialValue, new Func2() { - @SuppressWarnings("unchecked") - @Override - public R call(R r, T t) { - return (R) _f.call(r, t); - } - }); - } - - /** - * Synonymous with {@code reduce()}. *

* * @@ -2087,17 +1587,6 @@ public static Observable aggregate(Observable sequence, R initialVa return reduce(sequence, initialValue, accumulator); } - /** - * A version of {@code aggregate()} for use by dynamic languages. - *

- * - * - * @see #reduce(Observable, Object, Func2) - */ - public static Observable aggregate(Observable sequence, R initialValue, Object accumulator) { - return reduce(sequence, initialValue, accumulator); - } - /** * Returns an Observable that applies a function of your choosing to the first item emitted by a * source Observable, then feeds the result of that function along with the second item emitted @@ -2122,27 +1611,6 @@ public static Observable scan(Observable sequence, Func2 accu return create(OperationScan.scan(sequence, accumulator)); } - /** - * A version of {@code scan()} for use by dynamic languages. - *

- * - * - * @see #scan(Observable, Func2) - */ - public static Observable scan(final Observable sequence, final Object accumulator) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(accumulator); - return scan(sequence, new Func2() { - - @SuppressWarnings("unchecked") - @Override - public T call(T t1, T t2) { - return (T) _f.call(t1, t2); - } - - }); - } - /** * Returns an Observable that applies a function of your choosing to the first item emitted by a * source Observable, then feeds the result of that function along with the second item emitted @@ -2175,27 +1643,6 @@ public static Observable scan(Observable sequence, R initialValue, } /** - * A version of {@code scan()} for use by dynamic languages. - *

- * - * - * @see #scan(Observable, Object, Func2) - */ - public static Observable scan(final Observable sequence, final R initialValue, final Object accumulator) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(accumulator); - return scan(sequence, initialValue, new Func2() { - - @SuppressWarnings("unchecked") - @Override - public R call(R r, T t) { - return (R) _f.call(r, t); - } - }); - } - - /** - * Returns an Observable that emits a single Boolean value that indicates whether all items emitted by a * source Observable satisfy a condition. *

* @@ -2214,34 +1661,6 @@ public static Observable all(final Observable sequence, final Fu } /** - * Returns an Observable that emits a single Boolean value that indicates whether all items emitted by a - * source Observable satisfy a condition. - *

- * - * - * @param sequence - * an Observable whose emitted items you are evaluating - * @param predicate - * a function that evaluates each emitted item and returns a Boolean - * @param - * the type of items emitted by the source Observable - * @return an Observable that emits {@code true} if all items emitted by the source - * Observable satisfy the predicate; otherwise, {@code false} - */ - public static Observable all(final Observable sequence, Object predicate) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(predicate); - - return all(sequence, new Func1() { - @Override - public Boolean call(T t) { - return (Boolean) _f.call(t); - } - }); - } - - /** - * Returns an Observable that skips the first {@code num} items emitted by the source * Observable and emits the remaining items. *

* @@ -2358,31 +1777,6 @@ public static Observable takeWhile(final Observable items, Func1 - * - * - * @param items - * the source Observable - * @param predicate - * a function to test each item emitted by the source Observable for a condition - * @return an Observable that emits items from the source Observable so long as the predicate - * continues to return {@code true} for each item, then completes - */ - public static Observable takeWhile(final Observable items, Object predicate) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(predicate); - - return takeWhile(items, new Func1() { - @Override - public Boolean call(T t) { - return (Boolean) _f.call(t); - } - }); - } - /** * Returns an Observable that emits the items emitted by a source Observable so long as a given * predicate remains true, where the predicate can operate on both the item and its index @@ -2402,35 +1796,6 @@ public static Observable takeWhileWithIndex(final Observable items, Fu return create(OperationTakeWhile.takeWhileWithIndex(items, predicate)); } - /** - * Returns an Observable that emits the items emitted by a source Observable so long as a given - * predicate remains true, where the predicate can operate on both the item and its index - * relative to the complete sequence. - *

- * - * - * @param items - * the source Observable - * @param predicate - * a function to test each item emitted by the source Observable for a condition; - * the second parameter of the function represents the index of the source item - * @return an Observable that emits items from the source Observable so long as the predicate - * continues to return {@code true} for each item, then completes - */ - public static Observable takeWhileWithIndex(final Observable items, Object predicate) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(predicate); - - return create(OperationTakeWhile.takeWhileWithIndex(items, new Func2() - { - @Override - public Boolean call(T t, Integer integer) - { - return (Boolean) _f.call(t, integer); - } - })); - } - /** * Wraps each item emitted by a source Observable in a {@link Timestamped} object. *

@@ -2646,46 +2011,18 @@ public static Observable> toSortedList(Observable sequence, Func2 } /** - * Return an Observable that emits a single list of the items emitted by the source Observable, sorted - * by the given comparison function. *

- * - * - * @param sequence - * the source Observable - * @param sortFunction - * a function that compares two items emitted by the source Observable and returns - * an Integer that indicates their sort order - * @return an Observable that emits a single, sorted list of the items from the source Observable - */ - public static Observable> toSortedList(Observable sequence, final Object sortFunction) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(sortFunction); - return create(OperationToObservableSortedList.toSortedList(sequence, new Func2() { - - @Override - public Integer call(T t1, T t2) { - return (Integer) _f.call(t1, t2); - } - - })); - } - - /** - * Returns an Observable that emits the results of a function of your choosing applied to pairs - * of items emitted, in sequence, by two other Observables. - *

- * - *

- * {@code zip} applies this function in strict sequence, so the first item emitted by the - * new Observable will be the result of the function applied to the first item emitted by - * {@code w0} and the first item emitted by {@code w1}; the second item emitted by - * the new Observable will be the result of the function applied to the second item emitted by - * {@code w0} and the second item emitted by {@code w1}; and so forth. - *

- * The resulting {@code Observable} returned from {@code zip} will invoke - * {@link Observer#onNext onNext} as many times as the number of {@code onNext} invocations - * of the source Observable that emits the fewest items. + * + *

+ * {@code zip} applies this function in strict sequence, so the first item emitted by the + * new Observable will be the result of the function applied to the first item emitted by + * {@code w0} and the first item emitted by {@code w1}; the second item emitted by + * the new Observable will be the result of the function applied to the second item emitted by + * {@code w0} and the second item emitted by {@code w1}; and so forth. + *

+ * The resulting {@code Observable} returned from {@code zip} will invoke + * {@link Observer#onNext onNext} as many times as the number of {@code onNext} invocations + * of the source Observable that emits the fewest items. * * @param w0 * one source Observable @@ -2747,68 +2084,6 @@ public static Observable sequenceEqual(Observable first, Observa return zip(first, second, equality); } - /** - * Returns an Observable that emits Boolean values that indicate whether the pairs of items - * emitted by two source Observables are equal based on the results of a specified equality - * function. - *

- * - * - * @param first - * one Observable to compare - * @param second - * the second Observable to compare - * @param equality - * a function used to compare items emitted by both Observables - * @param - * the type of items emitted by each Observable - * @return an Observable that emits Booleans that indicate whether the corresponding items - * emitted by the source Observables are equal - */ - public static Observable sequenceEqual(Observable first, Observable second, Object equality) { - return zip(first, second, equality); - } - - /** - * Returns an Observable that emits the results of a function of your choosing applied to pairs - * of items emitted, in sequence, by two other Observables. - *

- * - *

- * zip applies this function in strict sequence, so the first item emitted by the - * new Observable will be the result of the function applied to the first item emitted by - * w0 and the first item emitted by w1; the second item emitted by - * the new Observable will be the result of the function applied to the second item emitted by - * w0 and the second item emitted by w1; and so forth. - *

- * The resulting Observable<R> returned from zip will invoke - * {@link Observer#onNext onNext} as many times as the number of onNext invocations - * of the source Observable that emits the fewest items. - * - * @param w0 - * one source Observable - * @param w1 - * another source Observable - * @param function - * a function that, when applied to a pair of items, each emitted by one of the two - * source Observables, results in an item that will be emitted by the resulting - * Observable - * @return an Observable that emits the zipped results - */ - public static Observable zip(Observable w0, Observable w1, final Object function) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(function); - return zip(w0, w1, new Func2() { - - @SuppressWarnings("unchecked") - @Override - public R call(T0 t0, T1 t1) { - return (R) _f.call(t0, t1); - } - - }); - } - /** * Returns an Observable that emits the results of a function of your choosing applied to * combinations of three items emitted, in sequence, by three other Observables. @@ -2841,48 +2116,6 @@ public static Observable zip(Observable w0, Observable - * - *

- * zip applies this function in strict sequence, so the first item emitted by the - * new Observable will be the result of the function applied to the first item emitted by - * w0, the first item emitted by w1, and the first item emitted by - * w2; the second item emitted by the new Observable will be the result of the - * function applied to the second item emitted by w0, the second item emitted by - * w1, and the second item emitted by w2; and so forth. - *

- * The resulting Observable<R> returned from zip will invoke - * {@link Observer#onNext onNext} as many times as the number of onNext invocations - * of the source Observable that emits the fewest items. - * - * @param w0 - * one source Observable - * @param w1 - * another source Observable - * @param w2 - * a third source Observable - * @param function - * a function that, when applied to an item emitted by each of the source - * Observables, results in an item that will be emitted by the resulting Observable - * @return an Observable that emits the zipped results - */ - public static Observable zip(Observable w0, Observable w1, Observable w2, final Object function) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(function); - return zip(w0, w1, w2, new Func3() { - - @SuppressWarnings("unchecked") - @Override - public R call(T0 t0, T1 t1, T2 t2) { - return (R) _f.call(t0, t1, t2); - } - - }); - } - /** * Returns an Observable that emits the results of a function of your choosing applied to * combinations of four items emitted, in sequence, by four other Observables. @@ -2917,50 +2150,6 @@ public static Observable zip(Observable w0, Observabl return create(OperationZip.zip(w0, w1, w2, w3, reduceFunction)); } - /** - * Returns an Observable that emits the results of a function of your choosing applied to - * combinations of four items emitted, in sequence, by four other Observables. - *

- * - *

- * zip applies this function in strict sequence, so the first item emitted by the - * new Observable will be the result of the function applied to the first item emitted by - * w0, the first item emitted by w1, the first item emitted by - * w2, and the first item emitted by w3; the second item emitted by - * the new Observable will be the result of the function applied to the second item emitted by - * each of those Observables; and so forth. - *

- * The resulting Observable<R> returned from zip will invoke - * {@link Observer#onNext onNext} as many times as the number of onNext invocations - * of the source Observable that emits the fewest items. - * - * @param w0 - * one source Observable - * @param w1 - * another source Observable - * @param w2 - * a third source Observable - * @param w3 - * a fourth source Observable - * @param function - * a function that, when applied to an item emitted by each of the source - * Observables, results in an item that will be emitted by the resulting Observable - * @return an Observable that emits the zipped results - */ - public static Observable zip(Observable w0, Observable w1, Observable w2, Observable w3, final Object function) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(function); - return zip(w0, w1, w2, w3, new Func4() { - - @SuppressWarnings("unchecked") - @Override - public R call(T0 t0, T1 t1, T2 t2, T3 t3) { - return (R) _f.call(t0, t1, t2, t3); - } - - }); - } - /** * Creates an Observable which produces buffers of collected values. * @@ -3199,34 +2388,6 @@ public Observable call(List> wsList) { }); } - /** - * Returns an Observable that emits the results of a function of your choosing applied to - * combinations of four items emitted, in sequence, by four other Observables. - *

- * zip applies this function in strict sequence, so the first item emitted by the - * new Observable will be the result of the function applied to the first item emitted by - * all of the Observalbes; the second item emitted by the new Observable will be the result of - * the function applied to the second item emitted by each of those Observables; and so forth. - *

- * The resulting Observable returned from zip will invoke - * onNext as many times as the number of onNext invocations of the - * source Observable that emits the fewest items. - *

- * - * - * @param ws - * An Observable of source Observables - * @param function - * a function that, when applied to an item emitted by each of the source - * Observables, results in an item that will be emitted by the resulting Observable - * @return an Observable that emits the zipped results - */ - public static Observable zip(Observable> ws, final Object function) { - @SuppressWarnings({ "unchecked" }) - final FuncN _f = Functions.from(function); - return zip(ws, _f); - } - /** * Returns an Observable that emits the results of a function of your choosing applied to * combinations of four items emitted, in sequence, by four other Observables. @@ -3253,34 +2414,6 @@ public static Observable zip(Collection> ws, FuncN reduc return create(OperationZip.zip(ws, reduceFunction)); } - /** - * Returns an Observable that emits the results of a function of your choosing applied to - * combinations of four items emitted, in sequence, by four other Observables. - *

- * {@code zip} applies this function in strict sequence, so the first item emitted by the - * new Observable will be the result of the function applied to the first item emitted by - * all of the Observalbes; the second item emitted by the new Observable will be the result of - * the function applied to the second item emitted by each of those Observables; and so forth. - *

- * The resulting {@code Observable} returned from {@code zip} will invoke - * {@code onNext} as many times as the number of {@code onNext} invocations of the - * source Observable that emits the fewest items. - *

- * - * - * @param ws - * A collection of source Observables - * @param function - * a function that, when applied to an item emitted by each of the source - * Observables, results in an item that will be emitted by the resulting Observable - * @return an Observable that emits the zipped results - */ - public static Observable zip(Collection> ws, final Object function) { - @SuppressWarnings({ "unchecked" }) - final FuncN _f = Functions.from(function); - return zip(ws, _f); - } - /** * Combines the given observables, emitting an event containing an aggregation of the latest values of each of the source observables * each time an event is received from one of the source observables, where the aggregation is defined by the given function. @@ -3313,8 +2446,7 @@ public static Observable combineLatest(Observable * * @@ -3344,29 +2476,6 @@ public Observable finallyDo(Action0 action) { return create(OperationFinally.finallyDo(this, action)); } - /** - * Filters an Observable by discarding any of its items that do not satisfy the given predicate. - *

- * - * - * @param callback - * a function that evaluates an item emitted by the source Observable, returning - * {@code true} if it passes the filter - * @return an Observable that emits only those items in the original Observable that the filter - * evaluates as {@code true} - */ - public Observable filter(final Object callback) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(callback); - return filter(this, new Func1() { - - @Override - public Boolean call(T t1) { - return (Boolean) _f.call(t1); - } - }); - } - /** * Creates a new Observable by applying a function that you supply to each item emitted by * the source Observable, where that function returns an Observable, and then merging those @@ -3389,28 +2498,6 @@ public Observable flatMap(Func1> func) { } /** - * Creates a new Observable by applying a function that you supply to each item emitted by - * the source Observable, where that function returns an Observable, and then merging those - * resulting Observables and emitting the results of this merger. - *

- * - *

- * Note: mapMany and flatMap are equivalent. - * - * @param callback - * a function that, when applied to an item emitted by the source Observable, returns - * an Observable - * @return an Observable that emits the result of applying the transformation function to each - * item emitted by the source Observable and merging the results of the Observables - * obtained from this transformation. - * @see #mapMany(Object) - */ - public Observable flatMap(final Object callback) { - return mapMany(callback); - } - - /** - * Filters an Observable by discarding any items it emits that do not satisfy the given predicate *

* * @@ -3440,30 +2527,6 @@ public Observable map(Func1 func) { return map(this, func); } - /** - * Returns an Observable that applies the given function to each item emitted by an - * Observable and emits the result. - *

- * - * - * @param callback - * a function to apply to each item emitted by the Observable - * @return an Observable that emits the items from the source Observable, transformed by the - * given function - */ - public Observable map(final Object callback) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(callback); - return map(this, new Func1() { - - @Override - @SuppressWarnings("unchecked") - public R call(T t1) { - return (R) _f.call(t1); - } - }); - } - /** * Creates a new Observable by applying a function that you supply to each item emitted by * the source Observable, where that function returns an Observable, and then merging those @@ -3485,36 +2548,6 @@ public Observable mapMany(Func1> func) { return mapMany(this, func); } - /** - * Creates a new Observable by applying a function that you supply to each item emitted by - * the source Observable, where that function returns an Observable, and then merging those - * resulting Observables and emitting the results of this merger. - *

- * - *

- * Note: mapMany and flatMap are equivalent. - * - * @param callback - * a function that, when applied to an item emitted by the source Observable, returns - * an Observable - * @return an Observable that emits the result of applying the transformation function to each - * item emitted by the source Observable and merging the results of the Observables - * obtained from this transformation. - * @see #flatMap(Object) - */ - public Observable mapMany(final Object callback) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(callback); - return mapMany(this, new Func1>() { - - @Override - @SuppressWarnings("unchecked") - public Observable call(T t1) { - return (Observable) _f.call(t1); - } - }); - } - /** * Turns all of the notifications from a source Observable into {@link Observer#onNext onNext} * emissions, and marks them with their original notification types within {@link Notification} @@ -3606,42 +2639,6 @@ public Observable onErrorResumeNext(final Func1> res return onErrorResumeNext(this, resumeFunction); } - /** - * Instruct an Observable to emit an item (returned by a specified function) rather than - * invoking {@link Observer#onError onError} if it encounters an error. - *

- * - *

- * By default, when an Observable encounters an error that prevents it from emitting the - * expected item to its {@link Observer}, the Observable invokes its Observer's - * onError method, and then quits without invoking any more of its Observer's - * methods. The onErrorReturn method changes this behavior. If you pass a function - * (resumeFunction) to an Observable's onErrorReturn method, if the - * original Observable encounters an error, instead of invoking its Observer's - * onError function, it will instead pass the return value of - * resumeFunction to the Observer's {@link Observer#onNext onNext} method. - *

- * You can use this to prevent errors from propagating or to supply fallback data should errors - * be encountered. - * - * @param resumeFunction - * a function that returns an item that the Observable will emit if the source - * Observable encounters an error - * @return the original Observable with appropriately modified behavior - */ - public Observable onErrorResumeNext(final Object resumeFunction) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(resumeFunction); - return onErrorResumeNext(this, new Func1>() { - - @Override - @SuppressWarnings("unchecked") - public Observable call(Throwable e) { - return (Observable) _f.call(e); - } - }); - } - /** * Instruct an Observable to pass control to another Observable rather than invoking * {@link Observer#onError onError} if it encounters an error. @@ -3731,42 +2728,6 @@ public Observable onErrorReturn(Func1 resumeFunction) { return onErrorReturn(this, resumeFunction); } - /** - * Instruct an Observable to emit a particular item rather than invoking - * {@link Observer#onError onError} if it encounters an error. - *

- * - *

- * By default, when an Observable encounters an error that prevents it from emitting the - * expected item to its {@link Observer}, the Observable invokes its Observer's - * onError method, and then quits without invoking any more of its Observer's - * methods. The onErrorReturn method changes this behavior. If you pass a function - * (resumeFunction) to an Observable's onErrorReturn method, if the - * original Observable encounters an error, instead of invoking its Observer's - * onError function, it will instead pass the return value of - * resumeFunction to the Observer's {@link Observer#onNext onNext} method. - *

- * You can use this to prevent errors from propagating or to supply fallback data should errors - * be encountered. - * - * @param resumeFunction - * a function that returns an item that the new Observable will emit if the source - * Observable encounters an error - * @return the original Observable with appropriately modified behavior - */ - public Observable onErrorReturn(final Object resumeFunction) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(resumeFunction); - return onErrorReturn(this, new Func1() { - - @Override - @SuppressWarnings("unchecked") - public T call(Throwable e) { - return (T) _f.call(e); - } - }); - } - /** * Returns an Observable that applies a function of your choosing to the first item emitted by a * source Observable, then feeds the result of that function along with the second item emitted @@ -3840,17 +2801,6 @@ public ConnectableObservable publish() { return publish(this); } - /** - * A version of reduce() for use by dynamic languages. - *

- * - * - * @see #reduce(Func2) - */ - public Observable reduce(Object accumulator) { - return reduce(this, accumulator); - } - /** * Synonymous with reduce(). *

@@ -3862,17 +2812,6 @@ public Observable aggregate(Func2 accumulator) { return aggregate(this, accumulator); } - /** - * A version of aggregate() for use by dynamic languages. - *

- * - * - * @see #reduce(Func2) - */ - public Observable aggregate(Object accumulator) { - return aggregate(this, accumulator); - } - /** * Returns an Observable that applies a function of your choosing to the first item emitted by a * source Observable, then feeds the result of that function along with the second item emitted @@ -3900,17 +2839,6 @@ public Observable reduce(R initialValue, Func2 accumulator) { return reduce(this, initialValue, accumulator); } - /** - * A version of reduce() for use by dynamic languages. - *

- * - * - * @see #reduce(Object, Func2) - */ - public Observable reduce(R initialValue, Object accumulator) { - return reduce(this, initialValue, accumulator); - } - /** * Synonymous with reduce(). *

@@ -3922,17 +2850,6 @@ public Observable aggregate(R initialValue, Func2 accumulator) { return aggregate(this, initialValue, accumulator); } - /** - * A version of aggregate() for use by dynamic languages. - *

- * - * - * @see #reduce(Object, Func2) - */ - public Observable aggregate(R initialValue, Object accumulator) { - return aggregate(this, initialValue, accumulator); - } - /** * Returns an Observable that applies a function of your choosing to the first item emitted by a * source Observable, then feeds the result of that function along with the second item emitted @@ -3993,17 +2910,6 @@ public Observable sample(long period, TimeUnit unit, Scheduler scheduler) { return create(OperationSample.sample(this, period, unit, scheduler)); } - /** - * A version of scan() for use by dynamic languages. - *

- * - * - * @see #scan(Func2) - */ - public Observable scan(final Object accumulator) { - return scan(this, accumulator); - } - /** * Returns an Observable that applies a function of your choosing to the first item emitted by a * source Observable, then feeds the result of that function along with the second item emitted @@ -4031,17 +2937,6 @@ public Observable scan(R initialValue, Func2 accumulator) { return scan(this, initialValue, accumulator); } - /** - * A version of scan() for use by dynamic languages. - *

- * - * - * @see #scan(Object, Func2) - */ - public Observable scan(final R initialValue, final Object accumulator) { - return scan(this, initialValue, accumulator); - } - /** * Returns an Observable that emits a Boolean that indicates whether all of the items emitted by * the source Observable satisfy a condition. @@ -4057,21 +2952,6 @@ public Observable all(Func1 predicate) { return all(this, predicate); } - /** - * Returns an Observable that emits a Boolean that indicates whether all of the items emitted by - * the source Observable satisfy a condition. - *

- * - * - * @param predicate - * a function that evaluates an item and returns a Boolean - * @return an Observable that emits true if all items emitted by the source - * Observable satisfy the predicate; otherwise, false - */ - public Observable all(Object predicate) { - return all(this, predicate); - } - /** * Returns an Observable that skips the first num items emitted by the source * Observable and emits the remainder. @@ -4126,22 +3006,6 @@ public Observable takeWhile(final Func1 predicate) { return takeWhile(this, predicate); } - /** - * Returns an Observable that emits items emitted by the source Observable so long as a - * specified condition is true. - *

- * - * - * @param predicate - * a function that evaluates an item emitted by the source Observable and returns a - * Boolean - * @return an Observable that emits the items from the source Observable so long as each item - * satisfies the condition defined by predicate - */ - public Observable takeWhile(final Object predicate) { - return takeWhile(this, predicate); - } - /** * Returns an Observable that emits the items emitted by a source Observable so long as a given * predicate remains true, where the predicate can operate on both the item and its index @@ -4159,24 +3023,6 @@ public Observable takeWhileWithIndex(final Func2 predica return takeWhileWithIndex(this, predicate); } - /** - * Returns an Observable that emits the items emitted by a source Observable so long as a given - * predicate remains true, where the predicate can operate on both the item and its index - * relative to the complete sequence. - *

- * - * - * @param predicate - * a function that evaluates an item emitted by the source Observable and returns a - * Boolean; the second parameter of the function represents the index of the source - * item - * @return an Observable that emits items from the source Observable so long as the predicate - * continues to return true for each item, then completes - */ - public Observable takeWhileWithIndex(final Object predicate) { - return takeWhileWithIndex(this, predicate); - } - /** * Returns an Observable that emits only the last count items emitted by the source * Observable. @@ -4265,21 +3111,6 @@ public Observable> toSortedList(Func2 sortFunction) { return toSortedList(this, sortFunction); } - /** - * Return an Observable that emits the items emitted by the source Observable, in a sorted - * order based on a specified comparison function - *

- * - * - * @param sortFunction - * a function that compares two items emitted by the source Observable and returns - * an Integer that indicates their sort order - * @return an Observable that emits the items from the source Observable in sorted order - */ - public Observable> toSortedList(final Object sortFunction) { - return toSortedList(this, sortFunction); - } - /** * Emit a specified set of items before beginning to emit items from the source Observable. *

@@ -4316,28 +3147,6 @@ public Observable> groupBy(final Func1 keyS return groupBy(this, keySelector, elementSelector); } - /** - * Groups the items emitted by an Observable according to a specified criterion, and emits these - * grouped items as {@link GroupedObservable}s, one GroupedObservable per group. - *

- * - * - * @param keySelector - * a function that extracts the key from an item - * @param elementSelector - * a function to map a source item to an item in a {@link GroupedObservable} - * @param - * the key type - * @param - * the type of items emitted by the resulting {@link GroupedObservable}s - * @return an Observable that emits {@link GroupedObservable}s, each of which corresponds to a - * unique key value and emits items representing items from the source Observable that - * share that key value - */ - public Observable> groupBy(final Object keySelector, final Object elementSelector) { - return groupBy(this, keySelector, elementSelector); - } - /** * Groups the items emitted by an Observable according to a specified criterion, and emits these * grouped items as {@link GroupedObservable}s, one GroupedObservable per group. @@ -4356,24 +3165,6 @@ public Observable> groupBy(final Func1 keySele return groupBy(this, keySelector); } - /** - * Groups the items emitted by an Observable according to a specified criterion, and emits these - * grouped items as {@link GroupedObservable}s, one GroupedObservable per group. - *

- * - * - * @param keySelector - * a function that extracts the key for each item - * @param - * the key type - * @return an Observable that emits {@link GroupedObservable}s, each of which corresponds to a - * unique key value and emits items representing items from the source Observable that - * share that key value - */ - public Observable> groupBy(final Object keySelector) { - return groupBy(this, keySelector); - } - /** * Converts an Observable into a {@link BlockingObservable} (an Observable with blocking * operators). @@ -4905,10 +3696,10 @@ public void run() { }).start(); return Subscriptions.empty(); } - }).subscribe(new Action1() { + }).subscribe(new Action1() { @Override - public void call(Object t1) { + public void call(String t1) { } diff --git a/rxjava-core/src/main/java/rx/observables/BlockingObservable.java b/rxjava-core/src/main/java/rx/observables/BlockingObservable.java index 2d826a8fea..eb16260d2b 100644 --- a/rxjava-core/src/main/java/rx/observables/BlockingObservable.java +++ b/rxjava-core/src/main/java/rx/observables/BlockingObservable.java @@ -117,22 +117,6 @@ public static T last(final Observable source, final Func1 pre return last(source.filter(predicate)); } - /** - * Returns the last item emitted by an {@link Observable} that matches a given predicate. - *

- * - * - * @param source - * the source {@link Observable} - * @param predicate - * a predicate function to evaluate items emitted by the {@link Observable} - * @return the last item emitted by the {@link Observable} for which the predicate function - * returns true - */ - public static T last(final Observable source, final Object predicate) { - return last(source.filter(predicate)); - } - /** * Returns the last item emitted by an {@link Observable}, or a default value if no item is * emitted. @@ -173,35 +157,6 @@ public static T lastOrDefault(Observable source, T defaultValue, Func1 - * - * - * @param source - * the source {@link Observable} - * @param defaultValue - * a default value to return if the {@link Observable} emits no matching items - * @param predicate - * a predicate function to evaluate items emitted by the {@link Observable} - * @param - * the type of items emitted by the {@link Observable} - * @return the last item emitted by an {@link Observable} that matches the predicate, or the - * default value if no matching item is emitted - */ - public static T lastOrDefault(Observable source, T defaultValue, Object predicate) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(predicate); - - return lastOrDefault(source, defaultValue, new Func1() { - @Override - public Boolean call(T args) { - return (Boolean) _f.call(args); - } - }); - } - /** * Returns an {@link Iterable} that always returns the item most recently emitted by an * {@link Observable}. @@ -293,24 +248,6 @@ public static T single(Observable source, Func1 predicate) { return from(source).single(predicate); } - /** - * If the {@link Observable} completes after emitting a single item that matches a given - * predicate, return that item, otherwise throw an exception. - * - * - * @param source - * the source {@link Observable} - * @param predicate - * a predicate function to evaluate items emitted by the {@link Observable} - * @return the single item emitted by the source {@link Observable} that matches the predicate - * @throws IllegalStateException - * if the {@link Observable} does not emit exactly one item that matches the - * predicate - */ - public static T single(Observable source, Object predicate) { - return from(source).single(predicate); - } - /** * If the {@link Observable} completes after emitting a single item, return that item, otherwise * return a default value. @@ -347,25 +284,6 @@ public static T singleOrDefault(Observable source, T defaultValue, Func1< return from(source).singleOrDefault(defaultValue, predicate); } - /** - * If the {@link Observable} completes after emitting a single item that matches a given - * predicate, return that item, otherwise return a default value. - *

- * - * - * @param source - * the source {@link Observable} - * @param defaultValue - * a default value to return if the {@link Observable} emits no matching items - * @param predicate - * a predicate function to evaluate items emitted by the {@link Observable} - * @return the single item emitted by the source {@link Observable} that matches the predicate, - * or a default value if no such value is emitted - */ - public static T singleOrDefault(Observable source, T defaultValue, Object predicate) { - return from(source).singleOrDefault(defaultValue, predicate); - } - /** * Returns a {@link Future} representing the single value emitted by an {@link Observable}. *

@@ -476,45 +394,6 @@ public void onNext(T args) { } } - /** - * Invoke a method on each item emitted by the {@link Observable}; block until the Observable - * completes. - *

- * NOTE: This will block even if the Observable is asynchronous. - *

- * This is similar to {@link #subscribe(Observer)}, but it blocks. Because it blocks it does - * not need the {@link Observer#onCompleted()} or {@link Observer#onError(Throwable)} methods. - *

- * - * - * @param o - * the {@link Action1} to invoke for every item emitted by the {@link Observable} - * @throws RuntimeException - * if an error occurs - */ - @SuppressWarnings({ "rawtypes", "unchecked" }) - public void forEach(final Object o) { - if (o instanceof Action1) { - // in case a dynamic language is not correctly handling the overloaded methods and we receive an Action1 just forward to the correct method. - forEach((Action1) o); - } - - // lookup and memoize onNext - if (o == null) { - throw new RuntimeException("onNext must be implemented"); - } - final FuncN onNext = Functions.from(o); - - forEach(new Action1() { - - @Override - public void call(Object args) { - onNext.call(args); - } - - }); - } - /** * Returns an {@link Iterator} that iterates over all items emitted by a specified * {@link Observable}. @@ -555,27 +434,6 @@ public T last(final Func1 predicate) { return last(this, predicate); } - /** - * Returns the last item emitted by a specified {@link Observable} that matches a predicate. - *

- * - * - * @param predicate - * a predicate function to evaluate items emitted by the {@link Observable} - * @return the last item emitted by the {@link Observable} that matches the predicate - */ - public T last(final Object predicate) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(predicate); - - return last(this, new Func1() { - @Override - public Boolean call(T args) { - return (Boolean) _f.call(args); - } - }); - } - /** * Returns the last item emitted by a specified {@link Observable}, or a default value if no * items are emitted. @@ -620,23 +478,6 @@ public T lastOrDefault(T defaultValue, Func1 predicate) { return lastOrDefault(this, defaultValue, predicate); } - /** - * Returns the last item emitted by a specified {@link Observable} that matches a predicate, or - * a default value if no such items are emitted. - *

- * - * - * @param defaultValue - * a default value to return if the {@link Observable} emits no matching items - * @param predicate - * a predicate function to evaluate items emitted by the {@link Observable} - * @return the last item emitted by the {@link Observable} that matches the predicate, or the - * default value if no matching items are emitted - */ - public T lastOrDefault(T defaultValue, Object predicate) { - return lastOrDefault(this, defaultValue, predicate); - } - /** * Returns an {@link Iterable} that always returns the item most recently emitted by an * {@link Observable}. @@ -692,28 +533,6 @@ public T single(Func1 predicate) { return _singleOrDefault(from(this.filter(predicate)), false, null); } - /** - * If the {@link Observable} completes after emitting a single item that matches a given - * predicate, return that item, otherwise throw an exception. - *

- * - * - * @param predicate - * a predicate function to evaluate items emitted by the {@link Observable} - * @return the single item emitted by the source {@link Observable} that matches the predicate - */ - public T single(Object predicate) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(predicate); - - return single(new Func1() { - @Override - public Boolean call(T t) { - return (Boolean) _f.call(t); - } - }); - } - /** * If the {@link Observable} completes after emitting a single item, return that item; if it * emits more than one item, throw an exception; if it emits no items, return a default value. @@ -747,32 +566,6 @@ public T singleOrDefault(T defaultValue, Func1 predicate) { return _singleOrDefault(from(this.filter(predicate)), true, defaultValue); } - /** - * If the {@link Observable} completes after emitting a single item that matches a predicate, - * return that item; if it emits more than one such item, throw an exception; if it emits no - * items, return a default value. - *

- * - * - * @param defaultValue - * a default value to return if the {@link Observable} emits no matching items - * @param predicate - * a predicate function to evaluate items emitted by the {@link Observable} - * @return the single item emitted by the {@link Observable} that matches the predicate, or the - * default value if no such items are emitted - */ - public T singleOrDefault(T defaultValue, final Object predicate) { - @SuppressWarnings("rawtypes") - final FuncN _f = Functions.from(predicate); - - return singleOrDefault(defaultValue, new Func1() { - @Override - public Boolean call(T t) { - return (Boolean) _f.call(t); - } - }); - } - /** * Returns a {@link Future} representing the single value emitted by an {@link Observable}. *

diff --git a/rxjava-core/src/main/java/rx/subjects/AsyncSubject.java b/rxjava-core/src/main/java/rx/subjects/AsyncSubject.java index b154702ff8..c25ea56dee 100644 --- a/rxjava-core/src/main/java/rx/subjects/AsyncSubject.java +++ b/rxjava-core/src/main/java/rx/subjects/AsyncSubject.java @@ -130,9 +130,8 @@ public static class UnitTest { @Test public void testNeverCompleted() { - AsyncSubject subject = AsyncSubject.create(); + AsyncSubject subject = AsyncSubject.create(); - @SuppressWarnings("unchecked") Observer aObserver = mock(Observer.class); subject.subscribe(aObserver); @@ -152,9 +151,8 @@ private void assertNeverCompletedObserver(Observer aObserver) @Test public void testCompleted() { - AsyncSubject subject = AsyncSubject.create(); + AsyncSubject subject = AsyncSubject.create(); - @SuppressWarnings("unchecked") Observer aObserver = mock(Observer.class); subject.subscribe(aObserver); @@ -175,9 +173,8 @@ private void assertCompletedObserver(Observer aObserver) @Test public void testError() { - AsyncSubject subject = AsyncSubject.create(); + AsyncSubject subject = AsyncSubject.create(); - @SuppressWarnings("unchecked") Observer aObserver = mock(Observer.class); subject.subscribe(aObserver); @@ -201,7 +198,7 @@ private void assertErrorObserver(Observer aObserver) @Test public void testUnsubscribeBeforeCompleted() { - AsyncSubject subject = AsyncSubject.create(); + AsyncSubject subject = AsyncSubject.create(); @SuppressWarnings("unchecked") Observer aObserver = mock(Observer.class); diff --git a/rxjava-core/src/main/java/rx/subjects/PublishSubject.java b/rxjava-core/src/main/java/rx/subjects/PublishSubject.java index 5fccdb86d2..467771758d 100644 --- a/rxjava-core/src/main/java/rx/subjects/PublishSubject.java +++ b/rxjava-core/src/main/java/rx/subjects/PublishSubject.java @@ -195,12 +195,12 @@ public static class UnitTest { @Test public void test() { PublishSubject subject = PublishSubject.create(); - final AtomicReference>> actualRef = new AtomicReference>>(); + final AtomicReference>> actualRef = new AtomicReference>>(); Observable>> wNotificationsList = subject.materialize().toList(); - wNotificationsList.subscribe(new Action1>>() { + wNotificationsList.subscribe(new Action1>>() { @Override - public void call(List> actual) { + public void call(List> actual) { actualRef.set(actual); } }); @@ -245,9 +245,8 @@ public void unsubscribe() { @Test public void testCompleted() { - PublishSubject subject = PublishSubject.create(); + PublishSubject subject = PublishSubject.create(); - @SuppressWarnings("unchecked") Observer aObserver = mock(Observer.class); subject.subscribe(aObserver); @@ -256,7 +255,6 @@ public void testCompleted() { subject.onNext("three"); subject.onCompleted(); - @SuppressWarnings("unchecked") Observer anotherObserver = mock(Observer.class); subject.subscribe(anotherObserver); @@ -286,9 +284,8 @@ private void assertNeverObserver(Observer aObserver) @Test public void testError() { - PublishSubject subject = PublishSubject.create(); + PublishSubject subject = PublishSubject.create(); - @SuppressWarnings("unchecked") Observer aObserver = mock(Observer.class); subject.subscribe(aObserver); @@ -297,7 +294,6 @@ public void testError() { subject.onNext("three"); subject.onError(testException); - @SuppressWarnings("unchecked") Observer anotherObserver = mock(Observer.class); subject.subscribe(anotherObserver); @@ -320,9 +316,8 @@ private void assertErrorObserver(Observer aObserver) @Test public void testSubscribeMidSequence() { - PublishSubject subject = PublishSubject.create(); + PublishSubject subject = PublishSubject.create(); - @SuppressWarnings("unchecked") Observer aObserver = mock(Observer.class); subject.subscribe(aObserver); @@ -331,7 +326,6 @@ public void testSubscribeMidSequence() { assertObservedUntilTwo(aObserver); - @SuppressWarnings("unchecked") Observer anotherObserver = mock(Observer.class); subject.subscribe(anotherObserver); @@ -353,9 +347,8 @@ private void assertCompletedStartingWithThreeObserver(Observer aObserver @Test public void testUnsubscribeFirstObserver() { - PublishSubject subject = PublishSubject.create(); + PublishSubject subject = PublishSubject.create(); - @SuppressWarnings("unchecked") Observer aObserver = mock(Observer.class); Subscription subscription = subject.subscribe(aObserver); @@ -365,7 +358,6 @@ public void testUnsubscribeFirstObserver() { subscription.unsubscribe(); assertObservedUntilTwo(aObserver); - @SuppressWarnings("unchecked") Observer anotherObserver = mock(Observer.class); subject.subscribe(anotherObserver); @@ -397,9 +389,8 @@ private void assertObservedUntilTwo(Observer aObserver) */ @Test public void testUnsubscribeAfterOnCompleted() { - PublishSubject subject = PublishSubject.create(); + PublishSubject subject = PublishSubject.create(); - @SuppressWarnings("unchecked") Observer anObserver = mock(Observer.class); subject.subscribe(anObserver); @@ -426,7 +417,7 @@ public void testUnsubscribeAfterOnCompleted() { @Test public void testUnsubscribeAfterOnError() { - PublishSubject subject = PublishSubject.create(); + PublishSubject subject = PublishSubject.create(); RuntimeException exception = new RuntimeException("failure"); @SuppressWarnings("unchecked") diff --git a/rxjava-core/src/main/java/rx/subjects/ReplaySubject.java b/rxjava-core/src/main/java/rx/subjects/ReplaySubject.java index b6711c15c4..2d852f5bb0 100644 --- a/rxjava-core/src/main/java/rx/subjects/ReplaySubject.java +++ b/rxjava-core/src/main/java/rx/subjects/ReplaySubject.java @@ -186,7 +186,7 @@ public static class UnitTest { @SuppressWarnings("unchecked") @Test public void testCompleted() { - ReplaySubject subject = ReplaySubject.create(); + ReplaySubject subject = ReplaySubject.create(); Observer o1 = mock(Observer.class); subject.subscribe(o1); @@ -223,7 +223,7 @@ private void assertCompletedObserver(Observer aObserver) @SuppressWarnings("unchecked") @Test public void testError() { - ReplaySubject subject = ReplaySubject.create(); + ReplaySubject subject = ReplaySubject.create(); Observer aObserver = mock(Observer.class); subject.subscribe(aObserver); @@ -256,7 +256,7 @@ private void assertErrorObserver(Observer aObserver) @SuppressWarnings("unchecked") @Test public void testSubscribeMidSequence() { - ReplaySubject subject = ReplaySubject.create(); + ReplaySubject subject = ReplaySubject.create(); Observer aObserver = mock(Observer.class); subject.subscribe(aObserver); @@ -280,7 +280,7 @@ public void testSubscribeMidSequence() { @SuppressWarnings("unchecked") @Test public void testUnsubscribeFirstObserver() { - ReplaySubject subject = ReplaySubject.create(); + ReplaySubject subject = ReplaySubject.create(); Observer aObserver = mock(Observer.class); Subscription subscription = subject.subscribe(aObserver); From b0291ac867eafacd006b303b7eca0b24044d8932 Mon Sep 17 00:00:00 2001 From: Matt Jacobs Date: Wed, 3 Jul 2013 01:39:33 -0700 Subject: [PATCH 2/8] Add Code generator that adapts the core Observable to a dynamic language's native function support --- build.gradle | 55 +++- language-adaptors/codegen/build.gradle | 39 +++ language-adaptors/codegen/examples.txt | 47 +++ .../java/rx/codegen/ClassPathBasedRunner.java | 48 +++ .../main/java/rx/codegen/CodeGenerator.java | 276 ++++++++++++++++++ language-adaptors/rxjava-clojure/build.gradle | 66 ++--- .../rx/lang/clojure/ClojureActionWrapper.java | 41 +++ .../java/rx/lang/clojure/ClojureAdaptor.java | 68 ++--- .../lang/clojure/ClojureFunctionWrapper.java | 58 ++++ language-adaptors/rxjava-dynamic/build.gradle | 43 +++ .../rx/lang/dynamic/DynamicActionWrapper.java | 38 +++ .../java/rx/lang/dynamic/DynamicAdaptor.java | 48 +++ .../lang/dynamic/DynamicFunctionWrapper.java | 57 ++++ language-adaptors/rxjava-groovy/build.gradle | 51 ++-- .../rx/lang/groovy/GroovyActionWrapper.java | 39 +++ .../java/rx/lang/groovy/GroovyAdaptor.java | 29 +- .../rx/lang/groovy/GroovyFunctionWrapper.java | 58 ++++ language-adaptors/rxjava-jruby/build.gradle | 44 +-- .../rx/lang/jruby/JRubyActionWrapper.java | 48 +++ .../main/java/rx/lang/jruby/JRubyAdaptor.java | 32 +- .../rx/lang/jruby/JRubyFunctionWrapper.java | 70 +++++ .../scala/rx/lang/scala/ScalaAdaptor.scala | 9 +- .../java/rx/subscriptions/Subscriptions.java | 17 -- .../main/java/rx/util/functions/Action.java | 10 + .../main/java/rx/util/functions/Action0.java | 2 +- .../main/java/rx/util/functions/Action1.java | 2 +- .../main/java/rx/util/functions/Action2.java | 2 +- .../main/java/rx/util/functions/Action3.java | 2 +- .../functions/FunctionLanguageAdaptor.java | 22 +- .../java/rx/util/functions/Functions.java | 212 ++------------ settings.gradle | 2 + 31 files changed, 1141 insertions(+), 394 deletions(-) create mode 100644 language-adaptors/codegen/build.gradle create mode 100644 language-adaptors/codegen/examples.txt create mode 100644 language-adaptors/codegen/src/main/java/rx/codegen/ClassPathBasedRunner.java create mode 100644 language-adaptors/codegen/src/main/java/rx/codegen/CodeGenerator.java create mode 100644 language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureActionWrapper.java create mode 100644 language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureFunctionWrapper.java create mode 100644 language-adaptors/rxjava-dynamic/build.gradle create mode 100644 language-adaptors/rxjava-dynamic/src/main/java/rx/lang/dynamic/DynamicActionWrapper.java create mode 100644 language-adaptors/rxjava-dynamic/src/main/java/rx/lang/dynamic/DynamicAdaptor.java create mode 100644 language-adaptors/rxjava-dynamic/src/main/java/rx/lang/dynamic/DynamicFunctionWrapper.java create mode 100644 language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyActionWrapper.java create mode 100644 language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyFunctionWrapper.java create mode 100644 language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyActionWrapper.java create mode 100644 language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyFunctionWrapper.java create mode 100644 rxjava-core/src/main/java/rx/util/functions/Action.java diff --git a/build.gradle b/build.gradle index 9073b716ee..85d83ea12d 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,9 @@ +apply from: file('gradle/convention.gradle') +apply from: file('gradle/maven.gradle') +//apply from: file('gradle/check.gradle') +apply from: file('gradle/license.gradle') +apply from: file('gradle/release.gradle') + ext.githubProjectName = rootProject.name buildscript { @@ -9,20 +15,55 @@ allprojects { repositories { mavenCentral() } } -apply from: file('gradle/convention.gradle') -apply from: file('gradle/maven.gradle') -//apply from: file('gradle/check.gradle') -apply from: file('gradle/license.gradle') -apply from: file('gradle/release.gradle') - subprojects { + apply plugin: 'java' + apply plugin: 'eclipse' + apply plugin: 'idea' + apply plugin: 'osgi' group = "com.netflix.${githubProjectName}" - sourceSets.test.java.srcDir 'src/main/java' + ext.coreJarDir = new File(rootDir, "/rxjava-core/build/libs").getCanonicalPath() + ext.outputDir = file("build/rewritten_classes") + + // make 'examples' use the same classpath + configurations { + core + examplesCompile.extendsFrom compile + examplesRuntime.extendsFrom runtime + } + + sourceSets.test.java.srcDirs.add('src/main/java') tasks.withType(Javadoc).each { it.classpath = sourceSets.main.compileClasspath } + + sourceSets { + //include /src/examples folder + examples + } + + // include 'examples' in build task + tasks.build { + dependsOn(examplesClasses) + } + + eclipse { + classpath { + // include 'provided' dependencies on the classpath + plusConfigurations += configurations.provided + + downloadSources = true + downloadJavadoc = true + } + } + + idea { + module { + // include 'provided' dependencies on the classpath + scopes.PROVIDED.plus += configurations.provided + } + } } diff --git a/language-adaptors/codegen/build.gradle b/language-adaptors/codegen/build.gradle new file mode 100644 index 0000000000..7967131270 --- /dev/null +++ b/language-adaptors/codegen/build.gradle @@ -0,0 +1,39 @@ +apply plugin: 'java' +apply plugin: 'groovy' +apply plugin: 'eclipse' +apply plugin: 'idea' +apply plugin: 'osgi' + +dependencies { + compile project(':rxjava-core') + compile 'org.javassist:javassist:3.17.1-GA' + + provided 'junit:junit:4.10' +} + +eclipse { + classpath { + // include 'provided' dependencies on the classpath + plusConfigurations += configurations.provided + + downloadSources = true + downloadJavadoc = true + } +} + +idea { + module { + // include 'provided' dependencies on the classpath + scopes.PROVIDED.plus += configurations.provided + } +} + +jar { + manifest { + name = 'rxjava-codegen' + instruction 'Bundle-Vendor', 'Netflix' + instruction 'Bundle-DocURL', 'https://github.com/Netflix/RxJava' + instruction 'Import-Package', '!org.junit,!junit.framework,!org.mockito.*,*' + instruction 'Fragment-Host', 'com.netflix.rxjava.core' + } +} \ No newline at end of file diff --git a/language-adaptors/codegen/examples.txt b/language-adaptors/codegen/examples.txt new file mode 100644 index 0000000000..3cece97b6f --- /dev/null +++ b/language-adaptors/codegen/examples.txt @@ -0,0 +1,47 @@ +Here's the example source of Observable (heavily elided): + +public class rx.Observable { + public static Observable create(Func1, Subscription> func) { + return new Observable(func); + } + + public static Observable create(Object func) { + ... + } + + public static Observable take(final Observable items, final int num) { + return create(OperationTake.take(items, num)); + } + + public static Observable takeWhile(final Observable items, Func1 predicate) { + return create(OperationTakeWhile.takeWhile(items, predicate)); + } + + public Observable filter(Func1 predicate) { + return filter(this, predicate); + } + + public static Observable filter(Observable that, Func1 predicate) { + return create(OperationFilter.filter(that, predicate)); + } +} + +Groovy-friendly version adds: + +public class rx.Observable { + public static rx.Observable create(groovy.lang.Closure func) { + return create(new GroovyFunctionAdaptor(func)); + } + + public static rx.Observable takeWhile(final Observable items, groovy.lang.Closure predicate) { + return takeWhile(items, new GroovyFunctionAdaptor(predicate)); + } + + public rx.Observable filter(groovy.lang.Closure predicate) { + return filter(new GroovyFunctionAdaptor(predicate)); + } + + public static rxObservable filter(Observable that, groovy.lang.Closure predicate) { + return filter(that, new GroovyFunctionAdaptor(predicate)); + } +} diff --git a/language-adaptors/codegen/src/main/java/rx/codegen/ClassPathBasedRunner.java b/language-adaptors/codegen/src/main/java/rx/codegen/ClassPathBasedRunner.java new file mode 100644 index 0000000000..5630724abd --- /dev/null +++ b/language-adaptors/codegen/src/main/java/rx/codegen/ClassPathBasedRunner.java @@ -0,0 +1,48 @@ +package rx.codegen; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import rx.util.functions.FunctionLanguageAdaptor; + +public class ClassPathBasedRunner { + public static void main(String[] args) { + if (args.length != 2) { + System.out.println("Usage : Expects 2 args: (Language, File to place classfiles)"); + System.out.println("Currently supported languages: Groovy/Clojure/JRuby"); + System.exit(1); + } + String lang = args[0]; + File dir = new File(args[1]); + System.out.println("Looking for Adaptor for : " + lang); + String className = "rx.lang." + lang.toLowerCase() + "." + lang + "Adaptor"; + try { + Class adaptorClass = Class.forName(className); + System.out.println("Found Adaptor : " + adaptorClass); + FunctionLanguageAdaptor adaptor = (FunctionLanguageAdaptor) adaptorClass.newInstance(); + + CodeGenerator codeGen = new CodeGenerator(); + System.out.println("Using dir : " + dir + " for outputting classfiles"); + for (Class observableClass: getObservableClasses()) { + codeGen.addMethods(observableClass, adaptor, new File(args[1])); + } + } catch (ClassNotFoundException ex) { + System.out.println("Did not find adaptor class : " + className); + System.exit(1); + } catch (InstantiationException ex) { + System.out.println("Reflective constuctor on : " + className + " failed"); + System.exit(1); + } catch (IllegalAccessException ex) { + System.out.println("Access to constructor on : " + className + " failed"); + System.exit(1); + } + } + + private static List> getObservableClasses() { + List> observableClasses = new ArrayList>(); + observableClasses.add(rx.Observable.class); + observableClasses.add(rx.observables.BlockingObservable.class); + return observableClasses; + } +} \ No newline at end of file diff --git a/language-adaptors/codegen/src/main/java/rx/codegen/CodeGenerator.java b/language-adaptors/codegen/src/main/java/rx/codegen/CodeGenerator.java new file mode 100644 index 0000000000..02d17d793d --- /dev/null +++ b/language-adaptors/codegen/src/main/java/rx/codegen/CodeGenerator.java @@ -0,0 +1,276 @@ +package rx.codegen; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.NotFoundException; +import rx.util.functions.Action; +import rx.util.functions.Action0; +import rx.util.functions.Action1; +import rx.util.functions.Action2; +import rx.util.functions.Action3; +import rx.util.functions.Func0; +import rx.util.functions.Func1; +import rx.util.functions.Func2; +import rx.util.functions.Func3; +import rx.util.functions.Func4; +import rx.util.functions.Func5; +import rx.util.functions.Func6; +import rx.util.functions.Func7; +import rx.util.functions.Func8; +import rx.util.functions.Func9; +import rx.util.functions.Function; +import rx.util.functions.FunctionLanguageAdaptor; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import org.junit.Test; + +public class CodeGenerator { + + static ClassPool pool = ClassPool.getDefault(); + + public void addMethods(Class initialClass, FunctionLanguageAdaptor adaptor, File file) { + Set> nativeFunctionClasses = adaptor.getAllClassesToRewrite(); + System.out.println("Adding dynamic language support to : " + initialClass.getSimpleName()); + for (Class nativeFunctionClass: nativeFunctionClasses) { + System.out.println(" * Adding : " + nativeFunctionClass.getName()); + } + addSupportFor(initialClass, adaptor, file); + } + + private static void addSupportFor(Class observableClass, FunctionLanguageAdaptor adaptor, File file) { + CtClass clazz; + + if (!observableClass.getName().startsWith("rx.")) { + throw new IllegalStateException("Refusing to rewrite a class that is not a core Rx Observable!"); + } + + Set> nativeFunctionClasses = adaptor.getAllClassesToRewrite(); + + try { + clazz = pool.get(observableClass.getName()); + } catch (NotFoundException e) { + throw new RuntimeException("Failed to add language adaptor methods as could not find observable Class named " + observableClass.getName(), e); + } + try { + rewriteMethodsWithRxArgs(clazz, adaptor); + writeClassFile(clazz, file); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Failed to add language adaptor methods.", e); + } + } + + private static void rewriteMethodsWithRxArgs(CtClass clazz, FunctionLanguageAdaptor adaptor) throws Exception { + List newMethods = new ArrayList(); + + for (CtMethod method : clazz.getMethods()) { + CtClass[] argTypes = method.getParameterTypes(); + boolean needsRewrite = false; + for (CtClass argType : argTypes) { + if (isRxFunctionType(argType) || isRxActionType(argType)) { + needsRewrite = true; + } + } + if (needsRewrite) { + try { + newMethods.addAll(getRewrittenMethods(clazz, method, adaptor)); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Failed to add language adaptor method: " + method.getName(), e); + } + } + } + + for (CtMethod cm: newMethods) { + clazz.addMethod(cm); + } + } + + private static boolean isNativeFunctionType(CtClass type, Set> nativeFunctionClasses) { + for (Class nativeFunctionClass: nativeFunctionClasses) { + if (type.getName().equals(nativeFunctionClass.getName())) { + return true; + } + } + return false; + } + + private static boolean isRxFunctionType(CtClass type) throws Exception { + return implementsInterface(type, Function.class); + } + + private static boolean isRxActionType(CtClass type) throws Exception { + return implementsInterface(type, Action.class); + } + + private static boolean implementsInterface(CtClass type, Class interfaceClass) throws Exception { + // Did I pass in the exact interface? + if (type.getName().equals(interfaceClass.getName())) { + return true; + } + // Do I implement the interface? + for (CtClass implementedInterface : type.getInterfaces()) { + if (implementedInterface.getName().equals(interfaceClass.getName())) { + return true; + } + } + return false; + } + + private static List getRewrittenMethods(CtClass clazz, CtMethod method, FunctionLanguageAdaptor adaptor) throws Exception { + List newMethods = new ArrayList(); + + for (Class nativeFunctionClass: adaptor.getAllClassesToRewrite()) { + Class functionAdaptorClass = adaptor.getFunctionClassRewritingMap().get(nativeFunctionClass); + Class actionAdaptorClass = adaptor.getActionClassRewritingMap().get(nativeFunctionClass); + ArrayList parameters = new ArrayList(); + CtClass[] argTypes = method.getParameterTypes(); + + ArrayList initialArgTypes = new ArrayList(); + ArrayList finalArgTypes = new ArrayList(); + + for (CtClass argType : argTypes) { + initialArgTypes.add(argType.getName()); + if (isRxFunctionType(argType) || isRxActionType(argType)) { + // needs conversion + finalArgTypes.add(nativeFunctionClass.getName()); + parameters.add(pool.get(nativeFunctionClass.getName())); + } else { + // no conversion, copy through + finalArgTypes.add(argType.getName()); + parameters.add(argType); + } + } + + String initialArgString = makeArgList(initialArgTypes); + String finalArgString = makeArgList(finalArgTypes); + + CtClass[] oldParameters = parameters.toArray(new CtClass[parameters.size()]); + CtMethod newMethod = new CtMethod(method.getReturnType(), method.getName(), oldParameters, clazz); + newMethod.setModifiers(method.getModifiers()); + List argumentList = new ArrayList(); + StringBuffer newBody = new StringBuffer(); + newBody.append("{ return "); + newBody.append(method.getName()); + newBody.append("("); + for (int i = 0; i < method.getParameterTypes().length; i++) { + CtClass argType = method.getParameterTypes()[i]; + if (isRxActionType(argType) && actionAdaptorClass != null) { + argumentList.add(getAdaptedArg(actionAdaptorClass, i + 1)); + } else if (isRxFunctionType(argType) && functionAdaptorClass != null) { + argumentList.add(getAdaptedArg(functionAdaptorClass, i + 1)); + } else { + argumentList.add(getUntouchedArg(i + 1)); + } + } + newBody.append(makeArgList(argumentList)); + newBody.append(")"); + newBody.append(";}"); + System.out.println(method.getReturnType().getName() + " " + method.getName() + "(" + initialArgString + ") --> " + newMethod.getReturnType().getName() + " " + newMethod.getName() + "(" + finalArgString + ")"); + System.out.println(" " + newBody.toString()); + newMethod.setBody(newBody.toString()); + newMethods.add(newMethod); + } + return newMethods; + } + + private static String getAdaptedArg(Class adaptorClass, int index) { + StringBuffer buffer = new StringBuffer(); + buffer.append("new "); + buffer.append(adaptorClass.getName()); + buffer.append("("); + buffer.append(getUntouchedArg(index)); + buffer.append(")"); + return buffer.toString(); + } + + private static String getUntouchedArg(int index) { + StringBuffer buffer = new StringBuffer(); + buffer.append("$"); + buffer.append(index); + return buffer.toString(); + } + + private static String makeArgList(List args) { + if (args.size() > 0) { + StringBuffer buffer = new StringBuffer(args.get(0)); + for (String arg: args.subList(1, args.size())) { + buffer.append("," + arg); + } + return buffer.toString(); + } + return ""; + } + + private static void writeClassFile(CtClass clazz, File dir) { + try { + System.out.println("Using " + dir.getCanonicalPath() + " for dynamic class file"); + clazz.writeFile(dir.getCanonicalPath()); + } catch (java.io.IOException ioe) { + System.out.println("Could not write classfile to : " + dir.toString()); + System.exit(1); + } catch (javassist.CannotCompileException cce) { + System.out.println("Could not create a valid classfile"); + System.exit(2); + } + } + + public static class UnitTest { + @Test + public void testIsRxFunctionType() { + try { + assertTrue(isRxFunctionType(pool.get(Function.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Func0.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Func1.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Func2.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Func3.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Func4.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Func5.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Func6.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Func7.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Func8.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Func9.class.getName()))); + assertFalse(isRxFunctionType(pool.get(Action.class.getName()))); + assertFalse(isRxFunctionType(pool.get(Action0.class.getName()))); + assertFalse(isRxFunctionType(pool.get(Action1.class.getName()))); + assertFalse(isRxFunctionType(pool.get(Action2.class.getName()))); + assertFalse(isRxFunctionType(pool.get(Action3.class.getName()))); + } catch (Exception e) { + fail(e.getMessage()); + } + } + + @Test + public void testIsRxActionType() { + try { + assertFalse(isRxActionType(pool.get(Function.class.getName()))); + assertFalse(isRxActionType(pool.get(Func0.class.getName()))); + assertFalse(isRxActionType(pool.get(Func1.class.getName()))); + assertFalse(isRxActionType(pool.get(Func2.class.getName()))); + assertFalse(isRxActionType(pool.get(Func3.class.getName()))); + assertFalse(isRxActionType(pool.get(Func4.class.getName()))); + assertFalse(isRxActionType(pool.get(Func5.class.getName()))); + assertFalse(isRxActionType(pool.get(Func6.class.getName()))); + assertFalse(isRxActionType(pool.get(Func7.class.getName()))); + assertFalse(isRxActionType(pool.get(Func8.class.getName()))); + assertFalse(isRxActionType(pool.get(Func9.class.getName()))); + assertTrue(isRxActionType(pool.get(Action.class.getName()))); + assertTrue(isRxActionType(pool.get(Action0.class.getName()))); + assertTrue(isRxActionType(pool.get(Action1.class.getName()))); + assertTrue(isRxActionType(pool.get(Action2.class.getName()))); + assertTrue(isRxActionType(pool.get(Action3.class.getName()))); + } catch (Exception e) { + fail(e.getMessage()); + } + } + } +} + diff --git a/language-adaptors/rxjava-clojure/build.gradle b/language-adaptors/rxjava-clojure/build.gradle index 0ea7feb27c..78cc056b41 100644 --- a/language-adaptors/rxjava-clojure/build.gradle +++ b/language-adaptors/rxjava-clojure/build.gradle @@ -1,17 +1,13 @@ -apply plugin: 'java' apply plugin: 'clojure' -apply plugin: 'eclipse' -apply plugin: 'idea' -apply plugin: 'osgi' dependencies { + core project(':rxjava-core') compile project(':rxjava-core') - provided 'org.clojure:clojure:1.4.+' + compile project(':language-adaptors:codegen') + compile 'org.clojure:clojure:1.4.+' + compile 'clj-http:clj-http:0.6.4' // https://clojars.org/clj-http provided 'junit:junit-dep:4.10' provided 'org.mockito:mockito-core:1.8.5' - - // clojure - testCompile 'clj-http:clj-http:0.6.4' // https://clojars.org/clj-http } /* @@ -26,57 +22,45 @@ buildscript { } repositories { - mavenCentral() clojarsRepo() } /* - * Add Counterclockwise and include 'provided' dependencies + * Add Counterclockwise to Eclipse */ eclipse { project { natures "ccw.nature" } - classpath { - plusConfigurations += configurations.provided - downloadSources = true - downloadJavadoc = true - } } -idea { - module { - // include 'provided' dependencies on the classpath - scopes.PROVIDED.plus += configurations.provided - } +task createAdaptedObservable(type: JavaExec) { + main = 'rx.codegen.ClassPathBasedRunner' + classpath = sourceSets.main.runtimeClasspath + args = ["Clojure", outputDir] + + inputs.files(sourceSets.main.runtimeClasspath) + outputs.dir(outputDir) } +tasks.test { + dependsOn(createAdaptedObservable) -eclipse { - classpath { - // include 'provided' dependencies on the classpath - plusConfigurations += configurations.provided - - downloadSources = true - downloadJavadoc = true - } + //Reorders the classpath so that the newly-create Observables win + classpath = createAdaptedObservable.outputs.files + sourceSets.test.runtimeClasspath } -// include /src/examples folder -sourceSets { - examples -} +tasks.jar { + dependsOn(createAdaptedObservable) -// make 'examples' use the same classpath -configurations { - examplesCompile.extendsFrom compile - examplesRuntime.extendsFrom runtime -} + from (zipTree(configurations.core.singleFile)) { + exclude "rx/Observable.class" + exclude "rx/observables/BlockingObservable.class" + } + from(outputDir) -// include 'examples' in build task -build.dependsOn examplesClasses + exclude('**/*$UnitTest*') -jar { manifest { name = 'rxjava-clojure' instruction 'Bundle-Vendor', 'Netflix' @@ -84,4 +68,4 @@ jar { instruction 'Import-Package', '!org.junit,!junit.framework,!org.mockito.*,*' instruction 'Fragment-Host', 'com.netflix.rxjava.core' } -} \ No newline at end of file +} diff --git a/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureActionWrapper.java b/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureActionWrapper.java new file mode 100644 index 0000000000..38d4dd64a8 --- /dev/null +++ b/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureActionWrapper.java @@ -0,0 +1,41 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.clojure; + +import rx.util.functions.Action0; +import rx.util.functions.Action1; + +import clojure.lang.IFn; +import clojure.lang.RT; +import clojure.lang.Var; + +public class ClojureActionWrapper implements Action0, Action1 { + private IFn ifn; + + public ClojureActionWrapper(IFn ifn) { + this.ifn = ifn; + } + + @Override + public void call() { + ifn.invoke(); + } + + @Override + public void call(T1 t1) { + ifn.invoke(t1); + } +} \ No newline at end of file diff --git a/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureAdaptor.java b/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureAdaptor.java index 4420f7c919..31cff79786 100644 --- a/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureAdaptor.java +++ b/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureAdaptor.java @@ -15,7 +15,10 @@ */ package rx.lang.clojure; -import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; import org.junit.Before; import org.junit.Test; @@ -32,57 +35,24 @@ public class ClojureAdaptor implements FunctionLanguageAdaptor { @Override - public Object call(Object function, Object[] args) { - if (args.length == 0) { - return ((IFn) function).invoke(); - } else if (args.length == 1) { - return ((IFn) function).invoke(args[0]); - } else if (args.length == 2) { - return ((IFn) function).invoke(args[0], args[1]); - } else if (args.length == 3) { - return ((IFn) function).invoke(args[0], args[1], args[2]); - } else if (args.length == 4) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3]); - } else if (args.length == 5) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4]); - } else if (args.length == 6) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4], args[5]); - } else if (args.length == 7) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6]); - } else if (args.length == 8) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]); - } else if (args.length == 9) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]); - } else if (args.length == 10) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]); - } else if (args.length == 11) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10]); - } else if (args.length == 12) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11]); - } else if (args.length == 13) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12]); - } else if (args.length == 14) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13]); - } else if (args.length == 15) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14]); - } else if (args.length == 16) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15]); - } else if (args.length == 17) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15], args[16]); - } else if (args.length == 18) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15], args[16], args[17]); - } else if (args.length == 19) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15], args[16], args[17], args[18]); - } else if (args.length == 20) { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15], args[16], args[17], args[18], args[19]); - } else { - return ((IFn) function).invoke(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11], args[12], args[13], args[14], args[15], args[16], args[17], args[18], args[19], Arrays.copyOfRange(args, 20, args.length)); - } + public Map, Class> getFunctionClassRewritingMap() { + Map, Class> m = new HashMap, Class>(); + m.put(IFn.class, ClojureFunctionWrapper.class); + return m; + } + + @Override + public Map, Class> getActionClassRewritingMap() { + Map, Class> m = new HashMap, Class>(); + m.put(IFn.class, ClojureActionWrapper.class); + return m; } @Override - public Class[] getFunctionClass() { - return new Class[] { IFn.class }; + public Set> getAllClassesToRewrite() { + Set> classes = new HashSet>(); + classes.add(IFn.class); + return classes; } public static class UnitTest { diff --git a/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureFunctionWrapper.java b/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureFunctionWrapper.java new file mode 100644 index 0000000000..1aed83fc7e --- /dev/null +++ b/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureFunctionWrapper.java @@ -0,0 +1,58 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.clojure; + +import rx.util.functions.Func0; +import rx.util.functions.Func1; +import rx.util.functions.Func2; +import rx.util.functions.Func3; +import rx.util.functions.Func4; +import rx.util.functions.FunctionLanguageAdaptor; + +import clojure.lang.IFn; + +public class ClojureFunctionWrapper implements Func0, Func1, Func2, Func3, Func4 { + private IFn ifn; + + public ClojureFunctionWrapper(IFn ifn) { + this.ifn = ifn; + } + + @Override + public R call() { + return (R) ifn.invoke(); + } + + @Override + public R call(T1 t1) { + return (R) ifn.invoke(t1); + } + + @Override + public R call(T1 t1, T2 t2) { + return (R) ifn.invoke(t1, t2); + } + + @Override + public R call(T1 t1, T2 t2, T3 t3) { + return (R) ifn.invoke(t1, t2, t3); + } + + @Override + public R call(T1 t1, T2 t2, T3 t3, T4 t4) { + return (R) ifn.invoke(t1, t2, t3, t4); + } +} \ No newline at end of file diff --git a/language-adaptors/rxjava-dynamic/build.gradle b/language-adaptors/rxjava-dynamic/build.gradle new file mode 100644 index 0000000000..570142943f --- /dev/null +++ b/language-adaptors/rxjava-dynamic/build.gradle @@ -0,0 +1,43 @@ +dependencies { + core project(':rxjava-core') + compile project(':rxjava-core') + compile project(':language-adaptors:codegen') + provided 'junit:junit-dep:4.10' + provided 'org.mockito:mockito-core:1.8.5' +} + +task createAdaptedObservable(type: JavaExec) { + main = 'rx.codegen.ClassPathBasedRunner' + classpath = sourceSets.main.runtimeClasspath + args = ["Dynamic", outputDir] + + inputs.files(sourceSets.main.runtimeClasspath) + outputs.dir(outputDir) +} + +tasks.test { + dependsOn(createAdaptedObservable) + + //Reorders the classpath so that the newly-create Observables win + classpath = createAdaptedObservable.outputs.files + sourceSets.test.runtimeClasspath +} + +tasks.jar { + dependsOn(createAdaptedObservable) + + from (zipTree(configurations.core.singleFile)) { + exclude "rx/Observable.class" + exclude "rx/observables/BlockingObservable.class" + } + from(outputDir) + + exclude('**/*$UnitTest*') + + manifest { + name = 'rxjava-dynamic' + instruction 'Bundle-Vendor', 'Netflix' + instruction 'Bundle-DocURL', 'https://github.com/Netflix/RxJava' + instruction 'Import-Package', '!org.junit,!junit.framework,!org.mockito.*,*' + instruction 'Fragment-Host', 'com.netflix.rxjava.core' + } +} diff --git a/language-adaptors/rxjava-dynamic/src/main/java/rx/lang/dynamic/DynamicActionWrapper.java b/language-adaptors/rxjava-dynamic/src/main/java/rx/lang/dynamic/DynamicActionWrapper.java new file mode 100644 index 0000000000..697176c80f --- /dev/null +++ b/language-adaptors/rxjava-dynamic/src/main/java/rx/lang/dynamic/DynamicActionWrapper.java @@ -0,0 +1,38 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.dynamic; + +import rx.util.functions.Action0; +import rx.util.functions.Action1; +import rx.util.functions.Functions; + +public class DynamicActionWrapper implements Action0, Action1 { + private Object object; + + public DynamicActionWrapper(Object object) { + this.object = object; + } + + @Override + public void call() { + Functions.from(object).call(); + } + + @Override + public void call(T1 t1) { + Functions.from(object).call(t1); + } +} \ No newline at end of file diff --git a/language-adaptors/rxjava-dynamic/src/main/java/rx/lang/dynamic/DynamicAdaptor.java b/language-adaptors/rxjava-dynamic/src/main/java/rx/lang/dynamic/DynamicAdaptor.java new file mode 100644 index 0000000000..636e0e54ea --- /dev/null +++ b/language-adaptors/rxjava-dynamic/src/main/java/rx/lang/dynamic/DynamicAdaptor.java @@ -0,0 +1,48 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.dynamic; + +import java.util.HashMap; +import java.util.Map; + +import rx.util.functions.FunctionLanguageAdaptor; + +import java.util.HashSet; +import java.util.Set; + +public class DynamicAdaptor implements FunctionLanguageAdaptor { + + @Override + public Map, Class> getFunctionClassRewritingMap() { + Map, Class> m = new HashMap, Class>(); + m.put(Object.class, DynamicFunctionWrapper.class); + return m; + } + + @Override + public Map, Class> getActionClassRewritingMap() { + Map, Class> m = new HashMap, Class>(); + m.put(Object.class, DynamicActionWrapper.class); + return m; + } + + @Override + public Set> getAllClassesToRewrite() { + Set> dynamicClasses = new HashSet>(); + dynamicClasses.add(Object.class); + return dynamicClasses; + } +} diff --git a/language-adaptors/rxjava-dynamic/src/main/java/rx/lang/dynamic/DynamicFunctionWrapper.java b/language-adaptors/rxjava-dynamic/src/main/java/rx/lang/dynamic/DynamicFunctionWrapper.java new file mode 100644 index 0000000000..897d9f8d81 --- /dev/null +++ b/language-adaptors/rxjava-dynamic/src/main/java/rx/lang/dynamic/DynamicFunctionWrapper.java @@ -0,0 +1,57 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.dynamic; + +import rx.util.functions.Func0; +import rx.util.functions.Func1; +import rx.util.functions.Func2; +import rx.util.functions.Func3; +import rx.util.functions.Func4; +import rx.util.functions.FunctionLanguageAdaptor; +import rx.util.functions.Functions; + +public class DynamicFunctionWrapper implements Func0, Func1, Func2, Func3, Func4 { + private Object object; + + public DynamicFunctionWrapper(Object object) { + this.object = object; + } + + @Override + public R call() { + return (R) Functions.from(object).call(); + } + + @Override + public R call(T1 t1) { + return (R) Functions.from(object).call(t1); + } + + @Override + public R call(T1 t1, T2 t2) { + return (R) Functions.from(object).call(t1, t2); + } + + @Override + public R call(T1 t1, T2 t2, T3 t3) { + return (R) Functions.from(object).call(t1, t2, t3); + } + + @Override + public R call(T1 t1, T2 t2, T3 t3, T4 t4) { + return (R) Functions.from(object).call(t1, t2, t3, t4); + } +} \ No newline at end of file diff --git a/language-adaptors/rxjava-groovy/build.gradle b/language-adaptors/rxjava-groovy/build.gradle index 91c060193d..fedb8bf395 100644 --- a/language-adaptors/rxjava-groovy/build.gradle +++ b/language-adaptors/rxjava-groovy/build.gradle @@ -1,48 +1,41 @@ -apply plugin: 'java' apply plugin: 'groovy' -apply plugin: 'eclipse' -apply plugin: 'idea' -apply plugin: 'osgi' dependencies { + core project(':rxjava-core') compile project(':rxjava-core') + compile project(':language-adaptors:codegen') groovy 'org.codehaus.groovy:groovy-all:2.+' provided 'junit:junit-dep:4.10' provided 'org.mockito:mockito-core:1.8.5' } -// include /src/examples folder -sourceSets { - examples +task createAdaptedObservable(type: JavaExec) { + main = 'rx.codegen.ClassPathBasedRunner' + classpath = sourceSets.main.runtimeClasspath + args = ["Groovy", outputDir] + + inputs.files(sourceSets.main.runtimeClasspath) + outputs.dir(outputDir) } -// make 'examples' use the same classpath -configurations { - examplesCompile.extendsFrom compile - examplesRuntime.extendsFrom runtime +tasks.test { + dependsOn(createAdaptedObservable) + + //Reorders the classpath so that the newly-create Observables win + classpath = createAdaptedObservable.outputs.files + sourceSets.test.runtimeClasspath } -// include 'examples' in build task -build.dependsOn examplesClasses +tasks.jar { + dependsOn(createAdaptedObservable) -eclipse { - classpath { - // include 'provided' dependencies on the classpath - plusConfigurations += configurations.provided - - downloadSources = true - downloadJavadoc = true + from (zipTree(configurations.core.singleFile)) { + exclude "rx/Observable.class" + exclude "rx/observables/BlockingObservable.class" } -} + from(outputDir) -idea { - module { - // include 'provided' dependencies on the classpath - scopes.PROVIDED.plus += configurations.provided - } -} + exclude('**/*$UnitTest*') -jar { manifest { name = 'rxjava-groovy' instruction 'Bundle-Vendor', 'Netflix' @@ -50,4 +43,4 @@ jar { instruction 'Import-Package', '!org.junit,!junit.framework,!org.mockito.*,*' instruction 'Fragment-Host', 'com.netflix.rxjava.core' } -} \ No newline at end of file +} diff --git a/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyActionWrapper.java b/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyActionWrapper.java new file mode 100644 index 0000000000..a831a0ed42 --- /dev/null +++ b/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyActionWrapper.java @@ -0,0 +1,39 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.groovy; + +import rx.util.functions.Action0; +import rx.util.functions.Action1; + +import groovy.lang.Closure; + +public class GroovyActionWrapper implements Action0, Action1 { + private Closure closure; + + public GroovyActionWrapper(Closure closure) { + this.closure = closure; + } + + @Override + public void call() { + closure.call(); + } + + @Override + public void call(T1 t1) { + closure.call(t1); + } +} \ No newline at end of file diff --git a/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyAdaptor.java b/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyAdaptor.java index 70cef9c18e..530a7d2d63 100644 --- a/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyAdaptor.java +++ b/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyAdaptor.java @@ -15,17 +15,36 @@ */ package rx.lang.groovy; -import groovy.lang.Closure; +import java.util.HashMap; +import java.util.Map; + import rx.util.functions.FunctionLanguageAdaptor; +import groovy.lang.Closure; + +import java.util.HashSet; +import java.util.Set; + public class GroovyAdaptor implements FunctionLanguageAdaptor { @Override - public Object call(Object function, Object[] args) { - return ((Closure) function).call(args); + public Map, Class> getFunctionClassRewritingMap() { + Map, Class> m = new HashMap, Class>(); + m.put(Closure.class, GroovyFunctionWrapper.class); + return m; } - public Class[] getFunctionClass() { - return new Class[] { Closure.class }; + @Override + public Map, Class> getActionClassRewritingMap() { + Map, Class> m = new HashMap, Class>(); + m.put(Closure.class, GroovyActionWrapper.class); + return m; + } + + @Override + public Set> getAllClassesToRewrite() { + Set> groovyClasses = new HashSet>(); + groovyClasses.add(Closure.class); + return groovyClasses; } } diff --git a/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyFunctionWrapper.java b/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyFunctionWrapper.java new file mode 100644 index 0000000000..1b038fd1ba --- /dev/null +++ b/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyFunctionWrapper.java @@ -0,0 +1,58 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.groovy; + +import rx.util.functions.Func0; +import rx.util.functions.Func1; +import rx.util.functions.Func2; +import rx.util.functions.Func3; +import rx.util.functions.Func4; +import rx.util.functions.FunctionLanguageAdaptor; + +import groovy.lang.Closure; + +public class GroovyFunctionWrapper implements Func0, Func1, Func2, Func3, Func4 { + private Closure closure; + + public GroovyFunctionWrapper(Closure closure) { + this.closure = closure; + } + + @Override + public R call() { + return (R) closure.call(); + } + + @Override + public R call(T1 t1) { + return (R) closure.call(t1); + } + + @Override + public R call(T1 t1, T2 t2) { + return (R) closure.call(t1, t2); + } + + @Override + public R call(T1 t1, T2 t2, T3 t3) { + return (R) closure.call(t1, t2, t3); + } + + @Override + public R call(T1 t1, T2 t2, T3 t3, T4 t4) { + return (R) closure.call(t1, t2, t3, t4); + } +} \ No newline at end of file diff --git a/language-adaptors/rxjava-jruby/build.gradle b/language-adaptors/rxjava-jruby/build.gradle index cf7d9533e2..f4aaf194cf 100644 --- a/language-adaptors/rxjava-jruby/build.gradle +++ b/language-adaptors/rxjava-jruby/build.gradle @@ -1,33 +1,39 @@ -apply plugin: 'java' -apply plugin: 'eclipse' -apply plugin: 'idea' -apply plugin: 'osgi' - dependencies { + core project(':rxjava-core') compile project(':rxjava-core') - provided 'org.jruby:jruby:1.6+' + compile project(':language-adaptors:codegen') + compile 'org.jruby:jruby:1.6+' provided 'junit:junit-dep:4.10' provided 'org.mockito:mockito-core:1.8.5' } -eclipse { - classpath { - // include 'provided' dependencies on the classpath - plusConfigurations += configurations.provided +task createAdaptedObservable(type: JavaExec) { + main = 'rx.codegen.ClassPathBasedRunner' + classpath = sourceSets.main.runtimeClasspath + args = ["JRuby", outputDir] - downloadSources = true - downloadJavadoc = true - } + inputs.files(sourceSets.main.runtimeClasspath) + outputs.dir(outputDir) } -idea { - module { - // include 'provided' dependencies on the classpath - scopes.PROVIDED.plus += configurations.provided - } +tasks.test { + dependsOn(createAdaptedObservable) + + //Reorders the classpath so that the newly-create Observables win + classpath = createAdaptedObservable.outputs.files + sourceSets.test.runtimeClasspath } -jar { +tasks.jar { + dependsOn(createAdaptedObservable) + + from (zipTree(configurations.core.singleFile)) { + exclude "rx/Observable.class" + exclude "rx/observables/BlockingObservable.class" + } + from(outputDir) + + exclude('**/*$UnitTest*') + manifest { name = 'rxjava-jruby' instruction 'Bundle-Vendor', 'Netflix' diff --git a/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyActionWrapper.java b/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyActionWrapper.java new file mode 100644 index 0000000000..ada202eaf1 --- /dev/null +++ b/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyActionWrapper.java @@ -0,0 +1,48 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.jruby; + +import org.jruby.Ruby; +import org.jruby.RubyProc; +import org.jruby.javasupport.JavaEmbedUtils; +import org.jruby.runtime.builtin.IRubyObject; + +import rx.util.functions.Action0; +import rx.util.functions.Action1; +import rx.util.functions.FunctionLanguageAdaptor; + +public class JRubyActionWrapper implements Action0, Action1 { + private RubyProc proc; + + public JRubyActionWrapper(RubyProc proc) { + this.proc = proc; + } + + @Override + public void call() { + Ruby ruby = proc.getRuntime(); + IRubyObject[] rubyArgs = new IRubyObject[0]; + proc.getBlock().call(ruby.getCurrentContext(), rubyArgs); + } + + @Override + public void call(T1 t1) { + Ruby ruby = proc.getRuntime(); + IRubyObject[] rubyArgs = new IRubyObject[1]; + rubyArgs[0] = JavaEmbedUtils.javaToRuby(ruby, t1); + proc.getBlock().call(ruby.getCurrentContext(), rubyArgs); + } +} \ No newline at end of file diff --git a/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyAdaptor.java b/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyAdaptor.java index d66dc16acf..f210d64917 100644 --- a/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyAdaptor.java +++ b/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyAdaptor.java @@ -19,12 +19,13 @@ import static org.mockito.Mockito.*; import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; -import org.jruby.Ruby; import org.jruby.RubyProc; import org.jruby.embed.ScriptingContainer; -import org.jruby.javasupport.JavaEmbedUtils; -import org.jruby.runtime.builtin.IRubyObject; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; @@ -40,19 +41,24 @@ public class JRubyAdaptor implements FunctionLanguageAdaptor { @Override - public Object call(Object function, Object[] args) { - RubyProc rubyProc = ((RubyProc) function); - Ruby ruby = rubyProc.getRuntime(); - IRubyObject rubyArgs[] = new IRubyObject[args.length]; - for (int i = 0; i < args.length; i++) { - rubyArgs[i] = JavaEmbedUtils.javaToRuby(ruby, args[i]); - } - return rubyProc.getBlock().call(ruby.getCurrentContext(), rubyArgs); + public Map, Class> getFunctionClassRewritingMap() { + Map, Class> m = new HashMap, Class>(); + m.put(RubyProc.class, JRubyFunctionWrapper.class); + return m; } @Override - public Class[] getFunctionClass() { - return new Class[] { RubyProc.class }; + public Map, Class> getActionClassRewritingMap() { + Map, Class> m = new HashMap, Class>(); + m.put(RubyProc.class, JRubyActionWrapper.class); + return m; + } + + @Override + public Set> getAllClassesToRewrite() { + Set> classes = new HashSet>(); + classes.add(RubyProc.class); + return classes; } public static class UnitTest { diff --git a/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyFunctionWrapper.java b/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyFunctionWrapper.java new file mode 100644 index 0000000000..c3e78957cb --- /dev/null +++ b/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyFunctionWrapper.java @@ -0,0 +1,70 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.jruby; + +import org.jruby.Ruby; +import org.jruby.RubyProc; +import org.jruby.javasupport.JavaEmbedUtils; +import org.jruby.runtime.builtin.IRubyObject; + +import rx.util.functions.Func0; +import rx.util.functions.Func1; +import rx.util.functions.Func2; +import rx.util.functions.Func3; +import rx.util.functions.Func4; +import rx.util.functions.FunctionLanguageAdaptor; + +public class JRubyFunctionWrapper implements Func0, Func1, Func2, Func3, Func4 { + private RubyProc proc; + + public JRubyFunctionWrapper(RubyProc proc) { + this.proc = proc; + } + + @Override + public R call() { + return (R) callRubyProc(); + } + + @Override + public R call(T1 t1) { + return (R) callRubyProc(t1); + } + + @Override + public R call(T1 t1, T2 t2) { + return (R) callRubyProc(t1, t2); + } + + @Override + public R call(T1 t1, T2 t2, T3 t3) { + return (R) callRubyProc(t1, t2, t3); + } + + @Override + public R call(T1 t1, T2 t2, T3 t3, T4 t4) { + return (R) callRubyProc(t1, t2, t3, t4); + } + + private Object callRubyProc(Object... args) { + Ruby ruby = proc.getRuntime(); + IRubyObject[] rubyArgs = new IRubyObject[args.length]; + for (int i = 0; i < args.length; i++) { + rubyArgs[i] = JavaEmbedUtils.javaToRuby(ruby, args[i]); + } + return (R) proc.getBlock().call(ruby.getCurrentContext(), rubyArgs); + } +} \ No newline at end of file diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/ScalaAdaptor.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/ScalaAdaptor.scala index 12418d516d..d2d2138976 100644 --- a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/ScalaAdaptor.scala +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/ScalaAdaptor.scala @@ -53,14 +53,7 @@ class ScalaAdaptor extends FunctionLanguageAdaptor { classOf[(AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object]) } - def call(function: AnyRef, args: Array[AnyRef]) : Object = { - function match { - case (func: Map[String, _]) => return matchOption(func.get(ON_NEXT), args) - case _ => return matchFunction(function, args) - } - } - - private def matchOption(funcOption: Option[_], args: Array[AnyRef]) : Object = { + private def matchOption(funcOption: Option[_], args: Array[AnyRef]) : Object = { funcOption match { case Some(func: AnyRef) => return matchFunction(func, args) case _ => return None diff --git a/rxjava-core/src/main/java/rx/subscriptions/Subscriptions.java b/rxjava-core/src/main/java/rx/subscriptions/Subscriptions.java index f3f1dd46c7..9ad366f579 100644 --- a/rxjava-core/src/main/java/rx/subscriptions/Subscriptions.java +++ b/rxjava-core/src/main/java/rx/subscriptions/Subscriptions.java @@ -83,23 +83,6 @@ public static CompositeSubscription create(Subscription... subscriptions) { return new CompositeSubscription(subscriptions); } - /** - * A {@link Subscription} implemented via an anonymous function (such as closures from other languages). - * - * @return {@link Subscription} - */ - public static Subscription create(final Object unsubscribe) { - final FuncN f = Functions.from(unsubscribe); - return new Subscription() { - - @Override - public void unsubscribe() { - f.call(); - } - - }; - } - /** * A {@link Subscription} that does nothing when its unsubscribe method is called. */ diff --git a/rxjava-core/src/main/java/rx/util/functions/Action.java b/rxjava-core/src/main/java/rx/util/functions/Action.java new file mode 100644 index 0000000000..c1d43eede6 --- /dev/null +++ b/rxjava-core/src/main/java/rx/util/functions/Action.java @@ -0,0 +1,10 @@ +package rx.util.functions; + +/** + * All Action interfaces extend from this. + *

+ * Marker interface to allow instanceof checks. + */ +public interface Action { + +} diff --git a/rxjava-core/src/main/java/rx/util/functions/Action0.java b/rxjava-core/src/main/java/rx/util/functions/Action0.java index 62d57bd563..832b78dd52 100644 --- a/rxjava-core/src/main/java/rx/util/functions/Action0.java +++ b/rxjava-core/src/main/java/rx/util/functions/Action0.java @@ -15,6 +15,6 @@ */ package rx.util.functions; -public interface Action0 extends Function { +public interface Action0 extends Action { public void call(); } \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/util/functions/Action1.java b/rxjava-core/src/main/java/rx/util/functions/Action1.java index 14fa7ced8c..68c1b804ed 100644 --- a/rxjava-core/src/main/java/rx/util/functions/Action1.java +++ b/rxjava-core/src/main/java/rx/util/functions/Action1.java @@ -15,6 +15,6 @@ */ package rx.util.functions; -public interface Action1 extends Function { +public interface Action1 extends Action { public void call(T1 t1); } \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/util/functions/Action2.java b/rxjava-core/src/main/java/rx/util/functions/Action2.java index 8a17875a9e..ba4826bdca 100644 --- a/rxjava-core/src/main/java/rx/util/functions/Action2.java +++ b/rxjava-core/src/main/java/rx/util/functions/Action2.java @@ -15,6 +15,6 @@ */ package rx.util.functions; -public interface Action2 extends Function { +public interface Action2 extends Action { public void call(T1 t1, T2 t2); } \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/util/functions/Action3.java b/rxjava-core/src/main/java/rx/util/functions/Action3.java index 2b613b621e..3c2b083c6f 100644 --- a/rxjava-core/src/main/java/rx/util/functions/Action3.java +++ b/rxjava-core/src/main/java/rx/util/functions/Action3.java @@ -15,6 +15,6 @@ */ package rx.util.functions; -public interface Action3 extends Function { +public interface Action3 extends Action { public void call(T1 t1, T2 t2, T3 t3); } \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/util/functions/FunctionLanguageAdaptor.java b/rxjava-core/src/main/java/rx/util/functions/FunctionLanguageAdaptor.java index 6ec87f358a..9e471e7329 100644 --- a/rxjava-core/src/main/java/rx/util/functions/FunctionLanguageAdaptor.java +++ b/rxjava-core/src/main/java/rx/util/functions/FunctionLanguageAdaptor.java @@ -15,16 +15,10 @@ */ package rx.util.functions; -public interface FunctionLanguageAdaptor { +import java.util.Map; +import java.util.Set; - /** - * Invoke the function and return the results. - * - * @param function - * @param args - * @return Object results from function execution - */ - Object call(Object function, Object[] args); +public interface FunctionLanguageAdaptor { /** * The Class of the Function that this adaptor serves. @@ -35,5 +29,13 @@ public interface FunctionLanguageAdaptor { * * @return Class[] of classes that this adaptor should be invoked for. */ - public Class[] getFunctionClass(); + //public Class[] getFunctionClass(); + + //TODO MRJ: Add Javadoc + public Map, Class> getActionClassRewritingMap(); + + //TODO MRJ: Add Javadoc + public Map, Class> getFunctionClassRewritingMap(); + + public Set> getAllClassesToRewrite(); } diff --git a/rxjava-core/src/main/java/rx/util/functions/Functions.java b/rxjava-core/src/main/java/rx/util/functions/Functions.java index 4486e7bd26..0581e46e13 100644 --- a/rxjava-core/src/main/java/rx/util/functions/Functions.java +++ b/rxjava-core/src/main/java/rx/util/functions/Functions.java @@ -15,74 +15,12 @@ */ package rx.util.functions; -import java.util.Collection; -import java.util.concurrent.ConcurrentHashMap; - /** - * Allows execution of functions from multiple different languages. - *

- * Language support is provided via implementations of {@link FunctionLanguageAdaptor}. - *

- * This class will dynamically look for known language adaptors on the classpath at startup or new ones can be registered using {@link #registerLanguageAdaptor(Class[], FunctionLanguageAdaptor)}. + * Utility class for getting concrete implementations of Functions/Actions */ public class Functions { - private final static ConcurrentHashMap, FunctionLanguageAdaptor> languageAdaptors = new ConcurrentHashMap, FunctionLanguageAdaptor>(); - - static { - /* optimistically look for supported languages if they are in the classpath */ - loadLanguageAdaptor("Groovy"); - loadLanguageAdaptor("JRuby"); - loadLanguageAdaptor("Clojure"); - loadLanguageAdaptor("Scala"); - // as new languages arise we can add them here but this does not prevent someone from using 'registerLanguageAdaptor' directly - } - - private static boolean loadLanguageAdaptor(String name) { - String className = "rx.lang." + name.toLowerCase() + "." + name + "Adaptor"; - try { - Class c = Class.forName(className); - FunctionLanguageAdaptor a = (FunctionLanguageAdaptor) c.newInstance(); - registerLanguageAdaptor(a.getFunctionClass(), a); - /* - * Using System.err/System.out as this is the only place in the library where we do logging and it's only at startup. - * I don't want to include SL4J/Log4j just for this and no one uses Java Logging. - */ - System.out.println("RxJava => Successfully loaded function language adaptor: " + name + " with path: " + className); - } catch (ClassNotFoundException e) { - System.err.println("RxJava => Could not find function language adaptor: " + name + " with path: " + className); - return false; - } catch (Throwable e) { - System.err.println("RxJava => Failed trying to initialize function language adaptor: " + className); - e.printStackTrace(); - return false; - } - return true; - } - - public static void registerLanguageAdaptor(Class[] functionClasses, FunctionLanguageAdaptor adaptor) { - for (Class functionClass : functionClasses) { - if (functionClass.getPackage().getName().startsWith("java.")) { - throw new IllegalArgumentException("FunctionLanguageAdaptor implementations can not specify java.lang.* classes."); - } - languageAdaptors.put(functionClass, adaptor); - } - } - - public static void removeLanguageAdaptor(Class functionClass) { - languageAdaptors.remove(functionClass); - } - - public static Collection getRegisteredLanguageAdaptors() { - return languageAdaptors.values(); - } - - /** - * Utility method for determining the type of closure/function and executing it. - * - * @param function - */ - @SuppressWarnings({ "rawtypes" }) + @SuppressWarnings({ "unchecked", "rawtypes" }) public static FuncN from(final Object function) { if (function == null) { throw new RuntimeException("function is null. Can't send arguments to null function."); @@ -91,131 +29,13 @@ public static FuncN from(final Object function) { /* check for typed Rx Function implementation first */ if (function instanceof Function) { return fromFunction((Function) function); - } else { - /* not an Rx Function so try language adaptors */ - - // check for language adaptor - for (final Class c : languageAdaptors.keySet()) { - if (c.isInstance(function)) { - final FunctionLanguageAdaptor la = languageAdaptors.get(c); - // found the language adaptor so wrap in FuncN and return - return new FuncN() { - - @Override - public Object call(Object... args) { - return la.call(function, args); - } - - }; - } - } - // no language adaptor found + } else if (function instanceof Action) { + return fromAction((Action) function); } - // no support found throw new RuntimeException("Unsupported closure type: " + function.getClass().getSimpleName()); } - // - // @SuppressWarnings("unchecked") - // private static R executionRxFunction(Function function, Object... args) { - // // check Func* classes - // if (function instanceof Func0) { - // Func0 f = (Func0) function; - // if (args.length != 0) { - // throw new RuntimeException("The closure was Func0 and expected no arguments, but we received: " + args.length); - // } - // return (R) f.call(); - // } else if (function instanceof Func1) { - // Func1 f = (Func1) function; - // if (args.length != 1) { - // throw new RuntimeException("The closure was Func1 and expected 1 argument, but we received: " + args.length); - // } - // return f.call(args[0]); - // } else if (function instanceof Func2) { - // Func2 f = (Func2) function; - // if (args.length != 2) { - // throw new RuntimeException("The closure was Func2 and expected 2 arguments, but we received: " + args.length); - // } - // return f.call(args[0], args[1]); - // } else if (function instanceof Func3) { - // Func3 f = (Func3) function; - // if (args.length != 3) { - // throw new RuntimeException("The closure was Func3 and expected 3 arguments, but we received: " + args.length); - // } - // return (R) f.call(args[0], args[1], args[2]); - // } else if (function instanceof Func4) { - // Func4 f = (Func4) function; - // if (args.length != 1) { - // throw new RuntimeException("The closure was Func4 and expected 4 arguments, but we received: " + args.length); - // } - // return f.call(args[0], args[1], args[2], args[3]); - // } else if (function instanceof Func5) { - // Func5 f = (Func5) function; - // if (args.length != 1) { - // throw new RuntimeException("The closure was Func5 and expected 5 arguments, but we received: " + args.length); - // } - // return f.call(args[0], args[1], args[2], args[3], args[4]); - // } else if (function instanceof Func6) { - // Func6 f = (Func6) function; - // if (args.length != 1) { - // throw new RuntimeException("The closure was Func6 and expected 6 arguments, but we received: " + args.length); - // } - // return f.call(args[0], args[1], args[2], args[3], args[4], args[5]); - // } else if (function instanceof Func7) { - // Func7 f = (Func7) function; - // if (args.length != 1) { - // throw new RuntimeException("The closure was Func7 and expected 7 arguments, but we received: " + args.length); - // } - // return f.call(args[0], args[1], args[2], args[3], args[4], args[5], args[6]); - // } else if (function instanceof Func8) { - // Func8 f = (Func8) function; - // if (args.length != 1) { - // throw new RuntimeException("The closure was Func8 and expected 8 arguments, but we received: " + args.length); - // } - // return f.call(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]); - // } else if (function instanceof Func9) { - // Func9 f = (Func9) function; - // if (args.length != 1) { - // throw new RuntimeException("The closure was Func9 and expected 9 arguments, but we received: " + args.length); - // } - // return f.call(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]); - // } else if (function instanceof FuncN) { - // FuncN f = (FuncN) function; - // return f.call(args); - // } else if (function instanceof Action0) { - // Action0 f = (Action0) function; - // if (args.length != 1) { - // throw new RuntimeException("The closure was Action0 and expected 0 arguments, but we received: " + args.length); - // } - // f.call(); - // return null; - // } else if (function instanceof Action1) { - // Action1 f = (Action1) function; - // if (args.length != 1) { - // throw new RuntimeException("The closure was Action1 and expected 1 argument, but we received: " + args.length); - // } - // f.call(args[0]); - // return null; - // } else if (function instanceof Action2) { - // Action2 f = (Action2) function; - // if (args.length != 1) { - // throw new RuntimeException("The closure was Action2 and expected 2 argument, but we received: " + args.length); - // } - // f.call(args[0], args[1]); - // return null; - // } else if (function instanceof Action3) { - // Action3 f = (Action3) function; - // if (args.length != 1) { - // throw new RuntimeException("The closure was Action1 and expected 1 argument, but we received: " + args.length); - // } - // f.call(args[0], args[1], args[2]); - // return null; - // } - // - // throw new RuntimeException("Unknown implementation of Function: " + function.getClass().getSimpleName()); - // } - @SuppressWarnings({ "unchecked", "rawtypes" }) private static FuncN fromFunction(Function function) { // check Func* classes @@ -241,19 +61,27 @@ private static FuncN fromFunction(Function function) { return fromFunc((Func9) function); } else if (function instanceof FuncN) { return (FuncN) function; - } else if (function instanceof Action0) { - return fromAction((Action0) function); - } else if (function instanceof Action1) { - return fromAction((Action1) function); - } else if (function instanceof Action2) { - return fromAction((Action2) function); - } else if (function instanceof Action3) { - return fromAction((Action3) function); } throw new RuntimeException("Unknown implementation of Function: " + function.getClass().getSimpleName()); } + @SuppressWarnings({ "unchecked", "rawtypes" }) + private static FuncN fromAction(Action action) { + // check Action* classes + if (action instanceof Action0) { + return fromAction((Action0) action); + } else if (action instanceof Action1) { + return fromAction((Action1) action); + } else if (action instanceof Action2) { + return fromAction((Action2) action); + } else if (action instanceof Action3) { + return fromAction((Action3) action); + } + + throw new RuntimeException("Unknown implementation of Action: " + action.getClass().getSimpleName()); + } + /** * Convert a function to FuncN to allow heterogeneous handling of functions with different arities. * diff --git a/settings.gradle b/settings.gradle index f07f904404..93c3e179ed 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,4 +4,6 @@ include 'rxjava-core', \ 'language-adaptors:rxjava-jruby', \ 'language-adaptors:rxjava-clojure', \ 'language-adaptors:rxjava-scala', \ +'language-adaptors:rxjava-dynamic', \ +'language-adaptors:codegen', \ 'rxjava-contrib:rxjava-swing' From 08b7757c267d6298992e8be8505009eea9f5fc55 Mon Sep 17 00:00:00 2001 From: Matt Jacobs Date: Wed, 3 Jul 2013 17:20:44 -0700 Subject: [PATCH 3/8] Reworked Scala adaptor to use implicits in RxImplicits, rather than code generation --- language-adaptors/rxjava-scala/build.gradle | 46 +- .../scala/rx/lang/scala/RxImplicits.scala | 592 ++++++++++++++++++ .../scala/rx/lang/scala/ScalaAdaptor.scala | 197 ------ 3 files changed, 609 insertions(+), 226 deletions(-) create mode 100644 language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/RxImplicits.scala delete mode 100644 language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/ScalaAdaptor.scala diff --git a/language-adaptors/rxjava-scala/build.gradle b/language-adaptors/rxjava-scala/build.gradle index 43704248d6..527766691e 100644 --- a/language-adaptors/rxjava-scala/build.gradle +++ b/language-adaptors/rxjava-scala/build.gradle @@ -1,11 +1,9 @@ apply plugin: 'scala' -apply plugin: 'eclipse' -apply plugin: 'idea' -apply plugin: 'osgi' tasks.withType(ScalaCompile) { scalaCompileOptions.fork = true scalaCompileOptions.unchecked = true + scalaCompileOptions.setAdditionalParameters(['-feature']) configure(scalaCompileOptions.forkOptions) { memoryMaximumSize = '1g' @@ -13,46 +11,36 @@ tasks.withType(ScalaCompile) { } } + +sourceSets { + test { + scala { + srcDir 'src/main/scala' + } + } +} + dependencies { // Scala compiler and related tools - scalaTools 'org.scala-lang:scala-compiler:2.10+' - scalaTools 'org.scala-lang:scala-library:2.10+' - provided 'org.scalatest:scalatest_2.10:1.9.1' + compile 'org.scala-lang:scala-compiler:2.10+' + compile 'org.scala-lang:scala-library:2.10+' compile project(':rxjava-core') - provided 'junit:junit-dep:4.10' - provided 'org.mockito:mockito-core:1.8.5' - - testCompile 'org.scalatest:scalatest_2.10:1.9.1' + compile 'junit:junit-dep:4.10' + compile 'org.mockito:mockito-core:1.8.5' + compile 'org.scalatest:scalatest_2.10:1.9.1' } task test(overwrite: true, dependsOn: testClasses) << { ant.taskdef(name: 'scalatest', classname: 'org.scalatest.tools.ScalaTestAntTask', - classpath: sourceSets.test.runtimeClasspath.asPath + classpath: configurations.testRuntime.asPath + ':' + compileScala.destinationDir ) - ant.scalatest(runpath: sourceSets.test.classesDir, + ant.scalatest(runpath: sourceSets.test.output.classesDir, haltonfailure: 'true', fork: 'false') {reporter(type: 'stdout')} } -eclipse { - classpath { - // include 'provided' dependencies on the classpath - plusConfigurations += configurations.provided - - downloadSources = true - downloadJavadoc = true - } -} - -idea { - module { - // include 'provided' dependencies on the classpath - scopes.PROVIDED.plus += configurations.provided - } -} - jar { manifest { name = 'rxjava-scala' diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/RxImplicits.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/RxImplicits.scala new file mode 100644 index 0000000000..67fd4ec0f1 --- /dev/null +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/RxImplicits.scala @@ -0,0 +1,592 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.scala + +object RxImplicits { + import java.{ lang => jlang } + import language.implicitConversions + + import rx.Observable + import rx.observables.BlockingObservable + import rx.util.functions._ + + /** + * Converts 0-arg function to Rx Action0 + */ + implicit def scalaFunction0ProducingUnitToAction0(f: (() => Unit)): Action0 = + new Action0 { + def call(): Unit = f() + } + + /** + * Converts 1-arg function to Rx Action1 + */ + implicit def scalaFunction1ProducingUnitToAction1[A](f: (A => Unit)): Action1[A] = + new Action1[A] { + def call(a: A): Unit = f(a) + } + + /** + * Converts 1-arg predicate to Rx Func1[A, java.lang.Boolean] + */ + implicit def scalaBooleanFunction1ToRxBooleanFunc1[A](f: (A => Boolean)): Func1[A, jlang.Boolean] = + new Func1[A, jlang.Boolean] { + def call(a: A): jlang.Boolean = f(a).booleanValue + } + + /** + * Converts a specific function shape (used in takeWhile) to the equivalent Java types with an Rx Func2 + */ + implicit def convertTakeWhileFuncToRxFunc2[A](f: (A, Int) => Boolean): Func2[A, jlang.Integer, jlang.Boolean] = + new Func2[A, jlang.Integer, jlang.Boolean] { + def call(a: A, b: jlang.Integer): jlang.Boolean = f(a, b).booleanValue + } + + /** + * Converts a function shaped ilke compareTo into the equivalent Rx Func2 + */ + implicit def convertComparisonFuncToRxFunc2[A](f: (A, A) => Int): Func2[A, A, jlang.Integer] = + new Func2[A, A, jlang.Integer] { + def call(a1: A, a2: A): jlang.Integer = f(a1, a2).intValue + } + + /* + * This implicit allows Scala code to use any exception type and still work + * with invariant Func1 interface + */ + implicit def exceptionFunction1ToRxExceptionFunc1[A <: Exception, B](f: (A => B)): Func1[Exception, B] = + new Func1[Exception, B] { + def call(ex: Exception): B = f(ex.asInstanceOf[A]) + } + + /** + * The following implicits convert functions of different arities into the Rx equivalents + */ + implicit def scalaFunction0ToRxFunc0[A](f: () => A): Func0[A] = + new Func0[A] { + def call(): A = f() + } + + implicit def scalaFunction1ToRxFunc1[A, B](f: (A => B)): Func1[A, B] = + new Func1[A, B] { + def call(a: A): B = f(a) + } + + implicit def scalaFunction2ToRxFunc2[A, B, C](f: (A, B) => C): Func2[A, B, C] = + new Func2[A, B, C] { + def call(a: A, b: B) = f(a, b) + } + + implicit def scalaFunction3ToRxFunc3[A, B, C, D](f: (A, B, C) => D): Func3[A, B, C, D] = + new Func3[A, B, C, D] { + def call(a: A, b: B, c: C) = f(a, b, c) + } + + implicit def scalaFunction4ToRxFunc4[A, B, C, D, E](f: (A, B, C, D) => E): Func4[A, B, C, D, E] = + new Func4[A, B, C, D, E] { + def call(a: A, b: B, c: C, d: D) = f(a, b, c, d) + } + + /** + * This implicit class implements all of the methods necessary for including Observables in a + * for-comprehension. Note that return type is always Observable, so that the ScalaObservable + * type never escapes the for-comprehension + */ + implicit class ScalaObservable[A](wrapped: Observable[A]) { + def map[B](f: A => B): Observable[B] = wrapped.map(f) + def flatMap[B](f: A => Observable[B]): Observable[B] = wrapped.mapMany(f) + def foreach(f: A => Unit): Unit = wrapped.toBlockingObservable.forEach(f) + def withFilter(p: A => Boolean): WithFilter = new WithFilter(p) + + class WithFilter(p: A => Boolean) { + def map[B](f: A => B): Observable[B] = wrapped.filter(p).map(f) + def flatMap[B](f: A => Observable[B]): Observable[B] = wrapped.filter(p).flatMap(f) + def foreach(f: A => Unit): Unit = wrapped.filter(p).toBlockingObservable.forEach(f) + def withFilter(p: A => Boolean): Observable[A] = wrapped.filter(p) + } + } +} + +import org.scalatest.junit.JUnitSuite + +class UnitTestSuite extends JUnitSuite { + import rx.lang.scala.RxImplicits._ + + import org.junit.{ Before, Test } + import org.junit.Assert._ + import org.mockito.Matchers.any + import org.mockito.Mockito._ + import org.mockito.{ MockitoAnnotations, Mock } + import rx.{ Notification, Observer, Observable, Subscription } + import rx.observables.GroupedObservable + import collection.mutable.ArrayBuffer + import collection.JavaConverters._ + + @Mock private[this] + val observer: Observer[Any] = null + + @Mock private[this] + val subscription: Subscription = null + + val isOdd = (i: Int) => i % 2 == 1 + val isEven = (i: Int) => i % 2 == 0 + + class ObservableWithException(s: Subscription, values: String*) extends Observable[String] { + var t: Thread = null + + override def subscribe(observer: Observer[String]): Subscription = { + println("ObservableWithException subscribed to ...") + t = new Thread(new Runnable() { + override def run() { + try { + println("running ObservableWithException thread") + values.toList.foreach(v => { + println("ObservableWithException onNext: " + v) + observer.onNext(v) + }) + throw new RuntimeException("Forced Failure") + } catch { + case ex: Exception => observer.onError(ex) + } + } + }) + println("starting ObservableWithException thread") + t.start() + println("done starting ObservableWithException thread") + s + } + } + + @Before def before { + MockitoAnnotations.initMocks(this) + } + + // tests of static methods + + @Test def testSingle { + assertEquals(1, Observable.from(1).toBlockingObservable.single) + } + + @Test def testSinglePredicate { + val found = Observable.from(1, 2, 3).toBlockingObservable.single(isEven) + assertEquals(2, found) + } + + @Test def testSingleOrDefault { + assertEquals(0, Observable.from[Int]().toBlockingObservable.singleOrDefault(0)) + assertEquals(1, Observable.from(1).toBlockingObservable.singleOrDefault(0)) + try { + Observable.from(1, 2, 3).toBlockingObservable.singleOrDefault(0) + fail("Did not catch any exception, expected IllegalStateException") + } catch { + case ex: IllegalStateException => println("Caught expected IllegalStateException") + case ex: Throwable => fail("Caught unexpected exception " + ex.getCause + ", expected IllegalStateException") + } + } + + @Test def testSingleOrDefaultPredicate { + assertEquals(2, Observable.from(1, 2, 3).toBlockingObservable.singleOrDefault(0, isEven)) + assertEquals(0, Observable.from(1, 3).toBlockingObservable.singleOrDefault(0, isEven)) + try { + Observable.from(1, 2, 3).toBlockingObservable.singleOrDefault(0, isOdd) + fail("Did not catch any exception, expected IllegalStateException") + } catch { + case ex: IllegalStateException => println("Caught expected IllegalStateException") + case ex: Throwable => fail("Caught unexpected exception " + ex.getCause + ", expected IllegalStateException") + } + } + + @Test def testFromJavaInterop { + val observable = Observable.from(List(1, 2, 3).asJava) + assertSubscribeReceives(observable)(1, 2, 3) + } + + @Test def testSubscribe { + val observable = Observable.from("1", "2", "3") + assertSubscribeReceives(observable)("1", "2", "3") + } + + //should not compile - adapted from https://gist.github.com/jmhofer/5195589 + /*@Test def testSubscribeOnInt() { + val observable = Observable.from("1", "2", "3") + observable.subscribe((arg: Int) => { + println("testSubscribe: arg = " + arg) + }) + }*/ + + @Test def testDefer { + val lazyObservableFactory = () => Observable.from(1, 2) + val observable = Observable.defer(lazyObservableFactory) + assertSubscribeReceives(observable)(1, 2) + } + + @Test def testJust { + val observable = Observable.just("foo") + assertSubscribeReceives(observable)("foo") + } + + @Test def testMerge { + val observable1 = Observable.from(1, 2, 3) + val observable2 = Observable.from(4, 5, 6) + val observableList = List(observable1, observable2).asJava + val merged = Observable.merge(observableList) + assertSubscribeReceives(merged)(1, 2, 3, 4, 5, 6) + } + + @Test def testFlattenMerge { + val observable = Observable.from(Observable.from(1, 2, 3)) + val merged = Observable.merge(observable) + assertSubscribeReceives(merged)(1, 2, 3) + } + + @Test def testSequenceMerge { + val observable1 = Observable.from(1, 2, 3) + val observable2 = Observable.from(4, 5, 6) + val merged = Observable.merge(observable1, observable2) + assertSubscribeReceives(merged)(1, 2, 3, 4, 5, 6) + } + + @Test def testConcat { + val observable1 = Observable.from(1, 2, 3) + val observable2 = Observable.from(4, 5, 6) + val concatenated = Observable.concat(observable1, observable2) + assertSubscribeReceives(concatenated)(1, 2, 3, 4, 5, 6) + } + + @Test def testSynchronize { + val observable = Observable.from(1, 2, 3) + val synchronized = Observable.synchronize(observable) + assertSubscribeReceives(synchronized)(1, 2, 3) + } + + @Test def testZip3() { + val numbers = Observable.from(1, 2, 3) + val colors = Observable.from("red", "green", "blue") + val names = Observable.from("lion-o", "cheetara", "panthro") + + case class Character(id: Int, color: String, name: String) + + val liono = Character(1, "red", "lion-o") + val cheetara = Character(2, "green", "cheetara") + val panthro = Character(3, "blue", "panthro") + + val characters = Observable.zip(numbers, colors, names, Character.apply _) + assertSubscribeReceives(characters)(liono, cheetara, panthro) + } + + @Test def testZip4() { + val numbers = Observable.from(1, 2, 3) + val colors = Observable.from("red", "green", "blue") + val names = Observable.from("lion-o", "cheetara", "panthro") + val isLeader = Observable.from(true, false, false) + + case class Character(id: Int, color: String, name: String, isLeader: Boolean) + + val liono = Character(1, "red", "lion-o", true) + val cheetara = Character(2, "green", "cheetara", false) + val panthro = Character(3, "blue", "panthro", false) + + val characters = Observable.zip(numbers, colors, names, isLeader, Character.apply _) + assertSubscribeReceives(characters)(liono, cheetara, panthro) + } + + //tests of instance methods + + // missing tests for : takeUntil, groupBy, next, mostRecent + + @Test def testFilter { + val numbers = Observable.from(1, 2, 3, 4, 5, 6, 7, 8, 9) + val observable = numbers.filter(isEven) + assertSubscribeReceives(observable)(2, 4, 6, 8) + } + + @Test def testLast { + val observable = Observable.from(1, 2, 3, 4).toBlockingObservable + assertEquals(4, observable.toBlockingObservable.last) + } + + @Test def testLastPredicate { + val observable = Observable.from(1, 2, 3, 4) + assertEquals(3, observable.toBlockingObservable.last(isOdd)) + } + + @Test def testLastOrDefault { + val observable = Observable.from(1, 2, 3, 4) + assertEquals(4, observable.toBlockingObservable.lastOrDefault(5)) + assertEquals(5, Observable.from[Int]().toBlockingObservable.lastOrDefault(5)) + } + + @Test def testLastOrDefaultPredicate { + val observable = Observable.from(1, 2, 3, 4) + assertEquals(3, observable.toBlockingObservable.lastOrDefault(5, isOdd)) + assertEquals(5, Observable.from[Int]().toBlockingObservable.lastOrDefault(5, isOdd)) + } + + @Test def testMap { + val numbers = Observable.from(1, 2, 3, 4, 5, 6, 7, 8, 9) + val mappedNumbers = ArrayBuffer.empty[Int] + numbers.map((x: Int) => x * x).subscribe((squareVal: Int) => { + mappedNumbers.append(squareVal) + }) + assertEquals(List(1, 4, 9, 16, 25, 36, 49, 64, 81), mappedNumbers.toList) + } + + @Test def testMapMany { + val numbers = Observable.from(1, 2, 3, 4) + val f = (i: Int) => Observable.from(List(i, -i).asJava) + val mappedNumbers = ArrayBuffer.empty[Int] + numbers.mapMany(f).subscribe((i: Int) => { + mappedNumbers.append(i) + }) + assertEquals(List(1, -1, 2, -2, 3, -3, 4, -4), mappedNumbers.toList) + } + + @Test def testMaterialize { + val observable = Observable.from(1, 2, 3, 4) + val expectedNotifications: List[Notification[Int]] = + ((1.to(4).map(i => new Notification(i))) :+ new Notification()).toList + val actualNotifications: ArrayBuffer[Notification[Int]] = ArrayBuffer.empty + observable.materialize.subscribe((n: Notification[Int]) => { + actualNotifications.append(n) + }) + assertEquals(expectedNotifications, actualNotifications.toList) + } + + @Test def testDematerialize { + val notifications: List[Notification[Int]] = + ((1.to(4).map(i => new Notification(i))) :+ new Notification()).toList + val observableNotifications: Observable[Notification[Int]] = + Observable.from(notifications.asJava) + val observable: Observable[Int] = + observableNotifications.dematerialize() + assertSubscribeReceives(observable)(1, 2, 3, 4) + } + + @Test def testOnErrorResumeNextObservableNoError { + val observable = Observable.from(1, 2, 3, 4) + val resumeObservable = Observable.from(5, 6, 7, 8) + val observableWithErrorHandler = observable.onErrorResumeNext(resumeObservable) + assertSubscribeReceives(observableWithErrorHandler)(1, 2, 3, 4) + } + + @Test def testOnErrorResumeNextObservableErrorOccurs { + val observable = new ObservableWithException(subscription, "foo", "bar") + val resumeObservable = Observable.from("a", "b", "c", "d") + val observableWithErrorHandler = observable.onErrorResumeNext(resumeObservable) + observableWithErrorHandler.subscribe(observer.asInstanceOf[Observer[String]]) + + try { + observable.t.join() + } catch { + case ex: InterruptedException => fail(ex.getMessage) + } + + List("foo", "bar", "a", "b", "c", "d").foreach(t => verify(observer, times(1)).onNext(t)) + verify(observer, never()).onError(any(classOf[Exception])) + verify(observer, times(1)).onCompleted() + } + + @Test def testOnErrorResumeNextFuncNoError { + val observable = Observable.from(1, 2, 3, 4) + val resumeFunc = (ex: Throwable) => Observable.from(5, 6, 7, 8) + val observableWithErrorHandler = observable.onErrorResumeNext(resumeFunc) + assertSubscribeReceives(observableWithErrorHandler)(1, 2, 3, 4) + } + + @Test def testOnErrorResumeNextFuncErrorOccurs { + val observable = new ObservableWithException(subscription, "foo", "bar") + val resumeFunc = (ex: Throwable) => Observable.from("a", "b", "c", "d") + val observableWithErrorHandler = observable.onErrorResumeNext(resumeFunc) + observableWithErrorHandler.subscribe(observer.asInstanceOf[Observer[String]]) + + try { + observable.t.join() + } catch { + case ex: InterruptedException => fail(ex.getMessage) + } + + List("foo", "bar", "a", "b", "c", "d").foreach(t => verify(observer, times(1)).onNext(t)) + verify(observer, never()).onError(any(classOf[Exception])) + verify(observer, times(1)).onCompleted() + } + + @Test def testOnErrorReturnFuncNoError { + val observable = Observable.from(1, 2, 3, 4) + val returnFunc = (ex: Throwable) => 87 + val observableWithErrorHandler = observable.onErrorReturn(returnFunc) + assertSubscribeReceives(observableWithErrorHandler)(1, 2, 3, 4) + } + + @Test def testOnErrorReturnFuncErrorOccurs { + val observable = new ObservableWithException(subscription, "foo", "bar") + val returnFunc = (ex: Throwable) => "baz" + val observableWithErrorHandler = observable.onErrorReturn(returnFunc) + observableWithErrorHandler.subscribe(observer.asInstanceOf[Observer[String]]) + + try { + observable.t.join() + } catch { + case ex: InterruptedException => fail(ex.getMessage) + } + + List("foo", "bar", "baz").foreach(t => verify(observer, times(1)).onNext(t)) + verify(observer, never()).onError(any(classOf[Exception])) + verify(observer, times(1)).onCompleted() + } + + @Test def testReduce { + val observable = Observable.from(1, 2, 3, 4) + assertEquals(10, observable.reduce((a: Int, b: Int) => a + b).toBlockingObservable.single) + } + + @Test def testSkip { + val observable = Observable.from(1, 2, 3, 4) + val skipped = observable.skip(2) + assertSubscribeReceives(skipped)(3, 4) + } + + /** + * Both testTake and testTakeWhileWithIndex exposed a bug with unsubscribes not properly propagating. + * observable.take(2) produces onNext(first), onNext(second), and 4 onCompleteds + * it should produce onNext(first), onNext(second), and 1 onCompleted + * + * Switching to Observable.create(OperationTake.take(observable, 2)) works as expected + */ + @Test def testTake { + import rx.operators._ + + val observable = Observable.from(1, 2, 3, 4, 5) + val took = Observable.create(OperationTake.take(observable, 2)) + assertSubscribeReceives(took)(1, 2) + } + + @Test def testTakeWhile { + val observable = Observable.from(1, 3, 5, 6, 7, 9, 11) + val took = observable.takeWhile(isOdd) + assertSubscribeReceives(took)(1, 3, 5) + } + + /*@Test def testTakeWhileWithIndex { + val observable = Observable.from(1, 3, 5, 6, 7, 9, 11, 12, 13, 15, 17) + val took = observable.takeWhileWithIndex((i: Int, idx: Int) => isOdd(i) && idx > 4) + assertSubscribeReceives(took)(9, 11) + }*/ + + @Test def testTakeLast { + val observable = Observable.from(1, 2, 3, 4, 5, 6, 7, 8, 9) + val tookLast = observable.takeLast(3) + assertSubscribeReceives(tookLast)(7, 8, 9) + } + + @Test def testToList { + val observable = Observable.from(1, 2, 3, 4) + val toList = observable.toList + assertSubscribeReceives(toList)(List(1, 2, 3, 4).asJava) + } + + @Test def testToSortedList { + val observable = Observable.from(1, 3, 4, 2) + val toSortedList = observable.toSortedList + assertSubscribeReceives(toSortedList)(List(1, 2, 3, 4).asJava) + } + + @Test def testToArbitrarySortedList { + val observable = Observable.from("a", "aaa", "aaaa", "aa") + val sortByLength = (s1: String, s2: String) => s1.length.compareTo(s2.length) + val toSortedList = observable.toSortedList(sortByLength) + assertSubscribeReceives(toSortedList)(List("a", "aa", "aaa", "aaaa").asJava) + } + + @Test def testToIterable { + val observable = Observable.from(1, 2) + val it = observable.toBlockingObservable.toIterable.iterator + assertTrue(it.hasNext) + assertEquals(1, it.next) + assertTrue(it.hasNext) + assertEquals(2, it.next) + assertFalse(it.hasNext) + } + + @Test def testStartWith { + val observable = Observable.from(1, 2, 3, 4) + val newStart = observable.startWith(-1, 0) + assertSubscribeReceives(newStart)(-1, 0, 1, 2, 3, 4) + } + + @Test def testOneLineForComprehension { + val mappedObservable = for { + i: Int <- Observable.from(1, 2, 3, 4) + } yield i + 1 + assertSubscribeReceives(mappedObservable)(2, 3, 4, 5) + assertFalse(mappedObservable.isInstanceOf[ScalaObservable[_]]) + } + + @Test def testSimpleMultiLineForComprehension { + val flatMappedObservable = for { + i: Int <- Observable.from(1, 2, 3, 4) + j: Int <- Observable.from(1, 10, 100, 1000) + } yield i + j + assertSubscribeReceives(flatMappedObservable)(2, 12, 103, 1004) + assertFalse(flatMappedObservable.isInstanceOf[ScalaObservable[_]]) + } + + @Test def testMultiLineForComprehension { + val doubler = (i: Int) => Observable.from(i, i) + val flatMappedObservable = for { + i: Int <- Observable.from(1, 2, 3, 4) + j: Int <- doubler(i) + } yield j + //can't use assertSubscribeReceives since each number comes in 2x + flatMappedObservable.subscribe(observer.asInstanceOf[Observer[Int]]) + List(1, 2, 3, 4).foreach(i => verify(observer, times(2)).onNext(i)) + verify(observer, never()).onError(any(classOf[Exception])) + verify(observer, times(1)).onCompleted() + assertFalse(flatMappedObservable.isInstanceOf[ScalaObservable[_]]) + } + + @Test def testFilterInForComprehension { + val doubler = (i: Int) => Observable.from(i, i) + val filteredObservable = for { + i: Int <- Observable.from(1, 2, 3, 4) + j: Int <- doubler(i) if isOdd(i) + } yield j + //can't use assertSubscribeReceives since each number comes in 2x + filteredObservable.subscribe(observer.asInstanceOf[Observer[Int]]) + List(1, 3).foreach(i => verify(observer, times(2)).onNext(i)) + verify(observer, never()).onError(any(classOf[Exception])) + verify(observer, times(1)).onCompleted() + assertFalse(filteredObservable.isInstanceOf[ScalaObservable[_]]) + } + + @Test def testForEachForComprehension { + val doubler = (i: Int) => Observable.from(i, i) + val intBuffer = ArrayBuffer.empty[Int] + val forEachComprehension = for { + i: Int <- Observable.from(1, 2, 3, 4) + j: Int <- doubler(i) if isEven(i) + } { + intBuffer.append(j) + } + assertEquals(List(2, 2, 4, 4), intBuffer.toList) + } + + private def assertSubscribeReceives[T](o: Observable[T])(values: T*) = { + o.subscribe(observer.asInstanceOf[Observer[T]]) + values.toList.foreach(t => verify(observer, times(1)).onNext(t)) + verify(observer, never()).onError(any(classOf[Exception])) + verify(observer, times(1)).onCompleted() + } +} diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/ScalaAdaptor.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/ScalaAdaptor.scala deleted file mode 100644 index d2d2138976..0000000000 --- a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/ScalaAdaptor.scala +++ /dev/null @@ -1,197 +0,0 @@ -/** - * Copyright 2013 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package rx.lang.scala - -import rx.util.functions.FunctionLanguageAdaptor -import org.junit.{Assert, Before, Test} -import rx.Observable -import org.scalatest.junit.JUnitSuite -import org.mockito.Mockito._ -import org.mockito.{MockitoAnnotations, Mock} - -import scala.collection.JavaConverters._ -import collection.mutable.ArrayBuffer - -class ScalaAdaptor extends FunctionLanguageAdaptor { - - val ON_NEXT = "onNext" - val ON_ERROR = "onError" - val ON_COMPLETED = "onCompleted" - - def getFunctionClass: Array[Class[_]] = { - return Array(classOf[Map[String, _]], classOf[(AnyRef) => Object], classOf[(AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef) => Object], classOf[(AnyRef, AnyRef, AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) =>Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object], - classOf[(AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object]) - } - - private def matchOption(funcOption: Option[_], args: Array[AnyRef]) : Object = { - funcOption match { - case Some(func: AnyRef) => return matchFunction(func, args) - case _ => return None - } - } - - private def matchFunction(function: AnyRef, args: Array[AnyRef]) : Object = function match { - case (f: ((AnyRef) => Object)) => return f(args(0)) - case (f: ((AnyRef, AnyRef) => Object)) => return f(args(0), args(1)) - case (f: ((AnyRef, AnyRef, AnyRef) => Object)) => return f(args(0), args(1), args(2)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5), args(6)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5), args(6), args(7)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5), args(6), args(7), args(8)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5), args(6), args(7), args(8), args(9)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5), args(6), args(7), args(8), args(9), args(10)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5), args(6), args(7), args(8), args(9), args(10), args(11)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5), args(6), args(7), args(8), args(9), args(10), args(11), args(12)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5), args(6), args(7), args(8), args(9), args(10), args(11), args(12), args(13)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5), args(6), args(7), args(8), args(9), args(10), args(11), args(12), args(13), args(14)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5), args(6), args(7), args(8), args(9), args(10), args(11), args(12), args(13), args(14), args(15)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5), args(6), args(7), args(8), args(9), args(10), args(11), args(12), args(13), args(14), args(15), args(16)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5), args(6), args(7), args(8), args(9), args(10), args(11), args(12), args(13), args(14), args(15), args(16), args(17)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5), args(6), args(7), args(8), args(9), args(10), args(11), args(12), args(13), args(14), args(15), args(16), args(17), args(18)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5), args(6), args(7), args(8), args(9), args(10), args(11), args(12), args(13), args(14), args(15), args(16), args(17), args(18), args(19)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5), args(6), args(7), args(8), args(9), args(10), args(11), args(12), args(13), args(14), args(15), args(16), args(17), args(18), args(19), args(20)) - case (f: ((AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef, AnyRef) => Object)) => - return f(args(0), args(1), args(2), args(3), args(4), args(5), args(6), args(7), args(8), args(9), args(10), args(11), args(12), args(13), args(14), args(15), args(16), args(17), args(18), args(19), args(20), args(21)) - - } -} - -class UnitTestSuite extends JUnitSuite { - @Mock private[this] - val assertion: ScriptAssertion = null - - @Before def before { - MockitoAnnotations.initMocks(this) - } - - @Test def testTake() { - Observable.from("1", "2", "3").take(1).subscribe(Map( - "onNext" -> ((callback: String) => { - print("testTake: callback = " + callback) - assertion.received(callback) - }) - )) - verify(assertion, times(1)).received("1") - } - - @Test def testClosureVersusMap() { - // using closure - Observable.from("1", "2", "3") - .take(2) - .subscribe((callback: String) => { - println(callback) - }) - - // using Map of closures - Observable.from("1", "2", "3") - .take(2) - .subscribe(Map( - "onNext" -> ((callback: String) => { - println(callback) - }))) - } - - @Test def testFilterWithToList() { - val numbers = Observable.from[Int](1, 2, 3, 4, 5, 6, 7, 8, 9) - numbers.filter((x: Int) => 0 == (x % 2)).toList().subscribe( - (callback: java.util.List[Int]) => { - val lst = callback.asScala.toList - println("filter onNext -> got " + lst) - assertion.received(lst) - } - ) - verify(assertion, times(1)).received(List(2,4,6,8)) - } - - @Test def testTakeLast() { - val numbers = Observable.from[Int](1, 2, 3, 4, 5, 6, 7, 8, 9) - numbers.takeLast(1).subscribe((callback: Int) => { - println("testTakeLast: onNext -> got " + callback) - assertion.received(callback) - }) - verify(assertion, times(1)).received(9) - } - - @Test def testMap() { - val numbers = Observable.from(1, 2, 3, 4, 5, 6, 7, 8, 9) - val mappedNumbers = new ArrayBuffer[Int]() - numbers.map(((x: Int)=> { x * x })).subscribe(((squareVal: Int) => { - println("square is " + squareVal ) - mappedNumbers += squareVal - })) - Assert.assertEquals(List(1,4,9,16,25,36,49,64,81), mappedNumbers.toList) - - } - - @Test def testZip() { - val numbers = Observable.from(1, 2, 3) - val colors = Observable.from("red", "green", "blue") - val characters = Observable.from("lion-o", "cheetara", "panthro") - - Observable.zip(numbers.toList, colors.toList, characters.toList, ((n: java.util.List[Int], c: java.util.List[String], t: java.util.List[String]) => { Map( - "numbers" -> n, - "colors" -> c, - "thundercats" -> t - )})).subscribe((m: Map[String, _]) => { - println("zipped map is " + m.toString()) - }) - - - } - - trait ScriptAssertion { - def error(ex: Exception) - - def received(obj: Any) - } -} From 6d15ac640d24c934ccf6819216ff512c5ac015e5 Mon Sep 17 00:00:00 2001 From: Matt Jacobs Date: Wed, 3 Jul 2013 23:02:15 -0700 Subject: [PATCH 4/8] Miscellaneous cleanup: - Added license to files I touched and moved Gradle config around - Bugfix to codegeneration - explicitly call a static/instance method in body - Remove '? super' generic definitions -- I couldn't get these to work once we removed the Object overloads that was apparently being invoked instead of the staticly typed method so removing them. If someone else can get these generics working then please submit a new pull request. - Removing zip FuncN overloads - they conflict with dynamic languages -- These methods will need different names as they have the same argument count so break with dynamic overloads once we do static handling of dynamic languages. --- build.gradle | 4 +- language-adaptors/codegen/build.gradle | 19 +--- .../java/rx/codegen/ClassPathBasedRunner.java | 16 ++++ .../main/java/rx/codegen/CodeGenerator.java | 27 +++++- language-adaptors/rxjava-clojure/build.gradle | 7 +- language-adaptors/rxjava-dynamic/build.gradle | 8 +- language-adaptors/rxjava-groovy/build.gradle | 7 +- language-adaptors/rxjava-jruby/build.gradle | 8 +- language-adaptors/rxjava-scala/build.gradle | 2 +- rxjava-contrib/rxjava-swing/build.gradle | 22 +---- .../java/rx/observables/SwingObservable.java | 2 + rxjava-core/build.gradle | 20 ----- rxjava-core/src/main/java/rx/Observable.java | 89 ------------------- .../rx/operators/OperationCombineLatest.java | 22 ++--- .../main/java/rx/util/functions/Action.java | 16 ++++ .../main/java/rx/util/functions/Function.java | 18 +++- 16 files changed, 111 insertions(+), 176 deletions(-) diff --git a/build.gradle b/build.gradle index 85d83ea12d..29b55f5c1a 100644 --- a/build.gradle +++ b/build.gradle @@ -19,12 +19,10 @@ subprojects { apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'idea' - apply plugin: 'osgi' group = "com.netflix.${githubProjectName}" - ext.coreJarDir = new File(rootDir, "/rxjava-core/build/libs").getCanonicalPath() - ext.outputDir = file("build/rewritten_classes") + ext.codeGenOutputDir = file("build/rewritten_classes") // make 'examples' use the same classpath configurations { diff --git a/language-adaptors/codegen/build.gradle b/language-adaptors/codegen/build.gradle index 7967131270..1541b10429 100644 --- a/language-adaptors/codegen/build.gradle +++ b/language-adaptors/codegen/build.gradle @@ -11,24 +11,9 @@ dependencies { provided 'junit:junit:4.10' } -eclipse { - classpath { - // include 'provided' dependencies on the classpath - plusConfigurations += configurations.provided - - downloadSources = true - downloadJavadoc = true - } -} - -idea { - module { - // include 'provided' dependencies on the classpath - scopes.PROVIDED.plus += configurations.provided - } -} - jar { + exclude('**/*$UnitTest*') + manifest { name = 'rxjava-codegen' instruction 'Bundle-Vendor', 'Netflix' diff --git a/language-adaptors/codegen/src/main/java/rx/codegen/ClassPathBasedRunner.java b/language-adaptors/codegen/src/main/java/rx/codegen/ClassPathBasedRunner.java index 5630724abd..ace0a316d2 100644 --- a/language-adaptors/codegen/src/main/java/rx/codegen/ClassPathBasedRunner.java +++ b/language-adaptors/codegen/src/main/java/rx/codegen/ClassPathBasedRunner.java @@ -1,3 +1,19 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package rx.codegen; import java.io.File; diff --git a/language-adaptors/codegen/src/main/java/rx/codegen/CodeGenerator.java b/language-adaptors/codegen/src/main/java/rx/codegen/CodeGenerator.java index 02d17d793d..ed737e4999 100644 --- a/language-adaptors/codegen/src/main/java/rx/codegen/CodeGenerator.java +++ b/language-adaptors/codegen/src/main/java/rx/codegen/CodeGenerator.java @@ -1,3 +1,18 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package rx.codegen; import java.io.File; @@ -8,7 +23,9 @@ import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; +import javassist.Modifier; import javassist.NotFoundException; + import rx.util.functions.Action; import rx.util.functions.Action0; import rx.util.functions.Action1; @@ -159,6 +176,11 @@ private static List getRewrittenMethods(CtClass clazz, CtMethod method List argumentList = new ArrayList(); StringBuffer newBody = new StringBuffer(); newBody.append("{ return "); + if (Modifier.isStatic(method.getModifiers())) { + newBody.append(clazz.getName() + "."); + } else { + newBody.append("this."); + } newBody.append(method.getName()); newBody.append("("); for (int i = 0; i < method.getParameterTypes().length; i++) { @@ -174,8 +196,9 @@ private static List getRewrittenMethods(CtClass clazz, CtMethod method newBody.append(makeArgList(argumentList)); newBody.append(")"); newBody.append(";}"); - System.out.println(method.getReturnType().getName() + " " + method.getName() + "(" + initialArgString + ") --> " + newMethod.getReturnType().getName() + " " + newMethod.getName() + "(" + finalArgString + ")"); - System.out.println(" " + newBody.toString()); + //Uncomment these to see all of the rewritten methods + //System.out.println(method.getReturnType().getName() + " " + method.getName() + "(" + initialArgString + ") --> " + newMethod.getReturnType().getName() + " " + newMethod.getName() + "(" + finalArgString + ")"); + //System.out.println(" " + newBody.toString()); newMethod.setBody(newBody.toString()); newMethods.add(newMethod); } diff --git a/language-adaptors/rxjava-clojure/build.gradle b/language-adaptors/rxjava-clojure/build.gradle index 78cc056b41..6fb4ea8c30 100644 --- a/language-adaptors/rxjava-clojure/build.gradle +++ b/language-adaptors/rxjava-clojure/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'clojure' +apply plugin: 'osgi' dependencies { core project(':rxjava-core') @@ -37,10 +38,10 @@ eclipse { task createAdaptedObservable(type: JavaExec) { main = 'rx.codegen.ClassPathBasedRunner' classpath = sourceSets.main.runtimeClasspath - args = ["Clojure", outputDir] + args = ["Clojure", codeGenOutputDir] inputs.files(sourceSets.main.runtimeClasspath) - outputs.dir(outputDir) + outputs.dir(codeGenOutputDir) } tasks.test { @@ -57,7 +58,7 @@ tasks.jar { exclude "rx/Observable.class" exclude "rx/observables/BlockingObservable.class" } - from(outputDir) + from(codeGenOutputDir) exclude('**/*$UnitTest*') diff --git a/language-adaptors/rxjava-dynamic/build.gradle b/language-adaptors/rxjava-dynamic/build.gradle index 570142943f..18d03a8d9c 100644 --- a/language-adaptors/rxjava-dynamic/build.gradle +++ b/language-adaptors/rxjava-dynamic/build.gradle @@ -1,3 +1,5 @@ +apply plugin: 'osgi' + dependencies { core project(':rxjava-core') compile project(':rxjava-core') @@ -9,10 +11,10 @@ dependencies { task createAdaptedObservable(type: JavaExec) { main = 'rx.codegen.ClassPathBasedRunner' classpath = sourceSets.main.runtimeClasspath - args = ["Dynamic", outputDir] + args = ["Dynamic", codeGenOutputDir] inputs.files(sourceSets.main.runtimeClasspath) - outputs.dir(outputDir) + outputs.dir(codeGenOutputDir) } tasks.test { @@ -29,7 +31,7 @@ tasks.jar { exclude "rx/Observable.class" exclude "rx/observables/BlockingObservable.class" } - from(outputDir) + from(codeGenOutputDir) exclude('**/*$UnitTest*') diff --git a/language-adaptors/rxjava-groovy/build.gradle b/language-adaptors/rxjava-groovy/build.gradle index fedb8bf395..edb9cb30ea 100644 --- a/language-adaptors/rxjava-groovy/build.gradle +++ b/language-adaptors/rxjava-groovy/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'groovy' +apply plugin: 'osgi' dependencies { core project(':rxjava-core') @@ -12,10 +13,10 @@ dependencies { task createAdaptedObservable(type: JavaExec) { main = 'rx.codegen.ClassPathBasedRunner' classpath = sourceSets.main.runtimeClasspath - args = ["Groovy", outputDir] + args = ["Groovy", codeGenOutputDir] inputs.files(sourceSets.main.runtimeClasspath) - outputs.dir(outputDir) + outputs.dir(codeGenOutputDir) } tasks.test { @@ -32,7 +33,7 @@ tasks.jar { exclude "rx/Observable.class" exclude "rx/observables/BlockingObservable.class" } - from(outputDir) + from(codeGenOutputDir) exclude('**/*$UnitTest*') diff --git a/language-adaptors/rxjava-jruby/build.gradle b/language-adaptors/rxjava-jruby/build.gradle index f4aaf194cf..d7a0d82d39 100644 --- a/language-adaptors/rxjava-jruby/build.gradle +++ b/language-adaptors/rxjava-jruby/build.gradle @@ -1,3 +1,5 @@ +apply plugin: 'osgi' + dependencies { core project(':rxjava-core') compile project(':rxjava-core') @@ -10,10 +12,10 @@ dependencies { task createAdaptedObservable(type: JavaExec) { main = 'rx.codegen.ClassPathBasedRunner' classpath = sourceSets.main.runtimeClasspath - args = ["JRuby", outputDir] + args = ["JRuby", codeGenOutputDir] inputs.files(sourceSets.main.runtimeClasspath) - outputs.dir(outputDir) + outputs.dir(codeGenOutputDir) } tasks.test { @@ -30,7 +32,7 @@ tasks.jar { exclude "rx/Observable.class" exclude "rx/observables/BlockingObservable.class" } - from(outputDir) + from(codeGenOutputDir) exclude('**/*$UnitTest*') diff --git a/language-adaptors/rxjava-scala/build.gradle b/language-adaptors/rxjava-scala/build.gradle index 527766691e..be61f1d846 100644 --- a/language-adaptors/rxjava-scala/build.gradle +++ b/language-adaptors/rxjava-scala/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'scala' +apply plugin: 'osgi' tasks.withType(ScalaCompile) { scalaCompileOptions.fork = true @@ -11,7 +12,6 @@ tasks.withType(ScalaCompile) { } } - sourceSets { test { scala { diff --git a/rxjava-contrib/rxjava-swing/build.gradle b/rxjava-contrib/rxjava-swing/build.gradle index 986f7ca6b9..0076915eee 100644 --- a/rxjava-contrib/rxjava-swing/build.gradle +++ b/rxjava-contrib/rxjava-swing/build.gradle @@ -1,6 +1,3 @@ -apply plugin: 'java' -apply plugin: 'eclipse' -apply plugin: 'idea' apply plugin: 'osgi' sourceCompatibility = JavaVersion.VERSION_1_6 @@ -12,23 +9,6 @@ dependencies { provided 'org.mockito:mockito-core:1.8.5' } -eclipse { - classpath { - // include 'provided' dependencies on the classpath - plusConfigurations += configurations.provided - - downloadSources = true - downloadJavadoc = true - } -} - -idea { - module { - // include 'provided' dependencies on the classpath - scopes.PROVIDED.plus += configurations.provided - } -} - javadoc { options { doclet = "org.benjchristensen.doclet.DocletExclude" @@ -40,6 +20,8 @@ javadoc { } jar { + exclude('**/*$UnitTest*') + manifest { name = 'rxjava-swing' instruction 'Bundle-Vendor', 'Netflix' diff --git a/rxjava-contrib/rxjava-swing/src/main/java/rx/observables/SwingObservable.java b/rxjava-contrib/rxjava-swing/src/main/java/rx/observables/SwingObservable.java index a2facde800..fa9ecd3095 100644 --- a/rxjava-contrib/rxjava-swing/src/main/java/rx/observables/SwingObservable.java +++ b/rxjava-contrib/rxjava-swing/src/main/java/rx/observables/SwingObservable.java @@ -15,6 +15,8 @@ */ package rx.observables; +import static rx.Observable.filter; + import java.awt.Component; import java.awt.Dimension; import java.awt.event.ActionEvent; diff --git a/rxjava-core/build.gradle b/rxjava-core/build.gradle index 72a984c88d..39232193e8 100644 --- a/rxjava-core/build.gradle +++ b/rxjava-core/build.gradle @@ -11,23 +11,6 @@ dependencies { provided 'org.mockito:mockito-core:1.8.5' } -eclipse { - classpath { - // include 'provided' dependencies on the classpath - plusConfigurations += configurations.provided - - downloadSources = true - downloadJavadoc = true - } -} - -idea { - module { - // include 'provided' dependencies on the classpath - scopes.PROVIDED.plus += configurations.provided - } -} - javadoc { // we do not want the org.rx.operations package include exclude '**/operations/**' @@ -48,7 +31,4 @@ jar { instruction 'Bundle-DocURL', 'https://github.com/Netflix/RxJava' instruction 'Import-Package', '!org.junit,!junit.framework,!org.mockito.*,*' } - // commenting out for now as it's breaking the rxjava-scala build and I can't figure out why - // exclude('**/*$UnitTest*') } - diff --git a/rxjava-core/src/main/java/rx/Observable.java b/rxjava-core/src/main/java/rx/Observable.java index 16d9bd4c53..57cfae16ac 100644 --- a/rxjava-core/src/main/java/rx/Observable.java +++ b/rxjava-core/src/main/java/rx/Observable.java @@ -2357,95 +2357,6 @@ public Observable> buffer(long timespan, long timeshift, TimeUnit unit, return buffer(this, timespan, timeshift, unit, scheduler); } - /** - * Returns an Observable that emits the results of a function of your choosing applied to - * combinations of four items emitted, in sequence, by four other Observables. - *

- * {@code zip} applies this function in strict sequence, so the first item emitted by the - * new Observable will be the result of the function applied to the first item emitted by - * all of the Observalbes; the second item emitted by the new Observable will be the result of - * the function applied to the second item emitted by each of those Observables; and so forth. - *

- * The resulting {@code Observable} returned from {@code zip} will invoke - * {@code onNext} as many times as the number of {@code onNext} invokations of the - * source Observable that emits the fewest items. - *

- * - * - * @param ws - * An Observable of source Observables - * @param reduceFunction - * a function that, when applied to an item emitted by each of the source - * Observables, results in an item that will be emitted by the resulting Observable - * @return an Observable that emits the zipped results - */ - public static Observable zip(Observable> ws, final FuncN reduceFunction) { - return ws.toList().mapMany(new Func1>, Observable>() { - @Override - public Observable call(List> wsList) { - return create(OperationZip.zip(wsList, reduceFunction)); - } - }); - } - - /** - * Returns an Observable that emits the results of a function of your choosing applied to - * combinations of four items emitted, in sequence, by four other Observables. - *

- * {@code zip} applies this function in strict sequence, so the first item emitted by the - * new Observable will be the result of the function applied to the first item emitted by - * all of the Observalbes; the second item emitted by the new Observable will be the result of - * the function applied to the second item emitted by each of those Observables; and so forth. - *

- * The resulting {@code Observable} returned from {@code zip} will invoke - * {@code onNext} as many times as the number of {@code onNext} invokations of the - * source Observable that emits the fewest items. - *

- * - * - * @param ws - * A collection of source Observables - * @param reduceFunction - * a function that, when applied to an item emitted by each of the source - * Observables, results in an item that will be emitted by the resulting Observable - * @return an Observable that emits the zipped results - */ - public static Observable zip(Collection> ws, FuncN reduceFunction) { - return create(OperationZip.zip(ws, reduceFunction)); - } - - /** - * Combines the given observables, emitting an event containing an aggregation of the latest values of each of the source observables - * each time an event is received from one of the source observables, where the aggregation is defined by the given function. - *

- * - * - * @param w0 - * The first source observable. - * @param w1 - * The second source observable. - * @param combineFunction - * The aggregation function used to combine the source observable values. - * @return An Observable that combines the source Observables with the given combine function - */ - public static Observable combineLatest(Observable w0, Observable w1, Func2 combineFunction) { - return create(OperationCombineLatest.combineLatest(w0, w1, combineFunction)); - } - - /** - * @see #combineLatest(Observable, Observable, Func2) - */ - public static Observable combineLatest(Observable w0, Observable w1, Observable w2, Func3 combineFunction) { - return create(OperationCombineLatest.combineLatest(w0, w1, w2, combineFunction)); - } - - /** - * @see #combineLatest(Observable, Observable, Func2) - */ - public static Observable combineLatest(Observable w0, Observable w1, Observable w2, Observable w3, Func4 combineFunction) { - return create(OperationCombineLatest.combineLatest(w0, w1, w2, w3, combineFunction)); - } - /** *

* diff --git a/rxjava-core/src/main/java/rx/operators/OperationCombineLatest.java b/rxjava-core/src/main/java/rx/operators/OperationCombineLatest.java index 089a850e99..f11af77898 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationCombineLatest.java +++ b/rxjava-core/src/main/java/rx/operators/OperationCombineLatest.java @@ -62,7 +62,7 @@ public class OperationCombineLatest { * The aggregation function used to combine the source observable values. * @return A function from an observer to a subscription. This can be used to create an observable from. */ - public static Func1, Subscription> combineLatest(Observable w0, Observable w1, Func2 combineLatestFunction) { + public static Func1, Subscription> combineLatest(Observable w0, Observable w1, Func2 combineLatestFunction) { Aggregator a = new Aggregator(Functions.fromFunc(combineLatestFunction)); a.addObserver(new CombineObserver(a, w0)); a.addObserver(new CombineObserver(a, w1)); @@ -72,7 +72,7 @@ public static Func1, Subscription> combineLatest /** * @see #combineLatest(Observable w0, Observable w1, Func2 combineLatestFunction) */ - public static Func1, Subscription> combineLatest(Observable w0, Observable w1, Observable w2, Func3 combineLatestFunction) { + public static Func1, Subscription> combineLatest(Observable w0, Observable w1, Observable w2, Func3 combineLatestFunction) { Aggregator a = new Aggregator(Functions.fromFunc(combineLatestFunction)); a.addObserver(new CombineObserver(a, w0)); a.addObserver(new CombineObserver(a, w1)); @@ -83,7 +83,7 @@ public static Func1, Subscription> combineLa /** * @see #combineLatest(Observable w0, Observable w1, Func2 combineLatestFunction) */ - public static Func1, Subscription> combineLatest(Observable w0, Observable w1, Observable w2, Observable w3, Func4 combineLatestFunction) { + public static Func1, Subscription> combineLatest(Observable w0, Observable w1, Observable w2, Observable w3, Func4 combineLatestFunction) { Aggregator a = new Aggregator(Functions.fromFunc(combineLatestFunction)); a.addObserver(new CombineObserver(a, w0)); a.addObserver(new CombineObserver(a, w1)); @@ -93,11 +93,11 @@ public static Func1, Subscription> combi } private static class CombineObserver implements Observer { - final Observable w; - final Aggregator a; + final Observable w; + final Aggregator a; private Subscription subscription; - public CombineObserver(Aggregator a, Observable w) { + public CombineObserver(Aggregator a, Observable w) { this.a = a; this.w = w; } @@ -130,9 +130,9 @@ public void onNext(T args) { * whenever we have received an event from one of the observables, as soon as each Observable has received * at least one event. */ - private static class Aggregator implements Func1, Subscription> { + private static class Aggregator implements Func1, Subscription> { - private volatile Observer observer; + private volatile Observer observer; private final FuncN combineLatestFunction; private final AtomicBoolean running = new AtomicBoolean(true); @@ -169,7 +169,7 @@ void addObserver(CombineObserver w) { * * @param w The observer that has completed. */ - void complete(CombineObserver w) { + void complete(CombineObserver w) { int completed = numCompleted.incrementAndGet(); // if all CombineObservers are completed, we mark the whole thing as completed if (completed == observers.size()) { @@ -199,7 +199,7 @@ void error(Throwable e) { * @param w * @param arg */ - void next(CombineObserver w, T arg) { + void next(CombineObserver w, T arg) { if (observer == null) { throw new RuntimeException("This shouldn't be running if an Observer isn't registered"); } @@ -232,7 +232,7 @@ void next(CombineObserver w, T arg) { } @Override - public Subscription call(Observer observer) { + public Subscription call(Observer observer) { if (this.observer != null) { throw new IllegalStateException("Only one Observer can subscribe to this Observable."); } diff --git a/rxjava-core/src/main/java/rx/util/functions/Action.java b/rxjava-core/src/main/java/rx/util/functions/Action.java index c1d43eede6..95b3176971 100644 --- a/rxjava-core/src/main/java/rx/util/functions/Action.java +++ b/rxjava-core/src/main/java/rx/util/functions/Action.java @@ -1,3 +1,19 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package rx.util.functions; /** diff --git a/rxjava-core/src/main/java/rx/util/functions/Function.java b/rxjava-core/src/main/java/rx/util/functions/Function.java index cfe85a221f..be3440323b 100644 --- a/rxjava-core/src/main/java/rx/util/functions/Function.java +++ b/rxjava-core/src/main/java/rx/util/functions/Function.java @@ -1,9 +1,25 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package rx.util.functions; /** * All Func and Action interfaces extend from this. *

- * Marker interface to allow isntanceof checks. + * Marker interface to allow instanceof checks. */ public interface Function { From 26d116d3ece5307ee21b22bb0c19a45d05c48fd4 Mon Sep 17 00:00:00 2001 From: Ben Christensen Date: Fri, 5 Jul 2013 16:07:54 -0700 Subject: [PATCH 5/8] Gradle fixes/optimizations - Upgrade to Gradle 1.6 (needed for Scala build to succeed) - s/rxjava-core-x.y.z.jar/rxjava-x.y.z.jar - Properly changed all artifacts of rxjava-core to be rxjava-x.y.z.* - For Groovy/Clojure/JRuby/dynamic, moved dependency on rxjava-core to provided scope, as well as all tests - For Scala, left rxjava-core as compile scope, since rxjava-scala jar doesn't contain Observable (as constructed) - Fixed classpaths for running example code in Clojure and Groovy - rxjava-scala Gradle jar task now includes rxjava-core classfiles - Leaving rxjava-swing UnitTest inner classes in the Jar (in case Scala consumes them) - Fix Maven artifactId for rxjava-core, so that dependencies can correctly build POMs (rxjava-swing in this case) - Shortened names of rxjava-scala implicit methods, per @jmhofer's suggestion --- gradle/wrapper/gradle-wrapper.properties | 2 +- language-adaptors/rxjava-clojure/build.gradle | 16 ++++++++--- language-adaptors/rxjava-dynamic/build.gradle | 6 ++-- language-adaptors/rxjava-groovy/build.gradle | 12 +++++--- language-adaptors/rxjava-jruby/build.gradle | 6 ++-- language-adaptors/rxjava-scala/build.gradle | 28 +++++++++++-------- .../scala/rx/lang/scala/RxImplicits.scala | 27 +++++++++--------- rxjava-contrib/rxjava-swing/build.gradle | 2 -- rxjava-core/build.gradle | 18 +++++++++--- 9 files changed, 72 insertions(+), 45 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e230e2b1c4..2abe81ceda 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=http\://services.gradle.org/distributions/gradle-1.3-bin.zip +distributionUrl=http\://services.gradle.org/distributions/gradle-1.6-bin.zip diff --git a/language-adaptors/rxjava-clojure/build.gradle b/language-adaptors/rxjava-clojure/build.gradle index 6fb4ea8c30..f2f074540e 100644 --- a/language-adaptors/rxjava-clojure/build.gradle +++ b/language-adaptors/rxjava-clojure/build.gradle @@ -3,8 +3,8 @@ apply plugin: 'osgi' dependencies { core project(':rxjava-core') - compile project(':rxjava-core') - compile project(':language-adaptors:codegen') + provided project(':rxjava-core') + provided project(':language-adaptors:codegen') compile 'org.clojure:clojure:1.4.+' compile 'clj-http:clj-http:0.6.4' // https://clojars.org/clj-http provided 'junit:junit-dep:4.10' @@ -35,9 +35,17 @@ eclipse { } } +tasks.clojureTest { + classpath = classpath + configurations.provided +} + +tasks.compileExamplesClojure { + classpath = classpath + configurations.provided +} + task createAdaptedObservable(type: JavaExec) { main = 'rx.codegen.ClassPathBasedRunner' - classpath = sourceSets.main.runtimeClasspath + classpath = sourceSets.main.runtimeClasspath + configurations.provided args = ["Clojure", codeGenOutputDir] inputs.files(sourceSets.main.runtimeClasspath) @@ -48,7 +56,7 @@ tasks.test { dependsOn(createAdaptedObservable) //Reorders the classpath so that the newly-create Observables win - classpath = createAdaptedObservable.outputs.files + sourceSets.test.runtimeClasspath + classpath = createAdaptedObservable.outputs.files + configurations.provided + sourceSets.test.runtimeClasspath } tasks.jar { diff --git a/language-adaptors/rxjava-dynamic/build.gradle b/language-adaptors/rxjava-dynamic/build.gradle index 18d03a8d9c..4b75e1d308 100644 --- a/language-adaptors/rxjava-dynamic/build.gradle +++ b/language-adaptors/rxjava-dynamic/build.gradle @@ -2,15 +2,15 @@ apply plugin: 'osgi' dependencies { core project(':rxjava-core') - compile project(':rxjava-core') - compile project(':language-adaptors:codegen') + provided project(':rxjava-core') + provided project(':language-adaptors:codegen') provided 'junit:junit-dep:4.10' provided 'org.mockito:mockito-core:1.8.5' } task createAdaptedObservable(type: JavaExec) { main = 'rx.codegen.ClassPathBasedRunner' - classpath = sourceSets.main.runtimeClasspath + classpath = sourceSets.main.runtimeClasspath + configurations.provided args = ["Dynamic", codeGenOutputDir] inputs.files(sourceSets.main.runtimeClasspath) diff --git a/language-adaptors/rxjava-groovy/build.gradle b/language-adaptors/rxjava-groovy/build.gradle index edb9cb30ea..b1e6168b26 100644 --- a/language-adaptors/rxjava-groovy/build.gradle +++ b/language-adaptors/rxjava-groovy/build.gradle @@ -3,16 +3,16 @@ apply plugin: 'osgi' dependencies { core project(':rxjava-core') - compile project(':rxjava-core') - compile project(':language-adaptors:codegen') - groovy 'org.codehaus.groovy:groovy-all:2.+' + provided project(':rxjava-core') + provided project(':language-adaptors:codegen') + compile 'org.codehaus.groovy:groovy-all:2.+' provided 'junit:junit-dep:4.10' provided 'org.mockito:mockito-core:1.8.5' } task createAdaptedObservable(type: JavaExec) { main = 'rx.codegen.ClassPathBasedRunner' - classpath = sourceSets.main.runtimeClasspath + classpath = sourceSets.main.runtimeClasspath + configurations.provided args = ["Groovy", codeGenOutputDir] inputs.files(sourceSets.main.runtimeClasspath) @@ -26,6 +26,10 @@ tasks.test { classpath = createAdaptedObservable.outputs.files + sourceSets.test.runtimeClasspath } +tasks.compileExamplesGroovy { + classpath = classpath + configurations.provided +} + tasks.jar { dependsOn(createAdaptedObservable) diff --git a/language-adaptors/rxjava-jruby/build.gradle b/language-adaptors/rxjava-jruby/build.gradle index d7a0d82d39..4359e242ac 100644 --- a/language-adaptors/rxjava-jruby/build.gradle +++ b/language-adaptors/rxjava-jruby/build.gradle @@ -2,8 +2,8 @@ apply plugin: 'osgi' dependencies { core project(':rxjava-core') - compile project(':rxjava-core') - compile project(':language-adaptors:codegen') + provided project(':rxjava-core') + provided project(':language-adaptors:codegen') compile 'org.jruby:jruby:1.6+' provided 'junit:junit-dep:4.10' provided 'org.mockito:mockito-core:1.8.5' @@ -11,7 +11,7 @@ dependencies { task createAdaptedObservable(type: JavaExec) { main = 'rx.codegen.ClassPathBasedRunner' - classpath = sourceSets.main.runtimeClasspath + classpath = sourceSets.main.runtimeClasspath + configurations.provided args = ["JRuby", codeGenOutputDir] inputs.files(sourceSets.main.runtimeClasspath) diff --git a/language-adaptors/rxjava-scala/build.gradle b/language-adaptors/rxjava-scala/build.gradle index be61f1d846..6a4e223439 100644 --- a/language-adaptors/rxjava-scala/build.gradle +++ b/language-adaptors/rxjava-scala/build.gradle @@ -21,27 +21,33 @@ sourceSets { } dependencies { - // Scala compiler and related tools - compile 'org.scala-lang:scala-compiler:2.10+' compile 'org.scala-lang:scala-library:2.10+' - compile project(':rxjava-core') - compile 'junit:junit-dep:4.10' - compile 'org.mockito:mockito-core:1.8.5' - compile 'org.scalatest:scalatest_2.10:1.9.1' + core project(':rxjava-core') + provided project(':rxjava-core') + + provided 'org.scalatest:scalatest_2.10:1.9.1' + provided 'junit:junit-dep:4.10' + provided 'org.mockito:mockito-core:1.8.5' } task test(overwrite: true, dependsOn: testClasses) << { ant.taskdef(name: 'scalatest', - classname: 'org.scalatest.tools.ScalaTestAntTask', - classpath: configurations.testRuntime.asPath + ':' + compileScala.destinationDir - ) + classname: 'org.scalatest.tools.ScalaTestAntTask', + classpath: configurations.testRuntime.asPath + ':' + compileScala.destinationDir + ":" + configurations.provided.asPath + ) ant.scalatest(runpath: sourceSets.test.output.classesDir, - haltonfailure: 'true', - fork: 'false') {reporter(type: 'stdout')} + haltonfailure: 'true', + fork: 'false') {reporter(type: 'stdout')} +} + +tasks.compileScala { + classpath = classpath + configurations.provided } jar { + from (zipTree(configurations.core.singleFile)) + manifest { name = 'rxjava-scala' instruction 'Bundle-Vendor', 'Netflix' diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/RxImplicits.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/RxImplicits.scala index 67fd4ec0f1..8f68f0e149 100644 --- a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/RxImplicits.scala +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/RxImplicits.scala @@ -26,7 +26,7 @@ object RxImplicits { /** * Converts 0-arg function to Rx Action0 */ - implicit def scalaFunction0ProducingUnitToAction0(f: (() => Unit)): Action0 = + implicit def toRxAction0(f: (() => Unit)): Action0 = new Action0 { def call(): Unit = f() } @@ -34,7 +34,7 @@ object RxImplicits { /** * Converts 1-arg function to Rx Action1 */ - implicit def scalaFunction1ProducingUnitToAction1[A](f: (A => Unit)): Action1[A] = + implicit def toRxAction1[A](f: (A => Unit)): Action1[A] = new Action1[A] { def call(a: A): Unit = f(a) } @@ -42,7 +42,7 @@ object RxImplicits { /** * Converts 1-arg predicate to Rx Func1[A, java.lang.Boolean] */ - implicit def scalaBooleanFunction1ToRxBooleanFunc1[A](f: (A => Boolean)): Func1[A, jlang.Boolean] = + implicit def toRxBoolFunc1[A](f: (A => Boolean)): Func1[A, jlang.Boolean] = new Func1[A, jlang.Boolean] { def call(a: A): jlang.Boolean = f(a).booleanValue } @@ -50,7 +50,7 @@ object RxImplicits { /** * Converts a specific function shape (used in takeWhile) to the equivalent Java types with an Rx Func2 */ - implicit def convertTakeWhileFuncToRxFunc2[A](f: (A, Int) => Boolean): Func2[A, jlang.Integer, jlang.Boolean] = + implicit def toRxTakeWhileFunc[A](f: (A, Int) => Boolean): Func2[A, jlang.Integer, jlang.Boolean] = new Func2[A, jlang.Integer, jlang.Boolean] { def call(a: A, b: jlang.Integer): jlang.Boolean = f(a, b).booleanValue } @@ -58,7 +58,7 @@ object RxImplicits { /** * Converts a function shaped ilke compareTo into the equivalent Rx Func2 */ - implicit def convertComparisonFuncToRxFunc2[A](f: (A, A) => Int): Func2[A, A, jlang.Integer] = + implicit def toRxCompareFunc[A](f: (A, A) => Int): Func2[A, A, jlang.Integer] = new Func2[A, A, jlang.Integer] { def call(a1: A, a2: A): jlang.Integer = f(a1, a2).intValue } @@ -67,7 +67,7 @@ object RxImplicits { * This implicit allows Scala code to use any exception type and still work * with invariant Func1 interface */ - implicit def exceptionFunction1ToRxExceptionFunc1[A <: Exception, B](f: (A => B)): Func1[Exception, B] = + implicit def toRxExceptionFunc[A <: Exception, B](f: (A => B)): Func1[Exception, B] = new Func1[Exception, B] { def call(ex: Exception): B = f(ex.asInstanceOf[A]) } @@ -75,34 +75,35 @@ object RxImplicits { /** * The following implicits convert functions of different arities into the Rx equivalents */ - implicit def scalaFunction0ToRxFunc0[A](f: () => A): Func0[A] = + implicit def toRxFunc0[A](f: () => A): Func0[A] = new Func0[A] { def call(): A = f() } - implicit def scalaFunction1ToRxFunc1[A, B](f: (A => B)): Func1[A, B] = + implicit def toRxFunc1[A, B](f: (A => B)): Func1[A, B] = new Func1[A, B] { def call(a: A): B = f(a) } - implicit def scalaFunction2ToRxFunc2[A, B, C](f: (A, B) => C): Func2[A, B, C] = + implicit def toRxFunc2[A, B, C](f: (A, B) => C): Func2[A, B, C] = new Func2[A, B, C] { def call(a: A, b: B) = f(a, b) } - implicit def scalaFunction3ToRxFunc3[A, B, C, D](f: (A, B, C) => D): Func3[A, B, C, D] = + implicit def toRxFunc3[A, B, C, D](f: (A, B, C) => D): Func3[A, B, C, D] = new Func3[A, B, C, D] { def call(a: A, b: B, c: C) = f(a, b, c) } - implicit def scalaFunction4ToRxFunc4[A, B, C, D, E](f: (A, B, C, D) => E): Func4[A, B, C, D, E] = + implicit def toRxFunc4[A, B, C, D, E](f: (A, B, C, D) => E): Func4[A, B, C, D, E] = new Func4[A, B, C, D, E] { def call(a: A, b: B, c: C, d: D) = f(a, b, c, d) } /** - * This implicit class implements all of the methods necessary for including Observables in a - * for-comprehension. Note that return type is always Observable, so that the ScalaObservable + * This implicit class implements all of the methods necessary for + * including Observables in a for-comprehension. + * Note that return type is always Observable, so that the ScalaObservable * type never escapes the for-comprehension */ implicit class ScalaObservable[A](wrapped: Observable[A]) { diff --git a/rxjava-contrib/rxjava-swing/build.gradle b/rxjava-contrib/rxjava-swing/build.gradle index 0076915eee..ea863813a2 100644 --- a/rxjava-contrib/rxjava-swing/build.gradle +++ b/rxjava-contrib/rxjava-swing/build.gradle @@ -20,8 +20,6 @@ javadoc { } jar { - exclude('**/*$UnitTest*') - manifest { name = 'rxjava-swing' instruction 'Bundle-Vendor', 'Netflix' diff --git a/rxjava-core/build.gradle b/rxjava-core/build.gradle index 39232193e8..a2420635f9 100644 --- a/rxjava-core/build.gradle +++ b/rxjava-core/build.gradle @@ -1,6 +1,4 @@ -apply plugin: 'java' -apply plugin: 'eclipse' -apply plugin: 'idea' +apply plugin: 'maven' apply plugin: 'osgi' sourceCompatibility = JavaVersion.VERSION_1_6 @@ -11,6 +9,16 @@ dependencies { provided 'org.mockito:mockito-core:1.8.5' } +uploadArchives { + repositories { + mavenDeployer { + pom { + artifactId = "rxjava" + } + } + } +} + javadoc { // we do not want the org.rx.operations package include exclude '**/operations/**' @@ -25,8 +33,10 @@ javadoc { } jar { + baseName = "rxjava" + manifest { - name = 'rxjava-core' + name = 'rxjava' instruction 'Bundle-Vendor', 'Netflix' instruction 'Bundle-DocURL', 'https://github.com/Netflix/RxJava' instruction 'Import-Package', '!org.junit,!junit.framework,!org.mockito.*,*' From afc81d057b1c543ae9e9751e518490a4a5ded86c Mon Sep 17 00:00:00 2001 From: Matt Jacobs Date: Mon, 12 Aug 2013 09:57:22 -0700 Subject: [PATCH 6/8] Adding support for the special case of subscribe(Map) to code-generation * Broke apart monolithic code generator as well * Changed signature in core to subscribe(Map) for type-safety * Removed rxjava-dynamic module --- build.gradle | 2 +- .../AddSpecializedDynamicMethodRewriter.java | 120 ++++++ .../java/rx/codegen/ClassPathBasedRunner.java | 38 +- .../main/java/rx/codegen/CodeGenerator.java | 299 --------------- .../main/java/rx/codegen/Func1Generator.java | 87 +++++ .../java/rx/codegen/MethodRewriteRequest.java | 54 +++ .../main/java/rx/codegen/MethodRewriter.java | 354 ++++++++++++++++++ .../java/rx/codegen/NoOpMethodRewriter.java | 48 +++ .../java/rx/codegen/ObservableRewriter.java | 94 +++++ .../OneArgSubscribeOnMapMethodRewriter.java | 99 +++++ language-adaptors/rxjava-dynamic/build.gradle | 45 --- .../rx/lang/dynamic/DynamicActionWrapper.java | 38 -- .../java/rx/lang/dynamic/DynamicAdaptor.java | 48 --- .../lang/dynamic/DynamicFunctionWrapper.java | 57 --- .../rx/lang/groovy/GroovyFunctionWrapper.java | 1 + .../rx/lang/groovy/ObservableTests.groovy | 37 ++ .../main/java/rx/lang/jruby/JRubyAdaptor.java | 4 +- .../rx/lang/jruby/JRubyFunctionWrapper.java | 8 +- rxjava-core/src/main/java/rx/Observable.java | 134 ++++++- .../main/java/rx/operators/OperationZip.java | 2 +- .../main/java/rx/util/functions/Action.java | 2 +- .../functions/FunctionLanguageAdaptor.java | 16 +- .../java/rx/util/functions/Functions.java | 20 +- settings.gradle | 1 - 24 files changed, 1078 insertions(+), 530 deletions(-) create mode 100644 language-adaptors/codegen/src/main/java/rx/codegen/AddSpecializedDynamicMethodRewriter.java delete mode 100644 language-adaptors/codegen/src/main/java/rx/codegen/CodeGenerator.java create mode 100644 language-adaptors/codegen/src/main/java/rx/codegen/Func1Generator.java create mode 100644 language-adaptors/codegen/src/main/java/rx/codegen/MethodRewriteRequest.java create mode 100644 language-adaptors/codegen/src/main/java/rx/codegen/MethodRewriter.java create mode 100644 language-adaptors/codegen/src/main/java/rx/codegen/NoOpMethodRewriter.java create mode 100644 language-adaptors/codegen/src/main/java/rx/codegen/ObservableRewriter.java create mode 100644 language-adaptors/codegen/src/main/java/rx/codegen/OneArgSubscribeOnMapMethodRewriter.java delete mode 100644 language-adaptors/rxjava-dynamic/build.gradle delete mode 100644 language-adaptors/rxjava-dynamic/src/main/java/rx/lang/dynamic/DynamicActionWrapper.java delete mode 100644 language-adaptors/rxjava-dynamic/src/main/java/rx/lang/dynamic/DynamicAdaptor.java delete mode 100644 language-adaptors/rxjava-dynamic/src/main/java/rx/lang/dynamic/DynamicFunctionWrapper.java diff --git a/build.gradle b/build.gradle index 29b55f5c1a..e2b41f7123 100644 --- a/build.gradle +++ b/build.gradle @@ -31,7 +31,7 @@ subprojects { examplesRuntime.extendsFrom runtime } - sourceSets.test.java.srcDirs.add('src/main/java') + sourceSets.test.java.srcDir 'src/main/java' tasks.withType(Javadoc).each { it.classpath = sourceSets.main.compileClasspath diff --git a/language-adaptors/codegen/src/main/java/rx/codegen/AddSpecializedDynamicMethodRewriter.java b/language-adaptors/codegen/src/main/java/rx/codegen/AddSpecializedDynamicMethodRewriter.java new file mode 100644 index 0000000000..8df3575d4a --- /dev/null +++ b/language-adaptors/codegen/src/main/java/rx/codegen/AddSpecializedDynamicMethodRewriter.java @@ -0,0 +1,120 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.codegen; + +import java.util.ArrayList; +import java.util.List; + +import javassist.CtClass; +import javassist.CtMethod; +import javassist.Modifier; + +import rx.util.functions.FunctionLanguageAdaptor; + +/** + * An implementation of method rewriting that leaves the rx.Function variant alone and adds dynamic language + * support by adding a new method per native function class. The new methods do nothing more than convert the + * native function class into an rx.Function and forward to the core implementation. + * + * For example, if {@code Observable} has a method Integer foo(String s, Func1 f, Integer i), then the + * rewritten class will add more 'foo's with dynamic language support. + * + * For Groovy (as an example), the new class would contain: + * Integer foo(String s, Func1 f, Integer i); + * Integer foo(String s, Closure f, Integer i) { + * return foo(s, new GroovyFunctionWrapper(f), i); + * } + */ +public class AddSpecializedDynamicMethodRewriter extends MethodRewriter { + + public AddSpecializedDynamicMethodRewriter(CtClass enclosingClass, CtMethod method, FunctionLanguageAdaptor adaptor) { + this.enclosingClass = enclosingClass; + this.initialMethod = method; + this.adaptor = adaptor; + } + + @Override + public boolean needsRewrite() { + return true; + } + + @Override + public boolean isReplacement() { + return false; + } + + @Override + protected List getNewMethodsToRewrite(CtClass[] initialArgTypes) { + List reqs = new ArrayList(); + + try { + for (Class nativeFunctionClass: adaptor.getAllClassesToRewrite()) { + Class functionAdaptorClass = adaptor.getFunctionClassRewritingMap().get(nativeFunctionClass); + Class actionAdaptorClass = adaptor.getActionClassRewritingMap().get(nativeFunctionClass); + + CtClass[] argTypes = initialMethod.getParameterTypes(); + List parameters = new ArrayList(); + + for (CtClass argType : argTypes) { + if (isRxFunctionType(argType) || isRxActionType(argType)) { + // needs conversion + parameters.add(pool.get(nativeFunctionClass.getName())); + } else { + // no conversion, copy through + parameters.add(argType); + } + } + MethodRewriteRequest req = new MethodRewriteRequest(functionAdaptorClass, actionAdaptorClass, parameters); + reqs.add(req); + } + } catch (Exception ex) { + System.out.println("Exception while rewriting method : " + initialMethod.getName()); + } + return reqs; + } + + @Override + protected String getRewrittenMethodBody(CtMethod method, CtClass enclosingClass, MethodRewriteRequest req) { + StringBuffer newBody = new StringBuffer(); + List argumentList = new ArrayList(); + newBody.append("{ return "); + if (Modifier.isStatic(method.getModifiers())) { + newBody.append(enclosingClass.getName() + "."); + } else { + newBody.append("this."); + } + newBody.append(method.getName()); + newBody.append("("); + try { + for (int i = 0; i < method.getParameterTypes().length; i++) { + CtClass argType = method.getParameterTypes()[i]; + if (isRxActionType(argType) && req.getActionAdaptorClass() != null) { + argumentList.add(getAdaptedArg(req.getActionAdaptorClass(), i + 1)); + } else if (isRxFunctionType(argType) && req.getFunctionAdaptorClass() != null) { + argumentList.add(getAdaptedArg(req.getFunctionAdaptorClass(), i + 1)); + } else { + argumentList.add(getUntouchedArg(i + 1)); + } + } + } catch (Exception ex) { + System.out.println("Exception while creating body for dynamic version of : " + initialMethod.getName()); + } + newBody.append(makeArgList(argumentList)); + newBody.append(")"); + newBody.append(";}"); + return newBody.toString(); + } +} \ No newline at end of file diff --git a/language-adaptors/codegen/src/main/java/rx/codegen/ClassPathBasedRunner.java b/language-adaptors/codegen/src/main/java/rx/codegen/ClassPathBasedRunner.java index ace0a316d2..80eeaee38d 100644 --- a/language-adaptors/codegen/src/main/java/rx/codegen/ClassPathBasedRunner.java +++ b/language-adaptors/codegen/src/main/java/rx/codegen/ClassPathBasedRunner.java @@ -13,15 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package rx.codegen; import java.io.File; import java.util.ArrayList; import java.util.List; +import javassist.ClassPool; +import javassist.CtClass; + import rx.util.functions.FunctionLanguageAdaptor; +/** + * Java Executable that performs bytecode rewriting at compile-time. + * It accepts 2 args: dynamic language and dir to store result output classfiles + */ public class ClassPathBasedRunner { public static void main(String[] args) { if (args.length != 2) { @@ -31,17 +37,25 @@ public static void main(String[] args) { } String lang = args[0]; File dir = new File(args[1]); + System.out.println("Using dir : " + dir + " for outputting classfiles"); System.out.println("Looking for Adaptor for : " + lang); String className = "rx.lang." + lang.toLowerCase() + "." + lang + "Adaptor"; try { + ClassPool pool = ClassPool.getDefault(); + Class adaptorClass = Class.forName(className); System.out.println("Found Adaptor : " + adaptorClass); FunctionLanguageAdaptor adaptor = (FunctionLanguageAdaptor) adaptorClass.newInstance(); - CodeGenerator codeGen = new CodeGenerator(); - System.out.println("Using dir : " + dir + " for outputting classfiles"); + Func1Generator func1Generator = new Func1Generator(pool, adaptor); + CtClass dynamicFunc1Class = func1Generator.createDynamicFunc1Class(); + writeClassFile(dynamicFunc1Class, dir); + pool.appendPathList(dir.getCanonicalPath()); + + ObservableRewriter rewriter = new ObservableRewriter(pool, adaptor); for (Class observableClass: getObservableClasses()) { - codeGen.addMethods(observableClass, adaptor, new File(args[1])); + CtClass rewrittenClass = rewriter.addMethods(observableClass); + writeClassFile(rewrittenClass, dir); } } catch (ClassNotFoundException ex) { System.out.println("Did not find adaptor class : " + className); @@ -52,6 +66,22 @@ public static void main(String[] args) { } catch (IllegalAccessException ex) { System.out.println("Access to constructor on : " + className + " failed"); System.exit(1); + } catch (Exception ex) { + System.out.println("Exception : " + ex.getMessage()); + System.exit(1); + } + } + + protected static void writeClassFile(CtClass clazz, File dir) { + try { + System.out.println("Using " + dir.getCanonicalPath() + " for dynamic class file"); + clazz.writeFile(dir.getCanonicalPath()); + } catch (java.io.IOException ioe) { + System.out.println("Could not write classfile to : " + dir.toString()); + System.exit(1); + } catch (javassist.CannotCompileException cce) { + System.out.println("Could not create a valid classfile"); + System.exit(2); } } diff --git a/language-adaptors/codegen/src/main/java/rx/codegen/CodeGenerator.java b/language-adaptors/codegen/src/main/java/rx/codegen/CodeGenerator.java deleted file mode 100644 index ed737e4999..0000000000 --- a/language-adaptors/codegen/src/main/java/rx/codegen/CodeGenerator.java +++ /dev/null @@ -1,299 +0,0 @@ -/** - * Copyright 2013 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package rx.codegen; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -import javassist.ClassPool; -import javassist.CtClass; -import javassist.CtMethod; -import javassist.Modifier; -import javassist.NotFoundException; - -import rx.util.functions.Action; -import rx.util.functions.Action0; -import rx.util.functions.Action1; -import rx.util.functions.Action2; -import rx.util.functions.Action3; -import rx.util.functions.Func0; -import rx.util.functions.Func1; -import rx.util.functions.Func2; -import rx.util.functions.Func3; -import rx.util.functions.Func4; -import rx.util.functions.Func5; -import rx.util.functions.Func6; -import rx.util.functions.Func7; -import rx.util.functions.Func8; -import rx.util.functions.Func9; -import rx.util.functions.Function; -import rx.util.functions.FunctionLanguageAdaptor; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import org.junit.Test; - -public class CodeGenerator { - - static ClassPool pool = ClassPool.getDefault(); - - public void addMethods(Class initialClass, FunctionLanguageAdaptor adaptor, File file) { - Set> nativeFunctionClasses = adaptor.getAllClassesToRewrite(); - System.out.println("Adding dynamic language support to : " + initialClass.getSimpleName()); - for (Class nativeFunctionClass: nativeFunctionClasses) { - System.out.println(" * Adding : " + nativeFunctionClass.getName()); - } - addSupportFor(initialClass, adaptor, file); - } - - private static void addSupportFor(Class observableClass, FunctionLanguageAdaptor adaptor, File file) { - CtClass clazz; - - if (!observableClass.getName().startsWith("rx.")) { - throw new IllegalStateException("Refusing to rewrite a class that is not a core Rx Observable!"); - } - - Set> nativeFunctionClasses = adaptor.getAllClassesToRewrite(); - - try { - clazz = pool.get(observableClass.getName()); - } catch (NotFoundException e) { - throw new RuntimeException("Failed to add language adaptor methods as could not find observable Class named " + observableClass.getName(), e); - } - try { - rewriteMethodsWithRxArgs(clazz, adaptor); - writeClassFile(clazz, file); - } catch (Exception e) { - e.printStackTrace(); - throw new RuntimeException("Failed to add language adaptor methods.", e); - } - } - - private static void rewriteMethodsWithRxArgs(CtClass clazz, FunctionLanguageAdaptor adaptor) throws Exception { - List newMethods = new ArrayList(); - - for (CtMethod method : clazz.getMethods()) { - CtClass[] argTypes = method.getParameterTypes(); - boolean needsRewrite = false; - for (CtClass argType : argTypes) { - if (isRxFunctionType(argType) || isRxActionType(argType)) { - needsRewrite = true; - } - } - if (needsRewrite) { - try { - newMethods.addAll(getRewrittenMethods(clazz, method, adaptor)); - } catch (Exception e) { - e.printStackTrace(); - throw new RuntimeException("Failed to add language adaptor method: " + method.getName(), e); - } - } - } - - for (CtMethod cm: newMethods) { - clazz.addMethod(cm); - } - } - - private static boolean isNativeFunctionType(CtClass type, Set> nativeFunctionClasses) { - for (Class nativeFunctionClass: nativeFunctionClasses) { - if (type.getName().equals(nativeFunctionClass.getName())) { - return true; - } - } - return false; - } - - private static boolean isRxFunctionType(CtClass type) throws Exception { - return implementsInterface(type, Function.class); - } - - private static boolean isRxActionType(CtClass type) throws Exception { - return implementsInterface(type, Action.class); - } - - private static boolean implementsInterface(CtClass type, Class interfaceClass) throws Exception { - // Did I pass in the exact interface? - if (type.getName().equals(interfaceClass.getName())) { - return true; - } - // Do I implement the interface? - for (CtClass implementedInterface : type.getInterfaces()) { - if (implementedInterface.getName().equals(interfaceClass.getName())) { - return true; - } - } - return false; - } - - private static List getRewrittenMethods(CtClass clazz, CtMethod method, FunctionLanguageAdaptor adaptor) throws Exception { - List newMethods = new ArrayList(); - - for (Class nativeFunctionClass: adaptor.getAllClassesToRewrite()) { - Class functionAdaptorClass = adaptor.getFunctionClassRewritingMap().get(nativeFunctionClass); - Class actionAdaptorClass = adaptor.getActionClassRewritingMap().get(nativeFunctionClass); - ArrayList parameters = new ArrayList(); - CtClass[] argTypes = method.getParameterTypes(); - - ArrayList initialArgTypes = new ArrayList(); - ArrayList finalArgTypes = new ArrayList(); - - for (CtClass argType : argTypes) { - initialArgTypes.add(argType.getName()); - if (isRxFunctionType(argType) || isRxActionType(argType)) { - // needs conversion - finalArgTypes.add(nativeFunctionClass.getName()); - parameters.add(pool.get(nativeFunctionClass.getName())); - } else { - // no conversion, copy through - finalArgTypes.add(argType.getName()); - parameters.add(argType); - } - } - - String initialArgString = makeArgList(initialArgTypes); - String finalArgString = makeArgList(finalArgTypes); - - CtClass[] oldParameters = parameters.toArray(new CtClass[parameters.size()]); - CtMethod newMethod = new CtMethod(method.getReturnType(), method.getName(), oldParameters, clazz); - newMethod.setModifiers(method.getModifiers()); - List argumentList = new ArrayList(); - StringBuffer newBody = new StringBuffer(); - newBody.append("{ return "); - if (Modifier.isStatic(method.getModifiers())) { - newBody.append(clazz.getName() + "."); - } else { - newBody.append("this."); - } - newBody.append(method.getName()); - newBody.append("("); - for (int i = 0; i < method.getParameterTypes().length; i++) { - CtClass argType = method.getParameterTypes()[i]; - if (isRxActionType(argType) && actionAdaptorClass != null) { - argumentList.add(getAdaptedArg(actionAdaptorClass, i + 1)); - } else if (isRxFunctionType(argType) && functionAdaptorClass != null) { - argumentList.add(getAdaptedArg(functionAdaptorClass, i + 1)); - } else { - argumentList.add(getUntouchedArg(i + 1)); - } - } - newBody.append(makeArgList(argumentList)); - newBody.append(")"); - newBody.append(";}"); - //Uncomment these to see all of the rewritten methods - //System.out.println(method.getReturnType().getName() + " " + method.getName() + "(" + initialArgString + ") --> " + newMethod.getReturnType().getName() + " " + newMethod.getName() + "(" + finalArgString + ")"); - //System.out.println(" " + newBody.toString()); - newMethod.setBody(newBody.toString()); - newMethods.add(newMethod); - } - return newMethods; - } - - private static String getAdaptedArg(Class adaptorClass, int index) { - StringBuffer buffer = new StringBuffer(); - buffer.append("new "); - buffer.append(adaptorClass.getName()); - buffer.append("("); - buffer.append(getUntouchedArg(index)); - buffer.append(")"); - return buffer.toString(); - } - - private static String getUntouchedArg(int index) { - StringBuffer buffer = new StringBuffer(); - buffer.append("$"); - buffer.append(index); - return buffer.toString(); - } - - private static String makeArgList(List args) { - if (args.size() > 0) { - StringBuffer buffer = new StringBuffer(args.get(0)); - for (String arg: args.subList(1, args.size())) { - buffer.append("," + arg); - } - return buffer.toString(); - } - return ""; - } - - private static void writeClassFile(CtClass clazz, File dir) { - try { - System.out.println("Using " + dir.getCanonicalPath() + " for dynamic class file"); - clazz.writeFile(dir.getCanonicalPath()); - } catch (java.io.IOException ioe) { - System.out.println("Could not write classfile to : " + dir.toString()); - System.exit(1); - } catch (javassist.CannotCompileException cce) { - System.out.println("Could not create a valid classfile"); - System.exit(2); - } - } - - public static class UnitTest { - @Test - public void testIsRxFunctionType() { - try { - assertTrue(isRxFunctionType(pool.get(Function.class.getName()))); - assertTrue(isRxFunctionType(pool.get(Func0.class.getName()))); - assertTrue(isRxFunctionType(pool.get(Func1.class.getName()))); - assertTrue(isRxFunctionType(pool.get(Func2.class.getName()))); - assertTrue(isRxFunctionType(pool.get(Func3.class.getName()))); - assertTrue(isRxFunctionType(pool.get(Func4.class.getName()))); - assertTrue(isRxFunctionType(pool.get(Func5.class.getName()))); - assertTrue(isRxFunctionType(pool.get(Func6.class.getName()))); - assertTrue(isRxFunctionType(pool.get(Func7.class.getName()))); - assertTrue(isRxFunctionType(pool.get(Func8.class.getName()))); - assertTrue(isRxFunctionType(pool.get(Func9.class.getName()))); - assertFalse(isRxFunctionType(pool.get(Action.class.getName()))); - assertFalse(isRxFunctionType(pool.get(Action0.class.getName()))); - assertFalse(isRxFunctionType(pool.get(Action1.class.getName()))); - assertFalse(isRxFunctionType(pool.get(Action2.class.getName()))); - assertFalse(isRxFunctionType(pool.get(Action3.class.getName()))); - } catch (Exception e) { - fail(e.getMessage()); - } - } - - @Test - public void testIsRxActionType() { - try { - assertFalse(isRxActionType(pool.get(Function.class.getName()))); - assertFalse(isRxActionType(pool.get(Func0.class.getName()))); - assertFalse(isRxActionType(pool.get(Func1.class.getName()))); - assertFalse(isRxActionType(pool.get(Func2.class.getName()))); - assertFalse(isRxActionType(pool.get(Func3.class.getName()))); - assertFalse(isRxActionType(pool.get(Func4.class.getName()))); - assertFalse(isRxActionType(pool.get(Func5.class.getName()))); - assertFalse(isRxActionType(pool.get(Func6.class.getName()))); - assertFalse(isRxActionType(pool.get(Func7.class.getName()))); - assertFalse(isRxActionType(pool.get(Func8.class.getName()))); - assertFalse(isRxActionType(pool.get(Func9.class.getName()))); - assertTrue(isRxActionType(pool.get(Action.class.getName()))); - assertTrue(isRxActionType(pool.get(Action0.class.getName()))); - assertTrue(isRxActionType(pool.get(Action1.class.getName()))); - assertTrue(isRxActionType(pool.get(Action2.class.getName()))); - assertTrue(isRxActionType(pool.get(Action3.class.getName()))); - } catch (Exception e) { - fail(e.getMessage()); - } - } - } -} - diff --git a/language-adaptors/codegen/src/main/java/rx/codegen/Func1Generator.java b/language-adaptors/codegen/src/main/java/rx/codegen/Func1Generator.java new file mode 100644 index 0000000000..290bbd0f18 --- /dev/null +++ b/language-adaptors/codegen/src/main/java/rx/codegen/Func1Generator.java @@ -0,0 +1,87 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.codegen; + +import javassist.CannotCompileException; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.Modifier; +import javassist.NotFoundException; + +import rx.util.functions.Func1; +import rx.util.functions.Function; +import rx.util.functions.FunctionLanguageAdaptor; + +/** + * Generate a class named {@code DYNAMIC_FUNC1_NAME}. This class gets constructed in code generated by + * {@code OneArgSubscribeOnMapMethodRewriter}. It supports a chain of instanceof checks that determine which + * native function class gets passed in, and converts it to an rx core {@code Action}. Contrast this with + * the version in core {@code actionIdentity}, which needs no runtime checks - it's just an identity. + * + * Once the Func1 has been constructed, you can pass that into the core subscribeWithConversion(Map, Func1) to + * apply the correct runtime checks/conversions. + */ +public class Func1Generator { + + private final ClassPool pool; + private final FunctionLanguageAdaptor adaptor; + + public Func1Generator(ClassPool pool, FunctionLanguageAdaptor adaptor) { + this.pool = pool; + this.adaptor = adaptor; + } + + static final String DYNAMIC_FUNC1_NAME = "Func1DynamicConverter"; + + public CtClass createDynamicFunc1Class() throws CannotCompileException, NotFoundException { + Class func1Class = Func1.class; + CtClass func1CtClass = pool.get(func1Class.getName()); + Class funcClass = Function.class; + CtClass funcCtClass = pool.get(funcClass.getName()); + CtClass objectCtClass = pool.get(Object.class.getName()); + CtClass[] params = new CtClass[1]; + params[0] = objectCtClass; + + CtClass conversionClass = pool.makeClass(DYNAMIC_FUNC1_NAME); + StringBuffer methodBody = new StringBuffer(); + StringBuffer classCheckBranching = new StringBuffer(); + for (Class nativeFunctionClass: adaptor.getAllClassesToRewrite()) { + Class actionAdaptorClass = adaptor.getActionClassRewritingMap().get(nativeFunctionClass); + + classCheckBranching.append("} else if ($1 instanceof " + nativeFunctionClass.getName() + ") {\n"); + classCheckBranching.append("return new " + actionAdaptorClass.getName() + "((" + nativeFunctionClass.getName() + ") $1);\n"); + } + + methodBody.append("{\n"); + methodBody.append("if ($1 instanceof " + funcClass.getName() + ") {\n"); + methodBody.append("return (" + funcClass.getName() + ") $1;\n"); + methodBody.append(classCheckBranching.toString()); + methodBody.append("} else {\n"); + methodBody.append("throw new RuntimeException(\"Can't convert from class : \" + $1.getClass().getName());\n"); + methodBody.append("}\n"); + methodBody.append("}\n"); + + CtMethod callMethod = new CtMethod(objectCtClass, "call", params, conversionClass); + callMethod.setModifiers(Modifier.PUBLIC); + callMethod.setBody(methodBody.toString()); + conversionClass.addMethod(callMethod); + conversionClass.addInterface(func1CtClass); + int classModifiers = conversionClass.getModifiers(); + conversionClass.setModifiers(Modifier.PUBLIC); + return conversionClass; + } +} \ No newline at end of file diff --git a/language-adaptors/codegen/src/main/java/rx/codegen/MethodRewriteRequest.java b/language-adaptors/codegen/src/main/java/rx/codegen/MethodRewriteRequest.java new file mode 100644 index 0000000000..174bc95b03 --- /dev/null +++ b/language-adaptors/codegen/src/main/java/rx/codegen/MethodRewriteRequest.java @@ -0,0 +1,54 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.codegen; + +import java.util.List; + +import javassist.CtClass; + +/** + * Immutable class representing a request to rewrite a method, without specifying how + */ +public class MethodRewriteRequest { + private Class functionAdaptorClass; + private Class actionAdaptorClass; + private List newArgTypes; + + /** + * Public constructor + * @param functionAdaptorClass adaptor for rewriting a native function to an Rx core {@code Function}. + * @param actionAdaptorClass adaptor for rewriting a native action to an Rx core {@code Action}. + * @param newArgTypes args of the new method that needs to be written + */ + public MethodRewriteRequest(Class functionAdaptorClass, Class actionAdaptorClass, List newArgTypes) { + this.functionAdaptorClass = functionAdaptorClass; + this.actionAdaptorClass = actionAdaptorClass; + this.newArgTypes = newArgTypes; + } + + public Class getFunctionAdaptorClass() { + return this.functionAdaptorClass; + } + + public Class getActionAdaptorClass() { + return this.actionAdaptorClass; + } + + public List getNewArgTypes() { + return this.newArgTypes; + } + +} \ No newline at end of file diff --git a/language-adaptors/codegen/src/main/java/rx/codegen/MethodRewriter.java b/language-adaptors/codegen/src/main/java/rx/codegen/MethodRewriter.java new file mode 100644 index 0000000000..6d3218bf94 --- /dev/null +++ b/language-adaptors/codegen/src/main/java/rx/codegen/MethodRewriter.java @@ -0,0 +1,354 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.codegen; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.NotFoundException; + +import rx.util.functions.Action; +import rx.util.functions.Action0; +import rx.util.functions.Action1; +import rx.util.functions.Action2; +import rx.util.functions.Action3; +import rx.util.functions.Func0; +import rx.util.functions.Func1; +import rx.util.functions.Func2; +import rx.util.functions.Func3; +import rx.util.functions.Func4; +import rx.util.functions.Func5; +import rx.util.functions.Func6; +import rx.util.functions.Func7; +import rx.util.functions.Func8; +import rx.util.functions.Func9; +import rx.util.functions.Function; +import rx.util.functions.FunctionLanguageAdaptor; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import org.junit.Test; + +/** + * A strategy for how to rewrite methods. This uses the javassist library heavily. For every method in an + * {@code Observable} class, a {@code MethodRewriter} gets constructed and used to output a method or methods + * that add dynamic language support to the {@code Observable} class. + */ +abstract class MethodRewriter { + + protected static ClassPool pool = ClassPool.getDefault(); + protected CtClass enclosingClass; + protected CtMethod initialMethod; + protected FunctionLanguageAdaptor adaptor; + + /** + * Public static constructor + * @param enclosingClass class in which method exists + * @param method method to rewrite + * @param adaptor Language adaptor which describes how to add dynamic language support + * @return a strategy for rewriting the given {@code CtMethod} + * @throws Exception on any error + */ + public static MethodRewriter from(CtClass enclosingClass, CtMethod method, FunctionLanguageAdaptor adaptor) throws Exception { + if (isOneArgSubscribeOnMap(enclosingClass, method)) { + return new OneArgSubscribeOnMapMethodRewriter(enclosingClass, method, adaptor); + } else if (argTypeIncludesRxFunction(method)) { + return new AddSpecializedDynamicMethodRewriter(enclosingClass, method, adaptor); + } else { + return new NoOpMethodRewriter(); + } + } + + /** + * Does the method require a rewrite at all? + */ + public abstract boolean needsRewrite(); + + /** + * Should the method replace the original or add additional methods? + */ + public abstract boolean isReplacement(); + + /** + * Given arguments on the initial method, what methods should get generated? + * @param initialArgTypes arguments of the initial method + * @return a list of method rewrites to attempt + */ + protected abstract List getNewMethodsToRewrite(CtClass[] initialArgTypes); + + /** + * Return the body of the rewritten body for the {@code MethodRewriteRequest} that gets made + * @param method initial method + * @param enclosingClass {@code Observable} class in which initial method exists + * @param methodRewriteRequest request for rewrite (includes args of new method and adaptor for how to add dynamic + * support for a given language) + * @return {@code String} body of new method + */ + protected abstract String getRewrittenMethodBody(CtMethod method, CtClass enclosingClass, MethodRewriteRequest methodRewriteRequest); + + /** + * Return all the fully-formed {@code CtMethod}s that the rewriting process generates, given the initial method + * @return new {@code CtMethod}s + * @throws Exception on any error + */ + public List getNewMethods() throws Exception { + List methodList = new ArrayList(); + + CtClass[] initialArgTypes = initialMethod.getParameterTypes(); + + List newMethodRewriteRequests = getNewMethodsToRewrite(initialArgTypes); + for (MethodRewriteRequest newMethodRewriteRequest: newMethodRewriteRequests) { + List newArgTypes = newMethodRewriteRequest.getNewArgTypes(); + CtClass[] newArgTypeArray = newArgTypes.toArray(new CtClass[newArgTypes.size()]); + CtMethod newMethod = new CtMethod(initialMethod.getReturnType(), initialMethod.getName(), newArgTypeArray, enclosingClass); + newMethod.setModifiers(initialMethod.getModifiers()); + String newBody = getRewrittenMethodBody(initialMethod, enclosingClass, newMethodRewriteRequest); + //Uncomment this to see all the rewritten method body + debugMethodRewriting(initialMethod, newMethod, newBody); + newMethod.setBody(newBody); + methodList.add(newMethod); + } + return methodList; + } + + /** + * Verbose output on how methods are getting rewritten + * @param initialMethod initial method + * @param newMethod new method + * @param newBody new method body + * @throws NotFoundException + */ + private static void debugMethodRewriting(CtMethod initialMethod, CtMethod newMethod, String newBody) throws NotFoundException { + List initialArgStrings = mapClassNames(initialMethod.getParameterTypes()); + List finalArgStrings = mapClassNames(newMethod.getParameterTypes()); + + String initialArgString = makeArgList(initialArgStrings); + String finalArgString = makeArgList(finalArgStrings); + + System.out.println(initialMethod.getReturnType().getName() + " " + initialMethod.getName() + "(" + initialArgString +") --> " + newMethod.getReturnType().getName() + " " + newMethod.getName() + "(" + finalArgString + ")"); + System.out.println(" " + newBody.toString()); + } + + /** + * We need to special case the subscribe(Map m) method. Check if the given method is subscribe(Map m) + * @param enclosingClass {@code Observable} class where method lives + * @param method method to check + * @return true iff the passed-in method is subscribe(Map m) + * @throws NotFoundException + */ + private static boolean isOneArgSubscribeOnMap(CtClass enclosingClass, CtMethod method) throws Exception { + String methodName = method.getName(); + CtClass[] args = method.getParameterTypes(); + if (enclosingClass.equals(method.getDeclaringClass()) && args.length == 1 && methodName.equals("subscribe")) { + if (isMap(args[0])) { + return true; + } + } + return false; + } + + /** + * Check if the passed-in method has any arguments which are an Rx core function type. + * If so, we will need to add a variant of this method that can be called with a native function class instead. + * @param method method to check + * @return true iff the method has an Rx core function type in its args + * @throws Exception + */ + private static boolean argTypeIncludesRxFunction(CtMethod method) throws Exception { + CtClass[] args = method.getParameterTypes(); + for (CtClass methodArg: args) { + if (isRxFunctionType(methodArg) || isRxActionType(methodArg)) { + return true; + } + } + return false; + } + + /** + * Check if a class is an Rx core {@code Function} type + * @param type class to check + * @return true iff the passed-in class is a {@code Function} + * @throws Exception + */ + protected static boolean isRxFunctionType(CtClass type) throws Exception { + return implementsInterface(type, Function.class); + } + + /** + * Check if a class is an Rx core {@code Action} type + * @param type class to check + * @return true iff the passed-in class is a {@code Action} + * @throws Exception + */ + protected static boolean isRxActionType(CtClass type) throws Exception { + return implementsInterface(type, Action.class); + } + + /** + * Check if the class is any subclass of {@code Map}. + * @param type class to check + * @return true iff the passed-in class in a {@code Map} + * @throws Exception + */ + protected static boolean isMap(CtClass type) throws Exception { + return implementsInterface(type, Map.class); + } + + /** + * Helper method to check if a given class implements a given interface (recursively) + * @param type class to check + * @param interfaceClass interface to check implementation of + * @return true iff the passed-in type implements the passed-in interface + * @throws Exception + */ + protected static boolean implementsInterface(CtClass type, Class interfaceClass) throws Exception { + // Did I pass in the exact interface? + if (type.getName().equals(interfaceClass.getName())) { + return true; + } + CtClass[] implementedInterfaces = type.getInterfaces(); + if (implementedInterfaces.length == 0) { + //no more superclasses to check + return false; + } else { + for (CtClass implementedInterface: implementedInterfaces) { + if (implementedInterface.getName().equals(interfaceClass.getName())) { + return true; + } + if (implementsInterface(implementedInterface, interfaceClass)) { + return true; + } + } + } + return false; + } + + /** + * Convert {@code CtClass} array to {@code List} + * @param classes classes to get names of + * @return list of class names + */ + protected static List mapClassNames(CtClass[] classes) { + List strs = new ArrayList(); + for (CtClass clazz: classes) { + strs.add(clazz.getName()); + } + return strs; + } + + /** + * Convert {@code List} into comma-separated single {@code String}. + * @param argTypes list of argument strings + * @return comma-separated list of strings + */ + protected static String makeArgList(List argTypes) { + if (argTypes.size() > 0) { + StringBuffer buffer = new StringBuffer(argTypes.get(0)); + for (String argType: argTypes.subList(1, argTypes.size())) { + buffer.append("," + argType); + } + return buffer.toString(); + } + return ""; + } + + /** + * Within a rewritten method body, wrap an arg in an adaptor class in a javassist-friendly way + * Ex: If adaptorClass is FooAdaptor, then this method performs: "$2" -> "new FooAdaptor($2)" + * @param adaptorClass adaptor intended to wrap the original arg + * @param index position of arg (this is how javassist calls args) + * @return String of wrapped arg + */ + protected static String getAdaptedArg(Class adaptorClass, int index) { + StringBuffer buffer = new StringBuffer(); + buffer.append("new "); + buffer.append(adaptorClass.getName()); + buffer.append("("); + buffer.append(getUntouchedArg(index)); + buffer.append(")"); + return buffer.toString(); + } + + /** + * Rewrite an arg (based on position) in a javassist-friendly way. + * Ex: 2 -> "$2" + * @param index arg index + * @return String of arg (in javassist-format) + */ + protected static String getUntouchedArg(int index) { + StringBuffer buffer = new StringBuffer(); + buffer.append("$"); + buffer.append(index); + return buffer.toString(); + } + + public static class UnitTest { + private static ClassPool pool = ClassPool.getDefault(); + + @Test + public void testIsRxFunctionType() { + try { + assertTrue(isRxFunctionType(pool.get(Function.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Func0.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Func1.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Func2.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Func3.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Func4.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Func5.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Func6.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Func7.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Func8.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Func9.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Action.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Action0.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Action1.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Action2.class.getName()))); + assertTrue(isRxFunctionType(pool.get(Action3.class.getName()))); + } catch (Exception e) { + fail(e.getMessage()); + } + } + + @Test + public void testIsRxActionType() { + try { + assertFalse(isRxActionType(pool.get(Function.class.getName()))); + assertFalse(isRxActionType(pool.get(Func0.class.getName()))); + assertFalse(isRxActionType(pool.get(Func1.class.getName()))); + assertFalse(isRxActionType(pool.get(Func2.class.getName()))); + assertFalse(isRxActionType(pool.get(Func3.class.getName()))); + assertFalse(isRxActionType(pool.get(Func4.class.getName()))); + assertFalse(isRxActionType(pool.get(Func5.class.getName()))); + assertFalse(isRxActionType(pool.get(Func6.class.getName()))); + assertFalse(isRxActionType(pool.get(Func7.class.getName()))); + assertFalse(isRxActionType(pool.get(Func8.class.getName()))); + assertFalse(isRxActionType(pool.get(Func9.class.getName()))); + assertTrue(isRxActionType(pool.get(Action.class.getName()))); + assertTrue(isRxActionType(pool.get(Action0.class.getName()))); + assertTrue(isRxActionType(pool.get(Action1.class.getName()))); + assertTrue(isRxActionType(pool.get(Action2.class.getName()))); + assertTrue(isRxActionType(pool.get(Action3.class.getName()))); + } catch (Exception e) { + fail(e.getMessage()); + } + } + } +} \ No newline at end of file diff --git a/language-adaptors/codegen/src/main/java/rx/codegen/NoOpMethodRewriter.java b/language-adaptors/codegen/src/main/java/rx/codegen/NoOpMethodRewriter.java new file mode 100644 index 0000000000..f391a4ee73 --- /dev/null +++ b/language-adaptors/codegen/src/main/java/rx/codegen/NoOpMethodRewriter.java @@ -0,0 +1,48 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.codegen; + +import java.util.ArrayList; +import java.util.List; + +import javassist.CtClass; +import javassist.CtMethod; + +/** + * Method that needs no rewriting to function with dynamic language. + */ +public class NoOpMethodRewriter extends MethodRewriter { + + @Override + public boolean needsRewrite() { + return false; + } + + @Override + public boolean isReplacement() { + return false; + } + + @Override + protected List getNewMethodsToRewrite(CtClass[] initialArgTypes) { + return new ArrayList(); + } + + @Override + protected String getRewrittenMethodBody(CtMethod method, CtClass enclosingClass, MethodRewriteRequest methodRewriteRequest) { + return ""; + } +} \ No newline at end of file diff --git a/language-adaptors/codegen/src/main/java/rx/codegen/ObservableRewriter.java b/language-adaptors/codegen/src/main/java/rx/codegen/ObservableRewriter.java new file mode 100644 index 0000000000..32cc569be1 --- /dev/null +++ b/language-adaptors/codegen/src/main/java/rx/codegen/ObservableRewriter.java @@ -0,0 +1,94 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.codegen; + +import java.util.List; +import java.util.Set; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.NotFoundException; + +import rx.util.functions.FunctionLanguageAdaptor; + +/** + * Given an initial {@code Observable} class, rewrite it with all necessary method to support a particular + * dynamic language (as specified by the {@code FunctionLanguageAdaptor}). + */ +public class ObservableRewriter { + + private final ClassPool pool; + private final FunctionLanguageAdaptor adaptor; + + public ObservableRewriter(ClassPool pool, FunctionLanguageAdaptor adaptor) { + this.pool = pool; + this.adaptor = adaptor; + } + + /** + * Entry point - given the passed-in class, add dynamic language support to the class and write it out + * to the filesystem + * + * @param initialClass class to add dynamic support to + * @return class with all rewritten methods + */ + public CtClass addMethods(Class initialClass) { + if (!initialClass.getName().startsWith("rx.")) { + throw new IllegalStateException("Refusing to rewrite a class that is not a core Rx Observable!"); + } + + Set> nativeFunctionClasses = adaptor.getAllClassesToRewrite(); + System.out.println("Adding dynamic language support to : " + initialClass.getSimpleName()); + for (Class nativeFunctionClass: nativeFunctionClasses) { + System.out.println(" * Adding : " + nativeFunctionClass.getName()); + } + + try { + CtClass clazz = pool.get(initialClass.getName()); + CtClass rewrittenClass = rewriteMethodsWithRxArgs(clazz); + return rewrittenClass; + } catch (NotFoundException e) { + throw new RuntimeException("Failed to add language adaptor methods as could not find observable Class named " + initialClass.getName(), e); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Failed to add language adaptor methods.", e); + } + } + + /** + * Loop over all methods and rewrite each (if necessary), returning the end result + * @param enclosingClass + * @return + * @throws Exception + */ + private CtClass rewriteMethodsWithRxArgs(CtClass enclosingClass) throws Exception { + for (CtMethod method : enclosingClass.getMethods()) { + MethodRewriter methodRewriter = MethodRewriter.from(enclosingClass, method, adaptor); + if (methodRewriter.needsRewrite()) { + if (methodRewriter.isReplacement()) { + enclosingClass.removeMethod(method); + } + List newMethods = methodRewriter.getNewMethods(); + for (CtMethod cm: newMethods) { + enclosingClass.addMethod(cm); + } + } + } + return enclosingClass; + } +} + diff --git a/language-adaptors/codegen/src/main/java/rx/codegen/OneArgSubscribeOnMapMethodRewriter.java b/language-adaptors/codegen/src/main/java/rx/codegen/OneArgSubscribeOnMapMethodRewriter.java new file mode 100644 index 0000000000..cbc5942b7c --- /dev/null +++ b/language-adaptors/codegen/src/main/java/rx/codegen/OneArgSubscribeOnMapMethodRewriter.java @@ -0,0 +1,99 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.codegen; + +import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; + +import javassist.CtClass; +import javassist.CtMethod; +import javassist.NotFoundException; + +import rx.util.functions.Func1; +import rx.util.functions.FunctionLanguageAdaptor; + +/** + * An implementation of method rewriting that handles subscribe(Map) specially. The core method has the type signature + * subscribe(Map m). Since {@code Map} is generic, we can't add a new method + * subscribe(Map m) (or some other dynamic variant), since the JVM believes those 2 methods + * are the same because of type erasure. + * + * Instead, we rewrite the body of the method to handle any necessary run-time checks and casting, while leaving the + * signature along. + * + * For Groovy (as an example), the new class would contain: + * subscribe(Map callbacks) { + * Func1 dynamicLanguageSupport = new DynamicConversion(); + * return subscribeWithConversion(callbacks, dynamicLanguageSupport); + * } + * {@see Func1Generator} for details on how the Func1 is generated to have the appropriate runtime checks. + */ +public class OneArgSubscribeOnMapMethodRewriter extends MethodRewriter { + + private static final String DYNAMIC_FUNC1_NAME = "Func1DynamicConverter"; + + public OneArgSubscribeOnMapMethodRewriter(CtClass enclosingClass, CtMethod method, FunctionLanguageAdaptor adaptor) { + this.enclosingClass = enclosingClass; + this.initialMethod = method; + this.adaptor = adaptor; + } + + @Override + public boolean needsRewrite() { + return true; + } + + @Override + public boolean isReplacement() { + return true; + } + + /** + * We want to return a subscribe(Map m) method. Note that this is true no matter how many + * dynamic languages we intend to support, since Map is generic. + */ + @Override + protected List getNewMethodsToRewrite(CtClass[] initialArgTypes) { + List reqs = new ArrayList(); + for (Class nativeFunctionClass: adaptor.getAllClassesToRewrite()) { + Class functionAdaptorClass = adaptor.getFunctionClassRewritingMap().get(nativeFunctionClass); + Class actionAdaptorClass = adaptor.getActionClassRewritingMap().get(nativeFunctionClass); + reqs.add(new MethodRewriteRequest(functionAdaptorClass, actionAdaptorClass, Arrays.asList(initialArgTypes))); + return reqs; + } + return reqs; + } + + @Override + protected String getRewrittenMethodBody(CtMethod method, CtClass enclosingClass, MethodRewriteRequest methodRewriteRequest) { + Class func1Class = Func1.class; + StringBuffer methodBody = new StringBuffer(); + + try { + CtClass dynamicFunc1CtClass = pool.get(DYNAMIC_FUNC1_NAME); + + methodBody.append("{"); + methodBody.append(func1Class.getName() + " dynamicLanguageSupport = new " + dynamicFunc1CtClass.getName() + "();\n"); + methodBody.append("return subscribeWithConversion($1, dynamicLanguageSupport);"); + methodBody.append("}"); + } catch (NotFoundException ex) { + System.out.println("Couldn't find class for : " + DYNAMIC_FUNC1_NAME); + throw new RuntimeException("Couldn't find class for : " + DYNAMIC_FUNC1_NAME); + } + return methodBody.toString(); + } +} \ No newline at end of file diff --git a/language-adaptors/rxjava-dynamic/build.gradle b/language-adaptors/rxjava-dynamic/build.gradle deleted file mode 100644 index 4b75e1d308..0000000000 --- a/language-adaptors/rxjava-dynamic/build.gradle +++ /dev/null @@ -1,45 +0,0 @@ -apply plugin: 'osgi' - -dependencies { - core project(':rxjava-core') - provided project(':rxjava-core') - provided project(':language-adaptors:codegen') - provided 'junit:junit-dep:4.10' - provided 'org.mockito:mockito-core:1.8.5' -} - -task createAdaptedObservable(type: JavaExec) { - main = 'rx.codegen.ClassPathBasedRunner' - classpath = sourceSets.main.runtimeClasspath + configurations.provided - args = ["Dynamic", codeGenOutputDir] - - inputs.files(sourceSets.main.runtimeClasspath) - outputs.dir(codeGenOutputDir) -} - -tasks.test { - dependsOn(createAdaptedObservable) - - //Reorders the classpath so that the newly-create Observables win - classpath = createAdaptedObservable.outputs.files + sourceSets.test.runtimeClasspath -} - -tasks.jar { - dependsOn(createAdaptedObservable) - - from (zipTree(configurations.core.singleFile)) { - exclude "rx/Observable.class" - exclude "rx/observables/BlockingObservable.class" - } - from(codeGenOutputDir) - - exclude('**/*$UnitTest*') - - manifest { - name = 'rxjava-dynamic' - instruction 'Bundle-Vendor', 'Netflix' - instruction 'Bundle-DocURL', 'https://github.com/Netflix/RxJava' - instruction 'Import-Package', '!org.junit,!junit.framework,!org.mockito.*,*' - instruction 'Fragment-Host', 'com.netflix.rxjava.core' - } -} diff --git a/language-adaptors/rxjava-dynamic/src/main/java/rx/lang/dynamic/DynamicActionWrapper.java b/language-adaptors/rxjava-dynamic/src/main/java/rx/lang/dynamic/DynamicActionWrapper.java deleted file mode 100644 index 697176c80f..0000000000 --- a/language-adaptors/rxjava-dynamic/src/main/java/rx/lang/dynamic/DynamicActionWrapper.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright 2013 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package rx.lang.dynamic; - -import rx.util.functions.Action0; -import rx.util.functions.Action1; -import rx.util.functions.Functions; - -public class DynamicActionWrapper implements Action0, Action1 { - private Object object; - - public DynamicActionWrapper(Object object) { - this.object = object; - } - - @Override - public void call() { - Functions.from(object).call(); - } - - @Override - public void call(T1 t1) { - Functions.from(object).call(t1); - } -} \ No newline at end of file diff --git a/language-adaptors/rxjava-dynamic/src/main/java/rx/lang/dynamic/DynamicAdaptor.java b/language-adaptors/rxjava-dynamic/src/main/java/rx/lang/dynamic/DynamicAdaptor.java deleted file mode 100644 index 636e0e54ea..0000000000 --- a/language-adaptors/rxjava-dynamic/src/main/java/rx/lang/dynamic/DynamicAdaptor.java +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright 2013 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package rx.lang.dynamic; - -import java.util.HashMap; -import java.util.Map; - -import rx.util.functions.FunctionLanguageAdaptor; - -import java.util.HashSet; -import java.util.Set; - -public class DynamicAdaptor implements FunctionLanguageAdaptor { - - @Override - public Map, Class> getFunctionClassRewritingMap() { - Map, Class> m = new HashMap, Class>(); - m.put(Object.class, DynamicFunctionWrapper.class); - return m; - } - - @Override - public Map, Class> getActionClassRewritingMap() { - Map, Class> m = new HashMap, Class>(); - m.put(Object.class, DynamicActionWrapper.class); - return m; - } - - @Override - public Set> getAllClassesToRewrite() { - Set> dynamicClasses = new HashSet>(); - dynamicClasses.add(Object.class); - return dynamicClasses; - } -} diff --git a/language-adaptors/rxjava-dynamic/src/main/java/rx/lang/dynamic/DynamicFunctionWrapper.java b/language-adaptors/rxjava-dynamic/src/main/java/rx/lang/dynamic/DynamicFunctionWrapper.java deleted file mode 100644 index 897d9f8d81..0000000000 --- a/language-adaptors/rxjava-dynamic/src/main/java/rx/lang/dynamic/DynamicFunctionWrapper.java +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright 2013 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package rx.lang.dynamic; - -import rx.util.functions.Func0; -import rx.util.functions.Func1; -import rx.util.functions.Func2; -import rx.util.functions.Func3; -import rx.util.functions.Func4; -import rx.util.functions.FunctionLanguageAdaptor; -import rx.util.functions.Functions; - -public class DynamicFunctionWrapper implements Func0, Func1, Func2, Func3, Func4 { - private Object object; - - public DynamicFunctionWrapper(Object object) { - this.object = object; - } - - @Override - public R call() { - return (R) Functions.from(object).call(); - } - - @Override - public R call(T1 t1) { - return (R) Functions.from(object).call(t1); - } - - @Override - public R call(T1 t1, T2 t2) { - return (R) Functions.from(object).call(t1, t2); - } - - @Override - public R call(T1 t1, T2 t2, T3 t3) { - return (R) Functions.from(object).call(t1, t2, t3); - } - - @Override - public R call(T1 t1, T2 t2, T3 t3, T4 t4) { - return (R) Functions.from(object).call(t1, t2, t3, t4); - } -} \ No newline at end of file diff --git a/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyFunctionWrapper.java b/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyFunctionWrapper.java index 1b038fd1ba..7d0f2bb240 100644 --- a/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyFunctionWrapper.java +++ b/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyFunctionWrapper.java @@ -20,6 +20,7 @@ import rx.util.functions.Func2; import rx.util.functions.Func3; import rx.util.functions.Func4; +import rx.util.functions.FuncN; import rx.util.functions.FunctionLanguageAdaptor; import groovy.lang.Closure; diff --git a/language-adaptors/rxjava-groovy/src/test/groovy/rx/lang/groovy/ObservableTests.groovy b/language-adaptors/rxjava-groovy/src/test/groovy/rx/lang/groovy/ObservableTests.groovy index dd36af1088..4bbf7f8ae9 100644 --- a/language-adaptors/rxjava-groovy/src/test/groovy/rx/lang/groovy/ObservableTests.groovy +++ b/language-adaptors/rxjava-groovy/src/test/groovy/rx/lang/groovy/ObservableTests.groovy @@ -21,6 +21,7 @@ import static org.mockito.Mockito.*; import java.util.Arrays; import java.util.Collection; import java.util.Map; +import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.Test; @@ -33,6 +34,7 @@ import rx.Notification; import rx.Observable; import rx.Observer; import rx.Subscription; +import rx.concurrency.TestScheduler; import rx.observables.GroupedObservable; import rx.subscriptions.Subscriptions; import rx.util.functions.Func1; @@ -50,6 +52,41 @@ def class ObservableTests { MockitoAnnotations.initMocks(this); } + @Test + public void testSubscribeWithMap() { + def o = Observable.from(1, 2, 3) + def subscribeMap = [ + "onNext" : { i -> a.received(i) }, + "onError" : { e -> a.received(e) }, + "onCompleted" : { a.received("COMPLETE") } + ] + o.subscribe(subscribeMap) + verify(a, times(1)).received(1) + verify(a, times(1)).received(2) + verify(a, times(1)).received(3) + verify(a, times(1)).received("COMPLETE") + } + + @Test + public void testSubscribeWithMapAndScheduler() { + def o = Observable.from(1, 2, 3) + def subscribeMap = [ + "onNext" : { i -> a.received(i) }, + "onError" : { e -> a.received(e) }, + "onCompleted" : { a.received("COMPLETE") } + ] + def scheduler = new TestScheduler() + o.subscribe(subscribeMap, scheduler) + verify(a, never()).received(any(Object.class)) + + scheduler.advanceTimeBy(5, TimeUnit.SECONDS) + + verify(a, times(1)).received(1) + verify(a, times(1)).received(2) + verify(a, times(1)).received(3) + verify(a, times(1)).received("COMPLETE") + } + @Test public void testCreate() { Observable.create({it.onNext('hello');it.onCompleted();}).subscribe({ result -> a.received(result)}); diff --git a/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyAdaptor.java b/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyAdaptor.java index f210d64917..5a120b92dc 100644 --- a/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyAdaptor.java +++ b/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyAdaptor.java @@ -169,10 +169,10 @@ private void runGroovyScript(String script) { container.put("a", assertion); StringBuilder b = new StringBuilder(); - // force JRuby to always use subscribe(Object) + // force JRuby to always use subscribe(RubyProc) b.append("import \"rx.Observable\"").append("\n"); b.append("class Observable").append("\n"); - b.append(" java_alias :subscribe, :subscribe, [java.lang.Object]").append("\n"); + b.append(" java_alias :subscribe, :subscribe, [org.jruby.RubyProc]").append("\n"); b.append("end").append("\n"); b.append(script); diff --git a/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyFunctionWrapper.java b/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyFunctionWrapper.java index c3e78957cb..4fd1dd70fb 100644 --- a/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyFunctionWrapper.java +++ b/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyFunctionWrapper.java @@ -16,6 +16,7 @@ package rx.lang.jruby; import org.jruby.Ruby; +import org.jruby.RubyBoolean; import org.jruby.RubyProc; import org.jruby.javasupport.JavaEmbedUtils; import org.jruby.runtime.builtin.IRubyObject; @@ -65,6 +66,11 @@ private Object callRubyProc(Object... args) { for (int i = 0; i < args.length; i++) { rubyArgs[i] = JavaEmbedUtils.javaToRuby(ruby, args[i]); } - return (R) proc.getBlock().call(ruby.getCurrentContext(), rubyArgs); + R result = (R) proc.getBlock().call(ruby.getCurrentContext(), rubyArgs); + if (result instanceof RubyBoolean) { + return ((RubyBoolean) result).isTrue(); + } else { + return result; + } } } \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/Observable.java b/rxjava-core/src/main/java/rx/Observable.java index 57cfae16ac..9b75aa2a9e 100644 --- a/rxjava-core/src/main/java/rx/Observable.java +++ b/rxjava-core/src/main/java/rx/Observable.java @@ -23,6 +23,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; @@ -36,6 +37,7 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import rx.concurrency.TestScheduler; import rx.observables.BlockingObservable; import rx.observables.ConnectableObservable; import rx.observables.GroupedObservable; @@ -91,6 +93,7 @@ import rx.util.OnErrorNotImplementedException; import rx.util.Range; import rx.util.Timestamped; +import rx.util.functions.Action; import rx.util.functions.Action0; import rx.util.functions.Action1; import rx.util.functions.Func0; @@ -268,17 +271,26 @@ private Subscription protectivelyWrapAndSubscribe(Observer o) { return subscription.wrap(subscribe(new SafeObserver(subscription, o))); } + /** + * Helper method that is especially useful for supporting dynamic languages ({@see FunctionLanguageAdaptor}). + * Given a {@code Map} of callbacks, pull the function out and use the supplied converter to get out an Rx core + * {@code Action}. + * @param callbacks {@code Map} of callback functions. "onNext" is required, and "onError"/"onCompleted" also supported as keys + * @param converter {@code Func1} that converts from some Function-y type to an Rx core {@code Action} + * @param specific dynamic language function type + * @return subscription using supplied callbacks and converter + */ @SuppressWarnings({ "rawtypes", "unchecked" }) - public Subscription subscribe(final Map callbacks) { + private Subscription subscribeWithConversion(final Map callbacks, final Func1 converter) { if (callbacks == null) { throw new RuntimeException("callbacks map can not be null"); } - Object _onNext = callbacks.get("onNext"); + F _onNext = callbacks.get("onNext"); if (_onNext == null) { throw new RuntimeException("'onNext' key must contain an implementation"); } // lookup and memoize onNext - final FuncN onNext = Functions.from(_onNext); + final Action1 onNext = (Action1) converter.call(_onNext); /** * Wrapping since raw functions provided by the user are being invoked. @@ -288,19 +300,21 @@ public Subscription subscribe(final Map callbacks) { return protectivelyWrapAndSubscribe(new Observer() { @Override - public void onCompleted() { - Object onComplete = callbacks.get("onCompleted"); - if (onComplete != null) { - Functions.from(onComplete).call(); + public void onCompleted() { + F _onComplete = callbacks.get("onCompleted"); + if (_onComplete != null) { + final Action0 onCompleteAction = (Action0) converter.call(_onComplete); + onCompleteAction.call(); } } @Override public void onError(Throwable e) { handleError(e); - Object onError = callbacks.get("onError"); - if (onError != null) { - Functions.from(onError).call(e); + F _onError = callbacks.get("onError"); + if (_onError != null) { + final Action1 onError = (Action1) converter.call(_onError); + onError.call(e); } else { throw new OnErrorNotImplementedException(e); } @@ -310,11 +324,30 @@ public void onError(Throwable e) { public void onNext(Object args) { onNext.call(args); } + }); + } - }); + /** + * Used in conjunction with {@code subscribeWithConversion} to do the proper no-op conversion. + * Contrast this with the {@code Func1}s in the dynamic language support codegen package. + */ + private static Func1 actionIdentity = new Func1() { + @Override + public Action call(Action a) { + return a; + } + }; + + /** + * Given a {@code Map} of callbacks, create a {@code Subscription} + * @param callbacks {@code Map} of callback functions. "onNext" is required, and "onError"/"onCompleted" also supported as keys + * @return subscription using supplied callbacks + */ + public Subscription subscribe(final Map callbacks) { + return subscribeWithConversion(callbacks, actionIdentity); } - public Subscription subscribe(final Map callbacks, Scheduler scheduler) { + public Subscription subscribe(final Map callbacks, Scheduler scheduler) { return subscribeOn(scheduler).subscribe(callbacks); } @@ -3188,6 +3221,83 @@ public void testSequenceEqual() { verify(result, times(1)).onNext(false); } + @Test + public void testSubscribeWithMap() { + Observable o = toObservable(1, 2, 3); + final Observer observer = mock(Observer.class); + Action1 onNext = new Action1() { + @Override + public void call(Integer in) { + observer.onNext(in); + } + }; + Action1 onError = new Action1() { + @Override + public void call(Exception ex) { + observer.onError(ex); + } + }; + Action0 onCompleted = new Action0() { + @Override + public void call() { + observer.onCompleted(); + } + }; + Map fMap = new HashMap(); + fMap.put("onNext", onNext); + fMap.put("onError", onError); + fMap.put("onCompleted", onCompleted); + o.subscribe(fMap); + verify(observer, times(1)).onNext(1); + verify(observer, times(1)).onNext(2); + verify(observer, times(1)).onNext(3); + verify(observer, times(1)).onCompleted(); + verify(observer, times(0)).onError(any(Exception.class)); + } + + @Test + public void testSubscribeWithMapAndScheduler() { + Observable o = toObservable(1, 2, 3); + final Observer observer = mock(Observer.class); + Action1 onNext = new Action1() { + @Override + public void call(Integer in) { + observer.onNext(in); + } + }; + Action1 onError = new Action1() { + @Override + public void call(Exception ex) { + observer.onError(ex); + } + }; + Action0 onCompleted = new Action0() { + @Override + public void call() { + observer.onCompleted(); + } + }; + Map fMap = new HashMap(); + fMap.put("onNext", onNext); + fMap.put("onError", onError); + fMap.put("onCompleted", onCompleted); + + TestScheduler scheduler = new TestScheduler(); + + o.subscribe(fMap, scheduler); + verify(observer, times(0)).onNext(any(Integer.class)); + verify(observer, times(0)).onError(any(Exception.class)); + verify(observer, times(0)).onCompleted(); + + scheduler.advanceTimeBy(10, TimeUnit.SECONDS); + + verify(observer, times(1)).onNext(1); + verify(observer, times(1)).onNext(2); + verify(observer, times(1)).onNext(3); + verify(observer, times(1)).onCompleted(); + verify(observer, times(0)).onError(any(Exception.class)); + } + @Test public void testOnSubscribeFails() { @SuppressWarnings("unchecked") diff --git a/rxjava-core/src/main/java/rx/operators/OperationZip.java b/rxjava-core/src/main/java/rx/operators/OperationZip.java index 92d987ffa7..e5b80c8be9 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationZip.java +++ b/rxjava-core/src/main/java/rx/operators/OperationZip.java @@ -299,7 +299,7 @@ public static class UnitTest { @SuppressWarnings("unchecked") @Test public void testCollectionSizeDifferentThanFunction() { - FuncN zipr = Functions.from(getConcatStringIntegerIntArrayZipr()); + FuncN zipr = Functions.fromFunction(getConcatStringIntegerIntArrayZipr()); /* define a Observer to receive aggregated events */ Observer aObserver = mock(Observer.class); diff --git a/rxjava-core/src/main/java/rx/util/functions/Action.java b/rxjava-core/src/main/java/rx/util/functions/Action.java index 95b3176971..59d358093a 100644 --- a/rxjava-core/src/main/java/rx/util/functions/Action.java +++ b/rxjava-core/src/main/java/rx/util/functions/Action.java @@ -21,6 +21,6 @@ *

* Marker interface to allow instanceof checks. */ -public interface Action { +public interface Action extends Function { } diff --git a/rxjava-core/src/main/java/rx/util/functions/FunctionLanguageAdaptor.java b/rxjava-core/src/main/java/rx/util/functions/FunctionLanguageAdaptor.java index 9e471e7329..d72f68856c 100644 --- a/rxjava-core/src/main/java/rx/util/functions/FunctionLanguageAdaptor.java +++ b/rxjava-core/src/main/java/rx/util/functions/FunctionLanguageAdaptor.java @@ -31,11 +31,23 @@ public interface FunctionLanguageAdaptor { */ //public Class[] getFunctionClass(); - //TODO MRJ: Add Javadoc + /** + * Map detailing how to rewrite a native function class into an Rx {@code Action} + * Example: for Groovy, a 1-element Map : { groovy.lang.Closure -> GroovyActionWrapper } + * @return map for rewriting native functions to Rx {@code Action}s. + */ public Map, Class> getActionClassRewritingMap(); - //TODO MRJ: Add Javadoc + /** + * Map detailing how to rewrite a native function class into an Rx {@code Function} + * Example: for Groovy, a 1-element Map : { groovy.lang.Closure -> GroovyFunctionWrapper } + * @return map for rewriting native functions to Rx {@code Function}s. + */ public Map, Class> getFunctionClassRewritingMap(); + /** + * All native function classes that require translation into the Rx world + * @return set of all native function classes + */ public Set> getAllClassesToRewrite(); } diff --git a/rxjava-core/src/main/java/rx/util/functions/Functions.java b/rxjava-core/src/main/java/rx/util/functions/Functions.java index 0581e46e13..218e0d2c9a 100644 --- a/rxjava-core/src/main/java/rx/util/functions/Functions.java +++ b/rxjava-core/src/main/java/rx/util/functions/Functions.java @@ -21,23 +21,7 @@ public class Functions { @SuppressWarnings({ "unchecked", "rawtypes" }) - public static FuncN from(final Object function) { - if (function == null) { - throw new RuntimeException("function is null. Can't send arguments to null function."); - } - - /* check for typed Rx Function implementation first */ - if (function instanceof Function) { - return fromFunction((Function) function); - } else if (function instanceof Action) { - return fromAction((Action) function); - } - // no support found - throw new RuntimeException("Unsupported closure type: " + function.getClass().getSimpleName()); - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - private static FuncN fromFunction(Function function) { + public static FuncN fromFunction(Function function) { // check Func* classes if (function instanceof Func0) { return fromFunc((Func0) function); @@ -67,7 +51,7 @@ private static FuncN fromFunction(Function function) { } @SuppressWarnings({ "unchecked", "rawtypes" }) - private static FuncN fromAction(Action action) { + public static FuncN fromAction(Action action) { // check Action* classes if (action instanceof Action0) { return fromAction((Action0) action); diff --git a/settings.gradle b/settings.gradle index 93c3e179ed..d6cc324181 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,6 +4,5 @@ include 'rxjava-core', \ 'language-adaptors:rxjava-jruby', \ 'language-adaptors:rxjava-clojure', \ 'language-adaptors:rxjava-scala', \ -'language-adaptors:rxjava-dynamic', \ 'language-adaptors:codegen', \ 'rxjava-contrib:rxjava-swing' From 598a94f1e27df9f6380dc5c0cc27c6fd0961790c Mon Sep 17 00:00:00 2001 From: Matt Jacobs Date: Sun, 18 Aug 2013 22:30:06 -0700 Subject: [PATCH 7/8] Added a special case to bytecode rewriting for methods which would otherwise conflict * 2 'buffer' methods introduced this case. --- .../AddSpecializedDynamicMethodRewriter.java | 37 +--- .../rx/codegen/ConflictingMethodGroup.java | 83 +++++++ .../rx/codegen/ConflictingMethodRewriter.java | 207 ++++++++++++++++++ .../main/java/rx/codegen/MethodRewriter.java | 76 ++++++- .../java/rx/codegen/NoOpMethodRewriter.java | 2 +- .../java/rx/codegen/ObservableRewriter.java | 63 +++++- .../OneArgSubscribeOnMapMethodRewriter.java | 2 +- .../rx/lang/clojure/ClojureActionWrapper.java | 8 +- .../java/rx/lang/clojure/ClojureAdaptor.java | 4 + .../rx/lang/clojure/ClojureArityChecker.java | 37 ++++ .../lang/clojure/ClojureFunctionWrapper.java | 12 +- .../rx/lang/groovy/GroovyActionWrapper.java | 8 +- .../java/rx/lang/groovy/GroovyAdaptor.java | 4 + .../rx/lang/groovy/GroovyArityChecker.java | 29 +++ .../rx/lang/groovy/GroovyFunctionWrapper.java | 12 +- .../rx/lang/groovy/ObservableTests.groovy | 113 +++++++++- .../rx/lang/jruby/JRubyActionWrapper.java | 8 +- .../main/java/rx/lang/jruby/JRubyAdaptor.java | 3 + .../java/rx/lang/jruby/JRubyArityChecker.java | 29 +++ .../rx/lang/jruby/JRubyFunctionWrapper.java | 12 +- 20 files changed, 683 insertions(+), 66 deletions(-) create mode 100644 language-adaptors/codegen/src/main/java/rx/codegen/ConflictingMethodGroup.java create mode 100644 language-adaptors/codegen/src/main/java/rx/codegen/ConflictingMethodRewriter.java create mode 100644 language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureArityChecker.java create mode 100644 language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyArityChecker.java create mode 100644 language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyArityChecker.java diff --git a/language-adaptors/codegen/src/main/java/rx/codegen/AddSpecializedDynamicMethodRewriter.java b/language-adaptors/codegen/src/main/java/rx/codegen/AddSpecializedDynamicMethodRewriter.java index 8df3575d4a..d02d1a8fe1 100644 --- a/language-adaptors/codegen/src/main/java/rx/codegen/AddSpecializedDynamicMethodRewriter.java +++ b/language-adaptors/codegen/src/main/java/rx/codegen/AddSpecializedDynamicMethodRewriter.java @@ -58,49 +58,24 @@ public boolean isReplacement() { @Override protected List getNewMethodsToRewrite(CtClass[] initialArgTypes) { - List reqs = new ArrayList(); - - try { - for (Class nativeFunctionClass: adaptor.getAllClassesToRewrite()) { - Class functionAdaptorClass = adaptor.getFunctionClassRewritingMap().get(nativeFunctionClass); - Class actionAdaptorClass = adaptor.getActionClassRewritingMap().get(nativeFunctionClass); - - CtClass[] argTypes = initialMethod.getParameterTypes(); - List parameters = new ArrayList(); - - for (CtClass argType : argTypes) { - if (isRxFunctionType(argType) || isRxActionType(argType)) { - // needs conversion - parameters.add(pool.get(nativeFunctionClass.getName())); - } else { - // no conversion, copy through - parameters.add(argType); - } - } - MethodRewriteRequest req = new MethodRewriteRequest(functionAdaptorClass, actionAdaptorClass, parameters); - reqs.add(req); - } - } catch (Exception ex) { - System.out.println("Exception while rewriting method : " + initialMethod.getName()); - } - return reqs; + return duplicatedMethodsWithWrappedFunctionTypes(); } @Override - protected String getRewrittenMethodBody(CtMethod method, CtClass enclosingClass, MethodRewriteRequest req) { + protected String getRewrittenMethodBody(MethodRewriteRequest req) { StringBuffer newBody = new StringBuffer(); List argumentList = new ArrayList(); newBody.append("{ return "); - if (Modifier.isStatic(method.getModifiers())) { + if (Modifier.isStatic(initialMethod.getModifiers())) { newBody.append(enclosingClass.getName() + "."); } else { newBody.append("this."); } - newBody.append(method.getName()); + newBody.append(initialMethod.getName()); newBody.append("("); try { - for (int i = 0; i < method.getParameterTypes().length; i++) { - CtClass argType = method.getParameterTypes()[i]; + for (int i = 0; i < initialMethod.getParameterTypes().length; i++) { + CtClass argType = initialMethod.getParameterTypes()[i]; if (isRxActionType(argType) && req.getActionAdaptorClass() != null) { argumentList.add(getAdaptedArg(req.getActionAdaptorClass(), i + 1)); } else if (isRxFunctionType(argType) && req.getFunctionAdaptorClass() != null) { diff --git a/language-adaptors/codegen/src/main/java/rx/codegen/ConflictingMethodGroup.java b/language-adaptors/codegen/src/main/java/rx/codegen/ConflictingMethodGroup.java new file mode 100644 index 0000000000..6475bef7bc --- /dev/null +++ b/language-adaptors/codegen/src/main/java/rx/codegen/ConflictingMethodGroup.java @@ -0,0 +1,83 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.codegen; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import javassist.CtMethod; +import javassist.Modifier; + +/** + * A group of methods that, if rewritten using the normal {@code AddSpecializedDynamicMethodRewriter} rewriting strategy, + * would conflict at the JVM level. These methods contain parameters with types that are different in Java, but the same + * in a dynamic language. + * + * The first example (more are expected) came up on the following buffer method signatures: + * public static Observable> buffer(Observable source, Func0> bufferClosingSelector) + * public Observable> buffer(Observable bufferOpenings, Func1> bufferClosingSelector) + * These methods both would be rewritten to buffer(Observable, groovy.lang.Closure) unless we handled the conflict + * explicitly. + */ +public class ConflictingMethodGroup { + private final List conflictingMethods; + + /** + * Comparator that provides a stable sort on methods. This is important, since we add 1 method per group. + * Sort by instance methods first, and then sort both instance and static methods by their hashes. + */ + private static Comparator methodSorter = new Comparator() { + @Override + public int compare(CtMethod m1, CtMethod m2) { + boolean isM1Static = Modifier.isStatic(m1.getModifiers()); + boolean isM2Static = Modifier.isStatic(m2.getModifiers()); + if (isM1Static && !isM2Static) { + return 1; + } else if (!isM1Static && isM2Static) { + return -1; + } else { + Integer hash1 = m1.hashCode(); + Integer hash2 = m2.hashCode(); + return hash1.compareTo(hash2); + } + } + }; + + public ConflictingMethodGroup(List conflictingMethods) { + assert(conflictingMethods.size() >= 2); + this.conflictingMethods = new ArrayList(conflictingMethods); + Collections.sort(this.conflictingMethods, methodSorter); + } + + /** + * Get all methods in the group + * @return all methods + */ + public List getMethods() { + return this.conflictingMethods; + } + + /** + * Given a method, is that method the first element in this group? + * @param method method to check + * @return true iff the method is the first element of this group + */ + public boolean hasHead(CtMethod method) { + return conflictingMethods.get(0).equals(method); + } +} diff --git a/language-adaptors/codegen/src/main/java/rx/codegen/ConflictingMethodRewriter.java b/language-adaptors/codegen/src/main/java/rx/codegen/ConflictingMethodRewriter.java new file mode 100644 index 0000000000..dd805ff4a2 --- /dev/null +++ b/language-adaptors/codegen/src/main/java/rx/codegen/ConflictingMethodRewriter.java @@ -0,0 +1,207 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.codegen; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javassist.CtClass; +import javassist.CtMethod; +import javassist.Modifier; +import javassist.NotFoundException; + +import rx.util.functions.FunctionLanguageAdaptor; +import rx.util.functions.Func0; +import rx.util.functions.Func1; +import rx.util.functions.Func2; +import rx.util.functions.Func3; +import rx.util.functions.Func4; +import rx.util.functions.Func5; +import rx.util.functions.Func6; +import rx.util.functions.Func7; +import rx.util.functions.Func8; +import rx.util.functions.Func9; +import rx.util.functions.Action0; +import rx.util.functions.Action1; +import rx.util.functions.Action2; +import rx.util.functions.Action3; + +/** + * A strategy for rewriting a single method out of a {@code ConflictingMethodGroup}. The goal is to have a single + * method inserted that branches on the arity of the arguments and dynamically forwards to an Rx core method. + * + * The first example of needing conflict resolution (more are expected) came up on the following buffer method signatures: + * public static Observable> buffer(Observable source, Func0> bufferClosingSelector) + * public Observable> buffer(Observable bufferOpenings, Func1> bufferClosingSelector) + * These methods both would be rewritten to buffer(Observable, groovy.lang.Closure) unless we handled the conflict + * explicitly. + */ +public class ConflictingMethodRewriter extends MethodRewriter { + + private ConflictingMethodGroup conflictingMethodGroup; + + public ConflictingMethodRewriter(CtClass enclosingClass, CtMethod initialMethod, FunctionLanguageAdaptor adaptor, ConflictingMethodGroup conflictingMethodGroup) { + this.enclosingClass = enclosingClass; + this.initialMethod = initialMethod; + this.adaptor = adaptor; + this.conflictingMethodGroup = conflictingMethodGroup; + } + + @Override + public boolean needsRewrite() { + return true; + } + + @Override + public boolean isReplacement() { + return false; + } + + @Override + protected List getNewMethodsToRewrite(CtClass[] initialArgTypes) { + return duplicatedMethodsWithWrappedFunctionTypes(); + } + + //Sample body: + //public T buffer(Observable $1, Closure $2) { + // GroovyFunctionAdaptor adaptor = new GroovyFunctionAdaptor($2); + // int arity = adaptor.getArity(); + // if (arity == 0) { + // return buffer($1, ((Func0) adaptor)); + // } else if (arity == 1) { + // return buffer($1, ((Func1) adaptor)); + // } else { + // throw new RuntimeException("Couldn't match num args : " + arity); + // } + //} + + /* + * Known shortcoming - this only works for method groups that have 1 Function in the params + * This simplified the generated code and at the moment, there is only 1 set of methods which + * needs this treatment: + * public static Observable> buffer(Observable source, Func0> bufferClosingSelector) + * public Observable> buffer(Observable bufferOpenings, Func1> bufferClosingSelector) + */ + @Override + protected String getRewrittenMethodBody(MethodRewriteRequest methodRewriteRequest) { + final Map arityMap = new HashMap(); + try { + for (CtMethod conflictingMethod: conflictingMethodGroup.getMethods()) { + CtClass[] parameterTypes = conflictingMethod.getParameterTypes(); + for (CtClass paramType: parameterTypes) { + if (isRxFunctionType(paramType) || isRxActionType(paramType)) { + Integer arity = getArityFromName(paramType); + arityMap.put(arity, conflictingMethod); + } + } + } + } catch (Exception e) { + System.out.println("Error determining arity map : " + e); + } + + final StringBuffer newBody = new StringBuffer(); + newBody.append("{"); + + try { + for (int i = 0; i < initialMethod.getParameterTypes().length; i++) { + CtClass argType = initialMethod.getParameterTypes()[i]; + if (isRxActionType(argType)) { + newBody.append(methodRewriteRequest.getActionAdaptorClass().getName() + " adaptor = "); + newBody.append(getAdaptedArg(methodRewriteRequest.getActionAdaptorClass(), i + 1)); + newBody.append(";"); + } else if (isRxFunctionType(argType)) { + newBody.append(methodRewriteRequest.getFunctionAdaptorClass().getName() + " adaptor = "); + newBody.append(getAdaptedArg(methodRewriteRequest.getFunctionAdaptorClass(), i + 1)); + newBody.append(";"); + } + } + + newBody.append("int arity = adaptor.getArity();"); + for (Integer arity: arityMap.keySet()) { + CtMethod methodWithArity = arityMap.get(arity); + newBody.append("if (arity == " + arity.toString() + ") {"); + newBody.append("return "); + if (Modifier.isStatic(methodWithArity.getModifiers())) { + newBody.append(enclosingClass.getName() + "."); + } else { + newBody.append("this."); + } + newBody.append(methodWithArity.getName()); + newBody.append("("); + List argumentList = new ArrayList(); + for (int i = 0; i < initialMethod.getParameterTypes().length; i++) { + CtClass argType = initialMethod.getParameterTypes()[i]; + if (isRxActionType(argType)) { + String actionArg = "((rx.util.functions.Action" + arity.toString() + ") adaptor)"; + argumentList.add(actionArg); + } else if (isRxFunctionType(argType)) { + String functionArg = "((rx.util.functions.Func" + arity.toString() + ") adaptor)"; + argumentList.add(functionArg); + } else { + argumentList.add(getUntouchedArg(i + 1)); + } + } + newBody.append(makeArgList(argumentList)); + newBody.append(");"); + newBody.append(" } else "); + } + newBody.append(" {"); + newBody.append("throw new RuntimeException(\"Couldn't match arity : \" + arity);"); + newBody.append("}"); + newBody.append("}"); + } catch (Exception e) { + System.out.println("Exception while create conflicted method body : " + e); + } + + return newBody.toString(); + } + + private int getArityFromName(CtClass clazz) { + if (clazz.getName().equals(Func0.class.getName())) { + return 0; + } else if (clazz.getName().equals(Func1.class.getName())) { + return 1; + } else if (clazz.getName().equals(Func2.class.getName())) { + return 2; + } else if (clazz.getName().equals(Func3.class.getName())) { + return 3; + } else if (clazz.getName().equals(Func4.class.getName())) { + return 4; + } else if (clazz.getName().equals(Func5.class.getName())) { + return 5; + } else if (clazz.getName().equals(Func6.class.getName())) { + return 6; + } else if (clazz.getName().equals(Func7.class.getName())) { + return 7; + } else if (clazz.getName().equals(Func8.class.getName())) { + return 8; + } else if (clazz.getName().equals(Func9.class.getName())) { + return 9; + } else if (clazz.getName().equals(Action0.class.getName())) { + return 0; + } else if (clazz.getName().equals(Action1.class.getName())) { + return 1; + } else if (clazz.getName().equals(Action2.class.getName())) { + return 2; + } else if (clazz.getName().equals(Action3.class.getName())) { + return 3; + } else { + return -1; + } + } +} \ No newline at end of file diff --git a/language-adaptors/codegen/src/main/java/rx/codegen/MethodRewriter.java b/language-adaptors/codegen/src/main/java/rx/codegen/MethodRewriter.java index 6d3218bf94..9ddcf0b049 100644 --- a/language-adaptors/codegen/src/main/java/rx/codegen/MethodRewriter.java +++ b/language-adaptors/codegen/src/main/java/rx/codegen/MethodRewriter.java @@ -67,14 +67,22 @@ abstract class MethodRewriter { * @return a strategy for rewriting the given {@code CtMethod} * @throws Exception on any error */ - public static MethodRewriter from(CtClass enclosingClass, CtMethod method, FunctionLanguageAdaptor adaptor) throws Exception { - if (isOneArgSubscribeOnMap(enclosingClass, method)) { - return new OneArgSubscribeOnMapMethodRewriter(enclosingClass, method, adaptor); - } else if (argTypeIncludesRxFunction(method)) { - return new AddSpecializedDynamicMethodRewriter(enclosingClass, method, adaptor); + public static MethodRewriter from(CtClass enclosingClass, CtMethod method, FunctionLanguageAdaptor adaptor, List conflictingMethodGroups) throws Exception { + ConflictingMethodGroup conflictingMethodGroup = getConflictingMethodGroup(conflictingMethodGroups, method); + if (conflictingMethodGroup != null) { + if (conflictingMethodGroup.hasHead(method)) { + return new ConflictingMethodRewriter(enclosingClass, method, adaptor, conflictingMethodGroup); + } } else { - return new NoOpMethodRewriter(); + if (enclosingClass.equals(method.getDeclaringClass())) { + if (isOneArgSubscribeOnMap(enclosingClass, method)) { + return new OneArgSubscribeOnMapMethodRewriter(enclosingClass, method, adaptor); + } else if (argTypeIncludesRxFunction(enclosingClass, method)) { + return new AddSpecializedDynamicMethodRewriter(enclosingClass, method, adaptor); + } + } } + return new NoOpMethodRewriter(); } /** @@ -102,7 +110,7 @@ public static MethodRewriter from(CtClass enclosingClass, CtMethod method, Funct * support for a given language) * @return {@code String} body of new method */ - protected abstract String getRewrittenMethodBody(CtMethod method, CtClass enclosingClass, MethodRewriteRequest methodRewriteRequest); + protected abstract String getRewrittenMethodBody(MethodRewriteRequest methodRewriteRequest); /** * Return all the fully-formed {@code CtMethod}s that the rewriting process generates, given the initial method @@ -120,11 +128,12 @@ public List getNewMethods() throws Exception { CtClass[] newArgTypeArray = newArgTypes.toArray(new CtClass[newArgTypes.size()]); CtMethod newMethod = new CtMethod(initialMethod.getReturnType(), initialMethod.getName(), newArgTypeArray, enclosingClass); newMethod.setModifiers(initialMethod.getModifiers()); - String newBody = getRewrittenMethodBody(initialMethod, enclosingClass, newMethodRewriteRequest); - //Uncomment this to see all the rewritten method body - debugMethodRewriting(initialMethod, newMethod, newBody); + String newBody = getRewrittenMethodBody(newMethodRewriteRequest); newMethod.setBody(newBody); methodList.add(newMethod); + + //Uncomment this to see the rewritten method body + //debugMethodRewriting(initialMethod, newMethod, newBody); } return methodList; } @@ -147,6 +156,15 @@ private static void debugMethodRewriting(CtMethod initialMethod, CtMethod newMet System.out.println(" " + newBody.toString()); } + private static ConflictingMethodGroup getConflictingMethodGroup(List conflictingMethodGroups, CtMethod method) { + for (ConflictingMethodGroup group: conflictingMethodGroups) { + if (group.getMethods().contains(method)) { + return group; + } + } + return null; + } + /** * We need to special case the subscribe(Map m) method. Check if the given method is subscribe(Map m) * @param enclosingClass {@code Observable} class where method lives @@ -157,7 +175,7 @@ private static void debugMethodRewriting(CtMethod initialMethod, CtMethod newMet private static boolean isOneArgSubscribeOnMap(CtClass enclosingClass, CtMethod method) throws Exception { String methodName = method.getName(); CtClass[] args = method.getParameterTypes(); - if (enclosingClass.equals(method.getDeclaringClass()) && args.length == 1 && methodName.equals("subscribe")) { + if (args.length == 1 && methodName.equals("subscribe")) { if (isMap(args[0])) { return true; } @@ -172,7 +190,7 @@ private static boolean isOneArgSubscribeOnMap(CtClass enclosingClass, CtMethod m * @return true iff the method has an Rx core function type in its args * @throws Exception */ - private static boolean argTypeIncludesRxFunction(CtMethod method) throws Exception { + private static boolean argTypeIncludesRxFunction(CtClass enclosingClass, CtMethod method) throws Exception { CtClass[] args = method.getParameterTypes(); for (CtClass methodArg: args) { if (isRxFunctionType(methodArg) || isRxActionType(methodArg)) { @@ -241,6 +259,40 @@ protected static boolean implementsInterface(CtClass type, Class interfaceCla return false; } + /** + * Given an initial method, return {@code MethodRewriteRequest}s that replace all Rx functions with + * the native function equivalent, so that dynamic languages can target this method. + * @return {@code MethodRewriteRequest}s that dynamic languages can natively target + */ + protected List duplicatedMethodsWithWrappedFunctionTypes() { + List reqs = new ArrayList(); + + try { + for (Class nativeFunctionClass: adaptor.getAllClassesToRewrite()) { + Class functionAdaptorClass = adaptor.getFunctionClassRewritingMap().get(nativeFunctionClass); + Class actionAdaptorClass = adaptor.getActionClassRewritingMap().get(nativeFunctionClass); + + CtClass[] argTypes = initialMethod.getParameterTypes(); + List parameters = new ArrayList(); + + for (CtClass argType : argTypes) { + if (isRxFunctionType(argType) || isRxActionType(argType)) { + // needs conversion + parameters.add(pool.get(nativeFunctionClass.getName())); + } else { + // no conversion, copy through + parameters.add(argType); + } + } + MethodRewriteRequest req = new MethodRewriteRequest(functionAdaptorClass, actionAdaptorClass, parameters); + reqs.add(req); + } + } catch (Exception ex) { + System.out.println("Exception while rewriting method : " + initialMethod.getName()); + } + return reqs; + } + /** * Convert {@code CtClass} array to {@code List} * @param classes classes to get names of diff --git a/language-adaptors/codegen/src/main/java/rx/codegen/NoOpMethodRewriter.java b/language-adaptors/codegen/src/main/java/rx/codegen/NoOpMethodRewriter.java index f391a4ee73..48e3dbec29 100644 --- a/language-adaptors/codegen/src/main/java/rx/codegen/NoOpMethodRewriter.java +++ b/language-adaptors/codegen/src/main/java/rx/codegen/NoOpMethodRewriter.java @@ -42,7 +42,7 @@ protected List getNewMethodsToRewrite(CtClass[] initialArg } @Override - protected String getRewrittenMethodBody(CtMethod method, CtClass enclosingClass, MethodRewriteRequest methodRewriteRequest) { + protected String getRewrittenMethodBody(MethodRewriteRequest methodRewriteRequest) { return ""; } } \ No newline at end of file diff --git a/language-adaptors/codegen/src/main/java/rx/codegen/ObservableRewriter.java b/language-adaptors/codegen/src/main/java/rx/codegen/ObservableRewriter.java index 32cc569be1..b519aa5636 100644 --- a/language-adaptors/codegen/src/main/java/rx/codegen/ObservableRewriter.java +++ b/language-adaptors/codegen/src/main/java/rx/codegen/ObservableRewriter.java @@ -15,7 +15,10 @@ */ package rx.codegen; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; import javassist.ClassPool; @@ -76,8 +79,9 @@ public CtClass addMethods(Class initialClass) { * @throws Exception */ private CtClass rewriteMethodsWithRxArgs(CtClass enclosingClass) throws Exception { + List conflictingMethodGroups = getConflictingMethodGroups(enclosingClass); for (CtMethod method : enclosingClass.getMethods()) { - MethodRewriter methodRewriter = MethodRewriter.from(enclosingClass, method, adaptor); + MethodRewriter methodRewriter = MethodRewriter.from(enclosingClass, method, adaptor, conflictingMethodGroups); if (methodRewriter.needsRewrite()) { if (methodRewriter.isReplacement()) { enclosingClass.removeMethod(method); @@ -90,5 +94,62 @@ private CtClass rewriteMethodsWithRxArgs(CtClass enclosingClass) throws Exceptio } return enclosingClass; } + + /** + * Iterate through all the methods in the given class and find methods which would collide + * if they were rewritten with dynamic wrappers. + * @param enclosingClass class to find conflicting methods in + * @return list of {@code ConflictingMethodGroup}s found in given class + */ + private List getConflictingMethodGroups(CtClass enclosingClass) throws Exception { + List groups = new ArrayList(); + + Map> methodHashes = new HashMap>(); + for (CtMethod m: enclosingClass.getMethods()) { + if (enclosingClass.equals(m.getDeclaringClass())) { + Integer hash = getHash(m); + if (methodHashes.containsKey(hash)) { + List existingMethods = methodHashes.get(hash); + existingMethods.add(m); + methodHashes.put(hash, existingMethods); + } else { + List newMethodList = new ArrayList(); + newMethodList.add(m); + methodHashes.put(hash, newMethodList); + } + } + } + + for (Integer key: methodHashes.keySet()) { + List methodsWithSameHash = methodHashes.get(key); + if (methodsWithSameHash.size() > 1) { + ConflictingMethodGroup group = new ConflictingMethodGroup(methodsWithSameHash); + groups.add(group); + } + } + + return groups; + } + + //we care about collisions post-rewrite. So take a hash over method name and arguments + //where every Rx {@code Action} or Rx {@code Function} is hashed to the same value + // in this case {@code Function} + private int getHash(CtMethod m) throws Exception { + final String delimiter = ","; + + final StringBuffer buffer = new StringBuffer(); + final String name = m.getName(); + buffer.append(name); + buffer.append(delimiter); + for (CtClass argClass: m.getParameterTypes()) { + if (MethodRewriter.isRxActionType(argClass) || MethodRewriter.isRxFunctionType(argClass)) { + buffer.append("RxFunc"); + } else { + buffer.append(argClass.getName()); + } + buffer.append(delimiter); + } + return buffer.toString().hashCode(); + } } diff --git a/language-adaptors/codegen/src/main/java/rx/codegen/OneArgSubscribeOnMapMethodRewriter.java b/language-adaptors/codegen/src/main/java/rx/codegen/OneArgSubscribeOnMapMethodRewriter.java index cbc5942b7c..8d87b17749 100644 --- a/language-adaptors/codegen/src/main/java/rx/codegen/OneArgSubscribeOnMapMethodRewriter.java +++ b/language-adaptors/codegen/src/main/java/rx/codegen/OneArgSubscribeOnMapMethodRewriter.java @@ -79,7 +79,7 @@ protected List getNewMethodsToRewrite(CtClass[] initialArg } @Override - protected String getRewrittenMethodBody(CtMethod method, CtClass enclosingClass, MethodRewriteRequest methodRewriteRequest) { + protected String getRewrittenMethodBody(MethodRewriteRequest methodRewriteRequest) { Class func1Class = Func1.class; StringBuffer methodBody = new StringBuffer(); diff --git a/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureActionWrapper.java b/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureActionWrapper.java index 38d4dd64a8..86a7d4bbf2 100644 --- a/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureActionWrapper.java +++ b/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureActionWrapper.java @@ -22,9 +22,11 @@ import clojure.lang.RT; import clojure.lang.Var; -public class ClojureActionWrapper implements Action0, Action1 { - private IFn ifn; - +/** + * Concrete wrapper that accepts an {@code IFn} and produces any needed Rx {@code Action}. + * @param + */ +public class ClojureActionWrapper extends ClojureArityChecker implements Action0, Action1 { public ClojureActionWrapper(IFn ifn) { this.ifn = ifn; } diff --git a/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureAdaptor.java b/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureAdaptor.java index 31cff79786..fec78b95b9 100644 --- a/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureAdaptor.java +++ b/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureAdaptor.java @@ -32,6 +32,10 @@ import clojure.lang.RT; import clojure.lang.Var; +/** + * Defines the single Clojure class {@code IFn} that should map to Rx functions + * For now, a unit test to prove the Clojure integration works + */ public class ClojureAdaptor implements FunctionLanguageAdaptor { @Override diff --git a/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureArityChecker.java b/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureArityChecker.java new file mode 100644 index 0000000000..2506e1058a --- /dev/null +++ b/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureArityChecker.java @@ -0,0 +1,37 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.clojure; + +import clojure.lang.IFn; + +import java.lang.reflect.Method; + +/** + * Base class for Clojure adaptors that knows how to get the arity of an {@code IFn}. + */ +public abstract class ClojureArityChecker { + protected IFn ifn; + + //Hoping this is correct: + //http://stackoverflow.com/questions/1696693/clojure-how-to-find-out-the-arity-of-function-at-runtime + int getArity() { + Class ifnClass = ifn.getClass(); + for (Method m: ifnClass.getDeclaredMethods()) { + return m.getParameterTypes().length; + } + return 0; + } +} \ No newline at end of file diff --git a/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureFunctionWrapper.java b/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureFunctionWrapper.java index 1aed83fc7e..3ad96f8e8b 100644 --- a/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureFunctionWrapper.java +++ b/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/ClojureFunctionWrapper.java @@ -24,9 +24,15 @@ import clojure.lang.IFn; -public class ClojureFunctionWrapper implements Func0, Func1, Func2, Func3, Func4 { - private IFn ifn; - +/** + * Concrete wrapper that accepts an {@code IFn} and produces any needed Rx {@code Function}. + * @param + * @param + * @param + * @param + * @param + */ +public class ClojureFunctionWrapper extends ClojureArityChecker implements Func0, Func1, Func2, Func3, Func4 { public ClojureFunctionWrapper(IFn ifn) { this.ifn = ifn; } diff --git a/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyActionWrapper.java b/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyActionWrapper.java index a831a0ed42..36e88794eb 100644 --- a/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyActionWrapper.java +++ b/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyActionWrapper.java @@ -20,9 +20,11 @@ import groovy.lang.Closure; -public class GroovyActionWrapper implements Action0, Action1 { - private Closure closure; - +/** + * Concrete wrapper that accepts a {@code Closure} and produces any needed Rx {@code Action}. + * @param + */ +public class GroovyActionWrapper extends GroovyArityChecker implements Action0, Action1 { public GroovyActionWrapper(Closure closure) { this.closure = closure; } diff --git a/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyAdaptor.java b/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyAdaptor.java index 530a7d2d63..55e416ac1b 100644 --- a/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyAdaptor.java +++ b/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyAdaptor.java @@ -25,6 +25,9 @@ import java.util.HashSet; import java.util.Set; +/** + * Defines the single Groovy class {@code Closure} that should map to Rx functions + */ public class GroovyAdaptor implements FunctionLanguageAdaptor { @Override @@ -48,3 +51,4 @@ public Set> getAllClassesToRewrite() { return groovyClasses; } } + diff --git a/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyArityChecker.java b/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyArityChecker.java new file mode 100644 index 0000000000..c23783b1cb --- /dev/null +++ b/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyArityChecker.java @@ -0,0 +1,29 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.groovy; + +import groovy.lang.Closure; + +/** + * Base class for Groovy adaptors that knows how to get the arity of a {@code Closure}. + */ +public abstract class GroovyArityChecker { + protected Closure closure; + + public int getArity() { + return closure.getMaximumNumberOfParameters(); + } +} \ No newline at end of file diff --git a/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyFunctionWrapper.java b/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyFunctionWrapper.java index 7d0f2bb240..0d0946c38d 100644 --- a/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyFunctionWrapper.java +++ b/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyFunctionWrapper.java @@ -25,9 +25,15 @@ import groovy.lang.Closure; -public class GroovyFunctionWrapper implements Func0, Func1, Func2, Func3, Func4 { - private Closure closure; - +/** + * Concrete wrapper that accepts a {@code Closure} and produces any needed Rx {@code Function}. + * @param + * @param + * @param + * @param + * @param + */ +public class GroovyFunctionWrapper extends GroovyArityChecker implements Func0, Func1, Func2, Func3, Func4 { public GroovyFunctionWrapper(Closure closure) { this.closure = closure; } diff --git a/language-adaptors/rxjava-groovy/src/test/groovy/rx/lang/groovy/ObservableTests.groovy b/language-adaptors/rxjava-groovy/src/test/groovy/rx/lang/groovy/ObservableTests.groovy index 4bbf7f8ae9..b539e6c65e 100644 --- a/language-adaptors/rxjava-groovy/src/test/groovy/rx/lang/groovy/ObservableTests.groovy +++ b/language-adaptors/rxjava-groovy/src/test/groovy/rx/lang/groovy/ObservableTests.groovy @@ -27,7 +27,9 @@ import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; +import org.mockito.InOrder; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import rx.Notification; @@ -37,6 +39,9 @@ import rx.Subscription; import rx.concurrency.TestScheduler; import rx.observables.GroupedObservable; import rx.subscriptions.Subscriptions; +import rx.util.BufferClosings; +import rx.util.BufferOpenings; +import rx.util.functions.Action0; import rx.util.functions.Func1; def class ObservableTests { @@ -47,9 +52,14 @@ def class ObservableTests { @Mock Observer w; + @Mock Observer> observer; + + TestScheduler scheduler; + @Before public void before() { MockitoAnnotations.initMocks(this); + scheduler = new TestScheduler(); } @Test @@ -75,7 +85,6 @@ def class ObservableTests { "onError" : { e -> a.received(e) }, "onCompleted" : { a.received("COMPLETE") } ] - def scheduler = new TestScheduler() o.subscribe(subscribeMap, scheduler) verify(a, never()).received(any(Object.class)) @@ -343,7 +352,107 @@ def class ObservableTests { assertEquals(6, count); } - + + /*@Test + public void testStaticBufferOfCollectedValues() { + def source = Observable.create( + { observer -> + push(observer, "one", 10) + push(observer, "two", 60) + push(observer, "three", 110) + push(observer, "four", 160) + push(observer, "five", 210) + complete(observer, 250) + return Subscriptions.empty() + } + ) + + def closer = { -> + return Observable.create( + { observer -> + push(observer, BufferClosings.create(), 100) + complete(observer, 101) + return Subscriptions.empty() + } + ) + } + + def buffered = Observable.create(buffer(source, closer)) + buffered.subscribe(observer) + + InOrder inOrder = Mockito.inOrder(observer) + scheduler.advanceTimeTo(500, TimeUnit.MILLISECONDS) + inOrder.verify(observer, Mockito.times(1)).onNext(list("one", "two")) + inOrder.verify(observer, Mockito.times(1)).onNext(list("three", "four")) + inOrder.verify(observer, Mockito.times(1)).onNext(list("five")) + inOrder.verify(observer, Mockito.never()).onNext(Mockito.anyListOf(String.class)) + inOrder.verify(observer, Mockito.never()).onError(Mockito.any(Throwable.class)) + inOrder.verify(observer, Mockito.times(1)).onCompleted() + }*/ + + @Test + public void testInstanceBufferWithOpeningAndClosing() { + def source = Observable.create( + { observer -> + push(observer, "one", 10) + push(observer, "two", 60) + push(observer, "three", 110) + push(observer, "four", 160) + push(observer, "five", 210) + complete(observer, 500) + return Subscriptions.empty() + } + ) + + def openings = Observable.create( + { observer -> + push(observer, BufferOpenings.create(), 50) + push(observer, BufferOpenings.create(), 200) + complete(observer, 250) + return Subscriptions.empty() + } + ) + + def closer = { + opening -> Observable.create( + { observer -> + push(observer, BufferClosings.create(), 100) + complete(observer, 101) + return Subscriptions.empty() + } + ) + } + + def buffered = source.buffer(openings, closer) + buffered.subscribe(observer) + + InOrder inOrder = Mockito.inOrder(observer) + scheduler.advanceTimeTo(500, TimeUnit.MILLISECONDS) + inOrder.verify(observer, Mockito.times(1)).onNext(["two", "three"]) + inOrder.verify(observer, Mockito.times(1)).onNext(["five"]) + inOrder.verify(observer, Mockito.never()).onNext(Mockito.anyListOf(String.class)) + inOrder.verify(observer, Mockito.never()).onError(Mockito.any(Throwable.class)) + inOrder.verify(observer, Mockito.times(1)).onCompleted() + } + + def push(observer, value, delay) { + Action0 onNext = new Action0() { + def void call() { + observer.onNext(value) + } + } + scheduler.schedule(onNext, delay, TimeUnit.MILLISECONDS) + } + + def complete(observer, delay) { + Action0 onCompleted = new Action0() { + def void call() { + observer.onCompleted() + } + } + + scheduler.schedule(onCompleted, delay, TimeUnit.MILLISECONDS) + } def class AsyncObservable implements Func1, Subscription> { diff --git a/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyActionWrapper.java b/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyActionWrapper.java index ada202eaf1..7edf51c471 100644 --- a/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyActionWrapper.java +++ b/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyActionWrapper.java @@ -24,9 +24,11 @@ import rx.util.functions.Action1; import rx.util.functions.FunctionLanguageAdaptor; -public class JRubyActionWrapper implements Action0, Action1 { - private RubyProc proc; - +/** + * Concrete wrapper that accepts a {@code RubyProc} and produces any needed Rx {@code Action}. + * @param + */ +public class JRubyActionWrapper extends JRubyArityChecker implements Action0, Action1 { public JRubyActionWrapper(RubyProc proc) { this.proc = proc; } diff --git a/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyAdaptor.java b/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyAdaptor.java index 5a120b92dc..c804e53023 100644 --- a/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyAdaptor.java +++ b/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyAdaptor.java @@ -38,6 +38,9 @@ import rx.util.functions.Func1; import rx.util.functions.FunctionLanguageAdaptor; +/** + * Defines the single JRuby class {@code RubyProc} that should map to Rx functions + */ public class JRubyAdaptor implements FunctionLanguageAdaptor { @Override diff --git a/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyArityChecker.java b/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyArityChecker.java new file mode 100644 index 0000000000..0bf0d3b937 --- /dev/null +++ b/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyArityChecker.java @@ -0,0 +1,29 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.jruby; + +import org.jruby.RubyProc; + +/** + * Base class for JRuby adaptors that knows how to get the arity of a {@code RubyProc}. + */ +public abstract class JRubyArityChecker { + protected RubyProc proc; + + public int getArity() { + return (int) proc.arity().getLongValue(); + } +} \ No newline at end of file diff --git a/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyFunctionWrapper.java b/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyFunctionWrapper.java index 4fd1dd70fb..d56a39d717 100644 --- a/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyFunctionWrapper.java +++ b/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyFunctionWrapper.java @@ -28,9 +28,15 @@ import rx.util.functions.Func4; import rx.util.functions.FunctionLanguageAdaptor; -public class JRubyFunctionWrapper implements Func0, Func1, Func2, Func3, Func4 { - private RubyProc proc; - +/** + * Concrete wrapper that accepts a {@code RubyProc} and produces any needed Rx {@code Function}. + * @param + * @param + * @param + * @param + * @param + */ +public class JRubyFunctionWrapper extends JRubyArityChecker implements Func0, Func1, Func2, Func3, Func4 { public JRubyFunctionWrapper(RubyProc proc) { this.proc = proc; } From 9577bdecf6503e1c8de784639836f8f57c3a3863 Mon Sep 17 00:00:00 2001 From: Matt Jacobs Date: Tue, 20 Aug 2013 15:34:32 -0700 Subject: [PATCH 8/8] Re-adding UnitTests to Jar generation (except for rxjava-core). This was breaking IDEs --- language-adaptors/codegen/build.gradle | 2 -- language-adaptors/rxjava-clojure/build.gradle | 2 -- language-adaptors/rxjava-groovy/build.gradle | 2 -- language-adaptors/rxjava-jruby/build.gradle | 2 -- 4 files changed, 8 deletions(-) diff --git a/language-adaptors/codegen/build.gradle b/language-adaptors/codegen/build.gradle index 1541b10429..af0e5f2eb8 100644 --- a/language-adaptors/codegen/build.gradle +++ b/language-adaptors/codegen/build.gradle @@ -12,8 +12,6 @@ dependencies { } jar { - exclude('**/*$UnitTest*') - manifest { name = 'rxjava-codegen' instruction 'Bundle-Vendor', 'Netflix' diff --git a/language-adaptors/rxjava-clojure/build.gradle b/language-adaptors/rxjava-clojure/build.gradle index f2f074540e..5731e9acf5 100644 --- a/language-adaptors/rxjava-clojure/build.gradle +++ b/language-adaptors/rxjava-clojure/build.gradle @@ -68,8 +68,6 @@ tasks.jar { } from(codeGenOutputDir) - exclude('**/*$UnitTest*') - manifest { name = 'rxjava-clojure' instruction 'Bundle-Vendor', 'Netflix' diff --git a/language-adaptors/rxjava-groovy/build.gradle b/language-adaptors/rxjava-groovy/build.gradle index b1e6168b26..fea197d263 100644 --- a/language-adaptors/rxjava-groovy/build.gradle +++ b/language-adaptors/rxjava-groovy/build.gradle @@ -39,8 +39,6 @@ tasks.jar { } from(codeGenOutputDir) - exclude('**/*$UnitTest*') - manifest { name = 'rxjava-groovy' instruction 'Bundle-Vendor', 'Netflix' diff --git a/language-adaptors/rxjava-jruby/build.gradle b/language-adaptors/rxjava-jruby/build.gradle index 4359e242ac..be02311889 100644 --- a/language-adaptors/rxjava-jruby/build.gradle +++ b/language-adaptors/rxjava-jruby/build.gradle @@ -34,8 +34,6 @@ tasks.jar { } from(codeGenOutputDir) - exclude('**/*$UnitTest*') - manifest { name = 'rxjava-jruby' instruction 'Bundle-Vendor', 'Netflix'