From 0ab595621a22ba406ac8de65b88a5a609b1bbae3 Mon Sep 17 00:00:00 2001 From: Peter Tackage Date: Fri, 23 Sep 2016 00:07:19 +0200 Subject: [PATCH 01/13] Evaluate Schedule initialization via Callable --- .../io/reactivex/plugins/RxJavaPlugins.java | 61 ++++++++++++++--- .../io/reactivex/schedulers/Schedulers.java | 33 ++++++++-- .../reactivex/plugins/RxJavaPluginsTest.java | 65 ++++++++++++++----- 3 files changed, 127 insertions(+), 32 deletions(-) diff --git a/src/main/java/io/reactivex/plugins/RxJavaPlugins.java b/src/main/java/io/reactivex/plugins/RxJavaPlugins.java index 083efb1426..10439e1c4b 100644 --- a/src/main/java/io/reactivex/plugins/RxJavaPlugins.java +++ b/src/main/java/io/reactivex/plugins/RxJavaPlugins.java @@ -13,6 +13,7 @@ package io.reactivex.plugins; import java.lang.Thread.UncaughtExceptionHandler; +import java.util.concurrent.Callable; import org.reactivestreams.Subscriber; @@ -186,10 +187,10 @@ public static Function getSingleSchedulerHandler() { * @param defaultScheduler the hook's input value * @return the value returned by the hook */ - public static Scheduler initComputationScheduler(Scheduler defaultScheduler) { + public static Scheduler initComputationScheduler(Callable defaultScheduler) { Function f = onInitComputationHandler; if (f == null) { - return defaultScheduler; + return callOrNull(defaultScheduler); } return apply(f, defaultScheduler); // JIT will skip this } @@ -199,10 +200,10 @@ public static Scheduler initComputationScheduler(Scheduler defaultScheduler) { * @param defaultScheduler the hook's input value * @return the value returned by the hook */ - public static Scheduler initIoScheduler(Scheduler defaultScheduler) { + public static Scheduler initIoScheduler(Callable defaultScheduler) { Function f = onInitIoHandler; if (f == null) { - return defaultScheduler; + return callOrNull(defaultScheduler); } return apply(f, defaultScheduler); } @@ -212,10 +213,10 @@ public static Scheduler initIoScheduler(Scheduler defaultScheduler) { * @param defaultScheduler the hook's input value * @return the value returned by the hook */ - public static Scheduler initNewThreadScheduler(Scheduler defaultScheduler) { + public static Scheduler initNewThreadScheduler(Callable defaultScheduler) { Function f = onInitNewThreadHandler; if (f == null) { - return defaultScheduler; + return callOrNull(defaultScheduler); } return apply(f, defaultScheduler); } @@ -225,10 +226,10 @@ public static Scheduler initNewThreadScheduler(Scheduler defaultScheduler) { * @param defaultScheduler the hook's input value * @return the value returned by the hook */ - public static Scheduler initSingleScheduler(Scheduler defaultScheduler) { + public static Scheduler initSingleScheduler(Callable defaultScheduler) { Function f = onInitSingleHandler; if (f == null) { - return defaultScheduler; + return callOrNull(defaultScheduler); } return apply(f, defaultScheduler); } @@ -934,6 +935,23 @@ static R apply(Function f, T t) { } } + /** + * Wraps the call to the function in try-catch and propagates thrown + * checked exceptions as RuntimeException. + * @param the input type + * @param the output type + * @param f the function to call, not null (not verified) + * @param t the {@link Callable} parameter value to the function + * @return the result of the function call + */ + static R apply(Function f, Callable t) { + try { + return f.apply(t.call()); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + /** * Wraps the call to the function in try-catch and propagates thrown * checked exceptions as RuntimeException. @@ -953,6 +971,33 @@ static R apply(BiFunction f, T t, U u) { } } + /** + * Wraps the call to the callable in try-catch and propagates thrown + * checked exceptions as RuntimeException. + * @param the input type + * @param the output type + * @param t the callable, not null (not verified) + * @return the result of the callable call + */ + static T call(Callable t) { + try { + return t.call(); + } catch (Throwable ex) { + throw ExceptionHelper.wrapOrThrow(ex); + } + } + + /** + * Wraps the call to the callable in try-catch and propagates thrown + * checked exceptions as RuntimeException. + * @param the input and output type + * @param t the callable, nullable + * @return the callable result if the callable is nonnull, null otherwise. + */ + static T callOrNull(Callable t) { + return t == null ? null : call(t); + } + /** Helper class, no instances. */ private RxJavaPlugins() { throw new IllegalStateException("No instances!"); diff --git a/src/main/java/io/reactivex/schedulers/Schedulers.java b/src/main/java/io/reactivex/schedulers/Schedulers.java index a645ab5fe9..8ce1e57e7f 100644 --- a/src/main/java/io/reactivex/schedulers/Schedulers.java +++ b/src/main/java/io/reactivex/schedulers/Schedulers.java @@ -13,6 +13,7 @@ package io.reactivex.schedulers; +import java.util.concurrent.Callable; import java.util.concurrent.Executor; import io.reactivex.Scheduler; @@ -45,15 +46,35 @@ public final class Schedulers { static final Scheduler NEW_THREAD; static { - SINGLE = RxJavaPlugins.initSingleScheduler(new SingleScheduler()); - - COMPUTATION = RxJavaPlugins.initComputationScheduler(new ComputationScheduler()); - - IO = RxJavaPlugins.initIoScheduler(new IoScheduler()); + SINGLE = RxJavaPlugins.initSingleScheduler(new Callable() { + @Override + public Scheduler call() throws Exception { + return new SingleScheduler(); + } + }); + + COMPUTATION = RxJavaPlugins.initComputationScheduler(new Callable() { + @Override + public Scheduler call() throws Exception { + return new ComputationScheduler(); + } + }); + + IO = RxJavaPlugins.initIoScheduler(new Callable() { + @Override + public Scheduler call() throws Exception { + return new IoScheduler(); + } + }); TRAMPOLINE = TrampolineScheduler.instance(); - NEW_THREAD = RxJavaPlugins.initNewThreadScheduler(NewThreadScheduler.instance()); + NEW_THREAD = RxJavaPlugins.initNewThreadScheduler(new Callable() { + @Override + public Scheduler call() throws Exception { + return NewThreadScheduler.instance(); + } + }); } /** Utility class. */ diff --git a/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java b/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java index ab37f608e9..3d6614f913 100644 --- a/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java +++ b/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java @@ -339,58 +339,82 @@ public void overrideNewThreadScheduler() { @Test public void overrideInitSingleScheduler() { - Scheduler s = Schedulers.single(); // make sure the Schedulers is initialized + final Scheduler s = Schedulers.single(); // make sure the Schedulers is initialized + Callable c = new Callable() { + @Override + public Scheduler call() throws Exception { + return s; + } + }; try { RxJavaPlugins.setInitSingleSchedulerHandler(replaceWithImmediate); - assertSame(ImmediateThinScheduler.INSTANCE, RxJavaPlugins.initSingleScheduler(s)); + assertSame(ImmediateThinScheduler.INSTANCE, RxJavaPlugins.initSingleScheduler(c)); } finally { RxJavaPlugins.reset(); } // make sure the reset worked - assertSame(s, RxJavaPlugins.initSingleScheduler(s)); + assertSame(s, RxJavaPlugins.initSingleScheduler(c)); } @Test public void overrideInitComputationScheduler() { - Scheduler s = Schedulers.computation(); // make sure the Schedulers is initialized + final Scheduler s = Schedulers.computation(); // make sure the Schedulers is initialized + Callable c = new Callable() { + @Override + public Scheduler call() throws Exception { + return s; + } + }; try { RxJavaPlugins.setInitComputationSchedulerHandler(replaceWithImmediate); - assertSame(ImmediateThinScheduler.INSTANCE, RxJavaPlugins.initComputationScheduler(s)); + assertSame(ImmediateThinScheduler.INSTANCE, RxJavaPlugins.initComputationScheduler(c)); } finally { RxJavaPlugins.reset(); } // make sure the reset worked - assertSame(s, RxJavaPlugins.initComputationScheduler(s)); + assertSame(s, RxJavaPlugins.initComputationScheduler(c)); } @Test public void overrideInitIoScheduler() { - Scheduler s = Schedulers.io(); // make sure the Schedulers is initialized + final Scheduler s = Schedulers.io(); // make sure the Schedulers is initialized; + Callable c = new Callable() { + @Override + public Scheduler call() throws Exception { + return s; + } + }; try { RxJavaPlugins.setInitIoSchedulerHandler(replaceWithImmediate); - assertSame(ImmediateThinScheduler.INSTANCE, RxJavaPlugins.initIoScheduler(s)); + assertSame(ImmediateThinScheduler.INSTANCE, RxJavaPlugins.initIoScheduler(c)); } finally { RxJavaPlugins.reset(); } // make sure the reset worked - assertSame(s, RxJavaPlugins.initIoScheduler(s)); + assertSame(s, RxJavaPlugins.initIoScheduler(c)); } @Test public void overrideInitNewThreadScheduler() { - Scheduler s = Schedulers.newThread(); // make sure the Schedulers is initialized + final Scheduler s = Schedulers.newThread(); // make sure the Schedulers is initialized; + Callable c = new Callable() { + @Override + public Scheduler call() throws Exception { + return s; + } + }; try { RxJavaPlugins.setInitNewThreadSchedulerHandler(replaceWithImmediate); - assertSame(ImmediateThinScheduler.INSTANCE, RxJavaPlugins.initNewThreadScheduler(s)); + assertSame(ImmediateThinScheduler.INSTANCE, RxJavaPlugins.initNewThreadScheduler(c)); } finally { RxJavaPlugins.reset(); } // make sure the reset worked - assertSame(s, RxJavaPlugins.initNewThreadScheduler(s)); + assertSame(s, RxJavaPlugins.initNewThreadScheduler(c)); } @SuppressWarnings("rawtypes") @@ -1153,8 +1177,13 @@ public void onComplete() { assertNull(RxJavaPlugins.onSingleScheduler(null)); - Scheduler s = ImmediateThinScheduler.INSTANCE; - + final Scheduler s = ImmediateThinScheduler.INSTANCE; + Callable c = new Callable() { + @Override + public Scheduler call() throws Exception { + return s; + } + }; assertSame(s, RxJavaPlugins.onComputationScheduler(s)); assertSame(s, RxJavaPlugins.onIoScheduler(s)); @@ -1172,13 +1201,13 @@ public void onComplete() { assertNull(RxJavaPlugins.initSingleScheduler(null)); - assertSame(s, RxJavaPlugins.initComputationScheduler(s)); + assertSame(s, RxJavaPlugins.initComputationScheduler(c)); - assertSame(s, RxJavaPlugins.initIoScheduler(s)); + assertSame(s, RxJavaPlugins.initIoScheduler(c)); - assertSame(s, RxJavaPlugins.initNewThreadScheduler(s)); + assertSame(s, RxJavaPlugins.initNewThreadScheduler(c)); - assertSame(s, RxJavaPlugins.initSingleScheduler(s)); + assertSame(s, RxJavaPlugins.initSingleScheduler(c)); } finally { From 51121173a8196d622540e7fdfdfa8325deac1937 Mon Sep 17 00:00:00 2001 From: Peter Tackage Date: Fri, 23 Sep 2016 00:49:50 +0200 Subject: [PATCH 02/13] Clarify docs that Schedulers are initialized by the return value of the Callable --- src/main/java/io/reactivex/plugins/RxJavaPlugins.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/reactivex/plugins/RxJavaPlugins.java b/src/main/java/io/reactivex/plugins/RxJavaPlugins.java index 10439e1c4b..a9acafcf84 100644 --- a/src/main/java/io/reactivex/plugins/RxJavaPlugins.java +++ b/src/main/java/io/reactivex/plugins/RxJavaPlugins.java @@ -184,7 +184,7 @@ public static Function getSingleSchedulerHandler() { /** * Calls the associated hook function. - * @param defaultScheduler the hook's input value + * @param defaultScheduler a {@link Callable} which returns the hook's input value * @return the value returned by the hook */ public static Scheduler initComputationScheduler(Callable defaultScheduler) { @@ -197,7 +197,7 @@ public static Scheduler initComputationScheduler(Callable defaultSche /** * Calls the associated hook function. - * @param defaultScheduler the hook's input value + * @param defaultScheduler a {@link Callable} which returns the hook's input value * @return the value returned by the hook */ public static Scheduler initIoScheduler(Callable defaultScheduler) { @@ -210,7 +210,7 @@ public static Scheduler initIoScheduler(Callable defaultScheduler) { /** * Calls the associated hook function. - * @param defaultScheduler the hook's input value + * @param defaultScheduler a {@link Callable} which returns the hook's input value * @return the value returned by the hook */ public static Scheduler initNewThreadScheduler(Callable defaultScheduler) { @@ -223,7 +223,7 @@ public static Scheduler initNewThreadScheduler(Callable defaultSchedu /** * Calls the associated hook function. - * @param defaultScheduler the hook's input value + * @param defaultScheduler a {@link Callable} which returns the hook's input value * @return the value returned by the hook */ public static Scheduler initSingleScheduler(Callable defaultScheduler) { From 85e14a97302cf0b17964fbfd0883d7330af48004 Mon Sep 17 00:00:00 2001 From: Peter Tackage Date: Sat, 24 Sep 2016 20:59:54 +0200 Subject: [PATCH 03/13] Enforce non-null Callable Scheduler and Scheduler --- .../io/reactivex/plugins/RxJavaPlugins.java | 37 +++++++++---------- .../reactivex/plugins/RxJavaPluginsTest.java | 30 +++++++++++++++ 2 files changed, 47 insertions(+), 20 deletions(-) diff --git a/src/main/java/io/reactivex/plugins/RxJavaPlugins.java b/src/main/java/io/reactivex/plugins/RxJavaPlugins.java index a9acafcf84..b3549afdbd 100644 --- a/src/main/java/io/reactivex/plugins/RxJavaPlugins.java +++ b/src/main/java/io/reactivex/plugins/RxJavaPlugins.java @@ -15,6 +15,7 @@ import java.lang.Thread.UncaughtExceptionHandler; import java.util.concurrent.Callable; +import io.reactivex.internal.functions.ObjectHelper; import org.reactivestreams.Subscriber; import io.reactivex.*; @@ -188,9 +189,10 @@ public static Function getSingleSchedulerHandler() { * @return the value returned by the hook */ public static Scheduler initComputationScheduler(Callable defaultScheduler) { + ObjectHelper.requireNonNull(defaultScheduler, "Scheduler Callable cannot be null."); Function f = onInitComputationHandler; if (f == null) { - return callOrNull(defaultScheduler); + return call(defaultScheduler); } return apply(f, defaultScheduler); // JIT will skip this } @@ -201,9 +203,10 @@ public static Scheduler initComputationScheduler(Callable defaultSche * @return the value returned by the hook */ public static Scheduler initIoScheduler(Callable defaultScheduler) { + ObjectHelper.requireNonNull(defaultScheduler, "Scheduler Callable cannot be null."); Function f = onInitIoHandler; if (f == null) { - return callOrNull(defaultScheduler); + return call(defaultScheduler); } return apply(f, defaultScheduler); } @@ -214,9 +217,10 @@ public static Scheduler initIoScheduler(Callable defaultScheduler) { * @return the value returned by the hook */ public static Scheduler initNewThreadScheduler(Callable defaultScheduler) { + ObjectHelper.requireNonNull(defaultScheduler, "Scheduler Callable cannot be null."); Function f = onInitNewThreadHandler; if (f == null) { - return callOrNull(defaultScheduler); + return call(defaultScheduler); } return apply(f, defaultScheduler); } @@ -227,9 +231,10 @@ public static Scheduler initNewThreadScheduler(Callable defaultSchedu * @return the value returned by the hook */ public static Scheduler initSingleScheduler(Callable defaultScheduler) { + ObjectHelper.requireNonNull(defaultScheduler, "Scheduler Callable cannot be null."); Function f = onInitSingleHandler; if (f == null) { - return callOrNull(defaultScheduler); + return call(defaultScheduler); } return apply(f, defaultScheduler); } @@ -942,11 +947,13 @@ static R apply(Function f, T t) { * @param the output type * @param f the function to call, not null (not verified) * @param t the {@link Callable} parameter value to the function - * @return the result of the function call + * @return the result of the function call, not null */ static R apply(Function f, Callable t) { try { - return f.apply(t.call()); + T value = t.call(); + ObjectHelper.requireNonNull(t, "Callable result cannot be null."); + return f.apply(value); } catch (Throwable ex) { throw ExceptionHelper.wrapOrThrow(ex); } @@ -975,29 +982,19 @@ static R apply(BiFunction f, T t, U u) { * Wraps the call to the callable in try-catch and propagates thrown * checked exceptions as RuntimeException. * @param the input type - * @param the output type * @param t the callable, not null (not verified) - * @return the result of the callable call + * @return the result of the callable call, not null */ static T call(Callable t) { try { - return t.call(); + T result = t.call(); + ObjectHelper.requireNonNull(result, "Callable result cannot be null."); + return result; } catch (Throwable ex) { throw ExceptionHelper.wrapOrThrow(ex); } } - /** - * Wraps the call to the callable in try-catch and propagates thrown - * checked exceptions as RuntimeException. - * @param the input and output type - * @param t the callable, nullable - * @return the callable result if the callable is nonnull, null otherwise. - */ - static T callOrNull(Callable t) { - return t == null ? null : call(t); - } - /** Helper class, no instances. */ private RxJavaPlugins() { throw new IllegalStateException("No instances!"); diff --git a/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java b/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java index 3d6614f913..335d158a44 100644 --- a/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java +++ b/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java @@ -417,6 +417,36 @@ public Scheduler call() throws Exception { assertSame(s, RxJavaPlugins.initNewThreadScheduler(c)); } + @Test + public void overrideInitSingleSchedulerCrashes() { + // Fail when Callable is null + try { + RxJavaPlugins.initSingleScheduler(null); + fail("Should have thrown NullPointerException"); + } catch (NullPointerException iae) { + // ignore - expected + } finally { + RxJavaPlugins.reset(); + } + + // TODO Verify reset. + + // Fail when Callable result is null + try { + RxJavaPlugins.initSingleScheduler(new Callable() { + @Override + public Scheduler call() throws Exception { + return null; + } + }); + fail("Should have thrown NullPointerException"); + } catch (NullPointerException iae) { + // ignore - expected + } finally { + RxJavaPlugins.reset(); + } + } + @SuppressWarnings("rawtypes") @Test public void observableCreate() { From 1303f47fcac9ea7e430af8c160bc5f27a1371793 Mon Sep 17 00:00:00 2001 From: Peter Tackage Date: Sat, 24 Sep 2016 21:26:11 +0200 Subject: [PATCH 04/13] Add remaining tests and tidy --- .../reactivex/plugins/RxJavaPluginsTest.java | 88 ++++++++++++++----- 1 file changed, 65 insertions(+), 23 deletions(-) diff --git a/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java b/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java index 335d158a44..6bbbd9f679 100644 --- a/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java +++ b/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java @@ -417,6 +417,13 @@ public Scheduler call() throws Exception { assertSame(s, RxJavaPlugins.initNewThreadScheduler(c)); } + Callable nullResultCallable = new Callable() { + @Override + public Scheduler call() throws Exception { + return null; + } + }; + @Test public void overrideInitSingleSchedulerCrashes() { // Fail when Callable is null @@ -424,26 +431,71 @@ public void overrideInitSingleSchedulerCrashes() { RxJavaPlugins.initSingleScheduler(null); fail("Should have thrown NullPointerException"); } catch (NullPointerException iae) { - // ignore - expected - } finally { - RxJavaPlugins.reset(); + // expected } - // TODO Verify reset. + // Fail when Callable result is null + try { + RxJavaPlugins.initSingleScheduler(nullResultCallable); + fail("Should have thrown NullPointerException"); + } catch (NullPointerException iae) { + // expected + } + } + + @Test + public void overrideInitComputationSchedulerCrashes() { + // Fail when Callable is null + try { + RxJavaPlugins.initComputationScheduler(null); + fail("Should have thrown NullPointerException"); + } catch (NullPointerException iae) { + // expected + } // Fail when Callable result is null try { - RxJavaPlugins.initSingleScheduler(new Callable() { - @Override - public Scheduler call() throws Exception { - return null; - } - }); + RxJavaPlugins.initComputationScheduler(nullResultCallable); fail("Should have thrown NullPointerException"); } catch (NullPointerException iae) { - // ignore - expected - } finally { - RxJavaPlugins.reset(); + // expected + } + } + + @Test + public void overrideInitIoSchedulerCrashes() { + // Fail when Callable is null + try { + RxJavaPlugins.initIoScheduler(null); + fail("Should have thrown NullPointerException"); + } catch (NullPointerException iae) { + // expected + } + + // Fail when Callable result is null + try { + RxJavaPlugins.initIoScheduler(nullResultCallable); + fail("Should have thrown NullPointerException"); + } catch (NullPointerException iae) { + // expected + } + } + + @Test + public void overrideInitNewThreadSchedulerCrashes() { + // Fail when Callable is null + try { + RxJavaPlugins.initNewThreadScheduler(null); + fail("Should have thrown NullPointerException"); + } catch (NullPointerException iae) { + // expected + } + // Fail when Callable result is null + try { + RxJavaPlugins.initNewThreadScheduler(nullResultCallable); + fail("Should have thrown NullPointerException"); + } catch (NullPointerException iae) { + // expected } } @@ -1222,15 +1274,6 @@ public Scheduler call() throws Exception { assertSame(s, RxJavaPlugins.onSingleScheduler(s)); - - assertNull(RxJavaPlugins.initComputationScheduler(null)); - - assertNull(RxJavaPlugins.initIoScheduler(null)); - - assertNull(RxJavaPlugins.initNewThreadScheduler(null)); - - assertNull(RxJavaPlugins.initSingleScheduler(null)); - assertSame(s, RxJavaPlugins.initComputationScheduler(c)); assertSame(s, RxJavaPlugins.initIoScheduler(c)); @@ -1239,7 +1282,6 @@ public Scheduler call() throws Exception { assertSame(s, RxJavaPlugins.initSingleScheduler(c)); - } finally { RxJavaPlugins.reset(); } From e5219269b8a4890c8c3cad5b1969c6c642dfd984 Mon Sep 17 00:00:00 2001 From: Peter Tackage Date: Sat, 24 Sep 2016 21:36:23 +0200 Subject: [PATCH 05/13] Expand relevant Javadoc --- .../java/io/reactivex/plugins/RxJavaPlugins.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/reactivex/plugins/RxJavaPlugins.java b/src/main/java/io/reactivex/plugins/RxJavaPlugins.java index b3549afdbd..ea19f289ad 100644 --- a/src/main/java/io/reactivex/plugins/RxJavaPlugins.java +++ b/src/main/java/io/reactivex/plugins/RxJavaPlugins.java @@ -187,6 +187,7 @@ public static Function getSingleSchedulerHandler() { * Calls the associated hook function. * @param defaultScheduler a {@link Callable} which returns the hook's input value * @return the value returned by the hook + * @throws NullPointerException if the callable parameter or its result are null */ public static Scheduler initComputationScheduler(Callable defaultScheduler) { ObjectHelper.requireNonNull(defaultScheduler, "Scheduler Callable cannot be null."); @@ -201,6 +202,7 @@ public static Scheduler initComputationScheduler(Callable defaultSche * Calls the associated hook function. * @param defaultScheduler a {@link Callable} which returns the hook's input value * @return the value returned by the hook + * @throws NullPointerException if the callable parameter or its result are null */ public static Scheduler initIoScheduler(Callable defaultScheduler) { ObjectHelper.requireNonNull(defaultScheduler, "Scheduler Callable cannot be null."); @@ -215,6 +217,7 @@ public static Scheduler initIoScheduler(Callable defaultScheduler) { * Calls the associated hook function. * @param defaultScheduler a {@link Callable} which returns the hook's input value * @return the value returned by the hook + * @throws NullPointerException if the callable parameter or its result are null */ public static Scheduler initNewThreadScheduler(Callable defaultScheduler) { ObjectHelper.requireNonNull(defaultScheduler, "Scheduler Callable cannot be null."); @@ -229,6 +232,7 @@ public static Scheduler initNewThreadScheduler(Callable defaultSchedu * Calls the associated hook function. * @param defaultScheduler a {@link Callable} which returns the hook's input value * @return the value returned by the hook + * @throws NullPointerException if the callable parameter or its result are null */ public static Scheduler initSingleScheduler(Callable defaultScheduler) { ObjectHelper.requireNonNull(defaultScheduler, "Scheduler Callable cannot be null."); @@ -942,12 +946,13 @@ static R apply(Function f, T t) { /** * Wraps the call to the function in try-catch and propagates thrown - * checked exceptions as RuntimeException. + * checked exceptions as RuntimeException. Takes a {@link Callable} to provide the function input value. * @param the input type * @param the output type * @param f the function to call, not null (not verified) - * @param t the {@link Callable} parameter value to the function + * @param t the {@link Callable} parameter value to the function, not null (not verified). Cannot return null. * @return the result of the function call, not null + * @throws NullPointerException if the callable returns null */ static R apply(Function f, Callable t) { try { @@ -982,8 +987,9 @@ static R apply(BiFunction f, T t, U u) { * Wraps the call to the callable in try-catch and propagates thrown * checked exceptions as RuntimeException. * @param the input type - * @param t the callable, not null (not verified) + * @param t the callable, not null (not verified). Cannot return null. * @return the result of the callable call, not null + * @throws NullPointerException if the callable returns null */ static T call(Callable t) { try { From 9ccf4ed937ac6d6a3a23366f60440689323c0f6f Mon Sep 17 00:00:00 2001 From: Peter Tackage Date: Sat, 24 Sep 2016 22:02:00 +0200 Subject: [PATCH 06/13] Make error messages more consistent --- .../java/io/reactivex/plugins/RxJavaPlugins.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/reactivex/plugins/RxJavaPlugins.java b/src/main/java/io/reactivex/plugins/RxJavaPlugins.java index ea19f289ad..0780d764f3 100644 --- a/src/main/java/io/reactivex/plugins/RxJavaPlugins.java +++ b/src/main/java/io/reactivex/plugins/RxJavaPlugins.java @@ -190,7 +190,7 @@ public static Function getSingleSchedulerHandler() { * @throws NullPointerException if the callable parameter or its result are null */ public static Scheduler initComputationScheduler(Callable defaultScheduler) { - ObjectHelper.requireNonNull(defaultScheduler, "Scheduler Callable cannot be null."); + ObjectHelper.requireNonNull(defaultScheduler, "Scheduler Callable can't be null"); Function f = onInitComputationHandler; if (f == null) { return call(defaultScheduler); @@ -205,7 +205,7 @@ public static Scheduler initComputationScheduler(Callable defaultSche * @throws NullPointerException if the callable parameter or its result are null */ public static Scheduler initIoScheduler(Callable defaultScheduler) { - ObjectHelper.requireNonNull(defaultScheduler, "Scheduler Callable cannot be null."); + ObjectHelper.requireNonNull(defaultScheduler, "Scheduler Callable can't be null"); Function f = onInitIoHandler; if (f == null) { return call(defaultScheduler); @@ -220,7 +220,7 @@ public static Scheduler initIoScheduler(Callable defaultScheduler) { * @throws NullPointerException if the callable parameter or its result are null */ public static Scheduler initNewThreadScheduler(Callable defaultScheduler) { - ObjectHelper.requireNonNull(defaultScheduler, "Scheduler Callable cannot be null."); + ObjectHelper.requireNonNull(defaultScheduler, "Scheduler Callable can't be null"); Function f = onInitNewThreadHandler; if (f == null) { return call(defaultScheduler); @@ -235,7 +235,7 @@ public static Scheduler initNewThreadScheduler(Callable defaultSchedu * @throws NullPointerException if the callable parameter or its result are null */ public static Scheduler initSingleScheduler(Callable defaultScheduler) { - ObjectHelper.requireNonNull(defaultScheduler, "Scheduler Callable cannot be null."); + ObjectHelper.requireNonNull(defaultScheduler, "Scheduler Callable can't be null"); Function f = onInitSingleHandler; if (f == null) { return call(defaultScheduler); @@ -957,7 +957,7 @@ static R apply(Function f, T t) { static R apply(Function f, Callable t) { try { T value = t.call(); - ObjectHelper.requireNonNull(t, "Callable result cannot be null."); + ObjectHelper.requireNonNull(t, "Callable result can't be null"); return f.apply(value); } catch (Throwable ex) { throw ExceptionHelper.wrapOrThrow(ex); @@ -994,7 +994,7 @@ static R apply(BiFunction f, T t, U u) { static T call(Callable t) { try { T result = t.call(); - ObjectHelper.requireNonNull(result, "Callable result cannot be null."); + ObjectHelper.requireNonNull(result, "Callable result can't be null"); return result; } catch (Throwable ex) { throw ExceptionHelper.wrapOrThrow(ex); From 38bb3c0c8260f7c174524e76e1d6096c7ca1f92c Mon Sep 17 00:00:00 2001 From: Peter Tackage Date: Sat, 24 Sep 2016 22:19:23 +0200 Subject: [PATCH 07/13] Correct Exception naming --- .../io/reactivex/plugins/RxJavaPluginsTest.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java b/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java index 6bbbd9f679..cfc01ff582 100644 --- a/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java +++ b/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java @@ -430,7 +430,7 @@ public void overrideInitSingleSchedulerCrashes() { try { RxJavaPlugins.initSingleScheduler(null); fail("Should have thrown NullPointerException"); - } catch (NullPointerException iae) { + } catch (NullPointerException npe) { // expected } @@ -438,7 +438,7 @@ public void overrideInitSingleSchedulerCrashes() { try { RxJavaPlugins.initSingleScheduler(nullResultCallable); fail("Should have thrown NullPointerException"); - } catch (NullPointerException iae) { + } catch (NullPointerException npe) { // expected } } @@ -449,7 +449,7 @@ public void overrideInitComputationSchedulerCrashes() { try { RxJavaPlugins.initComputationScheduler(null); fail("Should have thrown NullPointerException"); - } catch (NullPointerException iae) { + } catch (NullPointerException npe) { // expected } @@ -457,7 +457,7 @@ public void overrideInitComputationSchedulerCrashes() { try { RxJavaPlugins.initComputationScheduler(nullResultCallable); fail("Should have thrown NullPointerException"); - } catch (NullPointerException iae) { + } catch (NullPointerException npe) { // expected } } @@ -468,7 +468,7 @@ public void overrideInitIoSchedulerCrashes() { try { RxJavaPlugins.initIoScheduler(null); fail("Should have thrown NullPointerException"); - } catch (NullPointerException iae) { + } catch (NullPointerException npe) { // expected } @@ -476,7 +476,7 @@ public void overrideInitIoSchedulerCrashes() { try { RxJavaPlugins.initIoScheduler(nullResultCallable); fail("Should have thrown NullPointerException"); - } catch (NullPointerException iae) { + } catch (NullPointerException npe) { // expected } } @@ -487,14 +487,14 @@ public void overrideInitNewThreadSchedulerCrashes() { try { RxJavaPlugins.initNewThreadScheduler(null); fail("Should have thrown NullPointerException"); - } catch (NullPointerException iae) { + } catch (NullPointerException npe) { // expected } // Fail when Callable result is null try { RxJavaPlugins.initNewThreadScheduler(nullResultCallable); fail("Should have thrown NullPointerException"); - } catch (NullPointerException iae) { + } catch (NullPointerException npe) { // expected } } From 174ce7acbb44a3be3eaa33b1a89380eed4f65526 Mon Sep 17 00:00:00 2001 From: Peter Tackage Date: Sat, 24 Sep 2016 22:23:53 +0200 Subject: [PATCH 08/13] Add test for Exception message to verify root cause --- .../io/reactivex/plugins/RxJavaPluginsTest.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java b/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java index cfc01ff582..1f87e1a4f7 100644 --- a/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java +++ b/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java @@ -431,7 +431,7 @@ public void overrideInitSingleSchedulerCrashes() { RxJavaPlugins.initSingleScheduler(null); fail("Should have thrown NullPointerException"); } catch (NullPointerException npe) { - // expected + assertEquals("Scheduler Callable can't be null", npe.getMessage()); } // Fail when Callable result is null @@ -439,7 +439,7 @@ public void overrideInitSingleSchedulerCrashes() { RxJavaPlugins.initSingleScheduler(nullResultCallable); fail("Should have thrown NullPointerException"); } catch (NullPointerException npe) { - // expected + assertEquals("Callable result can't be null", npe.getMessage()); } } @@ -450,7 +450,7 @@ public void overrideInitComputationSchedulerCrashes() { RxJavaPlugins.initComputationScheduler(null); fail("Should have thrown NullPointerException"); } catch (NullPointerException npe) { - // expected + assertEquals("Scheduler Callable can't be null", npe.getMessage()); } // Fail when Callable result is null @@ -458,7 +458,7 @@ public void overrideInitComputationSchedulerCrashes() { RxJavaPlugins.initComputationScheduler(nullResultCallable); fail("Should have thrown NullPointerException"); } catch (NullPointerException npe) { - // expected + assertEquals("Callable result can't be null", npe.getMessage()); } } @@ -470,6 +470,7 @@ public void overrideInitIoSchedulerCrashes() { fail("Should have thrown NullPointerException"); } catch (NullPointerException npe) { // expected + assertEquals("Scheduler Callable can't be null", npe.getMessage()); } // Fail when Callable result is null @@ -477,7 +478,7 @@ public void overrideInitIoSchedulerCrashes() { RxJavaPlugins.initIoScheduler(nullResultCallable); fail("Should have thrown NullPointerException"); } catch (NullPointerException npe) { - // expected + assertEquals("Callable result can't be null", npe.getMessage()); } } @@ -489,13 +490,14 @@ public void overrideInitNewThreadSchedulerCrashes() { fail("Should have thrown NullPointerException"); } catch (NullPointerException npe) { // expected + assertEquals("Scheduler Callable can't be null", npe.getMessage()); } // Fail when Callable result is null try { RxJavaPlugins.initNewThreadScheduler(nullResultCallable); fail("Should have thrown NullPointerException"); } catch (NullPointerException npe) { - // expected + assertEquals("Callable result can't be null", npe.getMessage()); } } From aeafcc7054cd513284b745731a42cdbd3ded484f Mon Sep 17 00:00:00 2001 From: Peter Tackage Date: Sat, 24 Sep 2016 22:37:12 +0200 Subject: [PATCH 09/13] Add tests for alternative initialization path --- .../io/reactivex/plugins/RxJavaPlugins.java | 6 +- .../reactivex/plugins/RxJavaPluginsTest.java | 74 ++++++++++++++++--- 2 files changed, 68 insertions(+), 12 deletions(-) diff --git a/src/main/java/io/reactivex/plugins/RxJavaPlugins.java b/src/main/java/io/reactivex/plugins/RxJavaPlugins.java index 0780d764f3..5ecdbc98d6 100644 --- a/src/main/java/io/reactivex/plugins/RxJavaPlugins.java +++ b/src/main/java/io/reactivex/plugins/RxJavaPlugins.java @@ -956,9 +956,9 @@ static R apply(Function f, T t) { */ static R apply(Function f, Callable t) { try { - T value = t.call(); - ObjectHelper.requireNonNull(t, "Callable result can't be null"); - return f.apply(value); + T result = t.call(); + ObjectHelper.requireNonNull(result, "Callable result can't be null"); + return f.apply(result); } catch (Throwable ex) { throw ExceptionHelper.wrapOrThrow(ex); } diff --git a/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java b/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java index 1f87e1a4f7..e11b6ff7a1 100644 --- a/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java +++ b/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java @@ -426,7 +426,7 @@ public Scheduler call() throws Exception { @Test public void overrideInitSingleSchedulerCrashes() { - // Fail when Callable is null + // fail when Callable is null try { RxJavaPlugins.initSingleScheduler(null); fail("Should have thrown NullPointerException"); @@ -434,18 +434,32 @@ public void overrideInitSingleSchedulerCrashes() { assertEquals("Scheduler Callable can't be null", npe.getMessage()); } - // Fail when Callable result is null + // fail when Callable result is null try { RxJavaPlugins.initSingleScheduler(nullResultCallable); fail("Should have thrown NullPointerException"); } catch (NullPointerException npe) { assertEquals("Callable result can't be null", npe.getMessage()); } + + // fail when Callable result is null and initHandler set + try { + RxJavaPlugins.setInitSingleSchedulerHandler(replaceWithImmediate); + RxJavaPlugins.initSingleScheduler(nullResultCallable); + fail("Should have thrown NullPointerException"); + } catch (NullPointerException npe) { + assertEquals("Callable result can't be null", npe.getMessage()); + } finally { + RxJavaPlugins.reset(); + } + + // make sure the reset worked + assertNotSame(ImmediateThinScheduler.INSTANCE, Schedulers.single()); } @Test public void overrideInitComputationSchedulerCrashes() { - // Fail when Callable is null + // fail when Callable is null try { RxJavaPlugins.initComputationScheduler(null); fail("Should have thrown NullPointerException"); @@ -453,38 +467,65 @@ public void overrideInitComputationSchedulerCrashes() { assertEquals("Scheduler Callable can't be null", npe.getMessage()); } - // Fail when Callable result is null + // fail when Callable result is null try { RxJavaPlugins.initComputationScheduler(nullResultCallable); fail("Should have thrown NullPointerException"); } catch (NullPointerException npe) { assertEquals("Callable result can't be null", npe.getMessage()); } + + // fail when Callable result is null and initHandler set + try { + RxJavaPlugins.setInitComputationSchedulerHandler(replaceWithImmediate); + RxJavaPlugins.initComputationScheduler(nullResultCallable); + fail("Should have thrown NullPointerException"); + } catch (NullPointerException npe) { + assertEquals("Callable result can't be null", npe.getMessage()); + } finally { + RxJavaPlugins.reset(); + } + + // make sure the reset worked + assertNotSame(ImmediateThinScheduler.INSTANCE, Schedulers.computation()); } @Test public void overrideInitIoSchedulerCrashes() { - // Fail when Callable is null + // fail when Callable is null try { RxJavaPlugins.initIoScheduler(null); fail("Should have thrown NullPointerException"); } catch (NullPointerException npe) { - // expected assertEquals("Scheduler Callable can't be null", npe.getMessage()); } - // Fail when Callable result is null + // fail when Callable result is null try { RxJavaPlugins.initIoScheduler(nullResultCallable); fail("Should have thrown NullPointerException"); } catch (NullPointerException npe) { assertEquals("Callable result can't be null", npe.getMessage()); } + + // fail when Callable result is null and initHandler set + try { + RxJavaPlugins.setInitIoSchedulerHandler(replaceWithImmediate); + RxJavaPlugins.initIoScheduler(nullResultCallable); + fail("Should have thrown NullPointerException"); + } catch (NullPointerException npe) { + assertEquals("Callable result can't be null", npe.getMessage()); + } finally { + RxJavaPlugins.reset(); + } + + // make sure the reset worked + assertNotSame(ImmediateThinScheduler.INSTANCE, Schedulers.io()); } @Test public void overrideInitNewThreadSchedulerCrashes() { - // Fail when Callable is null + // fail when Callable is null try { RxJavaPlugins.initNewThreadScheduler(null); fail("Should have thrown NullPointerException"); @@ -492,13 +533,28 @@ public void overrideInitNewThreadSchedulerCrashes() { // expected assertEquals("Scheduler Callable can't be null", npe.getMessage()); } - // Fail when Callable result is null + + // fail when Callable result is null + try { + RxJavaPlugins.initNewThreadScheduler(nullResultCallable); + fail("Should have thrown NullPointerException"); + } catch (NullPointerException npe) { + assertEquals("Callable result can't be null", npe.getMessage()); + } + + // fail when Callable result is null and initHandler set try { + RxJavaPlugins.setInitNewThreadSchedulerHandler(replaceWithImmediate); RxJavaPlugins.initNewThreadScheduler(nullResultCallable); fail("Should have thrown NullPointerException"); } catch (NullPointerException npe) { assertEquals("Callable result can't be null", npe.getMessage()); + } finally { + RxJavaPlugins.reset(); } + + // make sure the reset worked + assertNotSame(ImmediateThinScheduler.INSTANCE, Schedulers.newThread()); } @SuppressWarnings("rawtypes") From 786090dcc2954cf970f8d8f3a1ae46fc741f8864 Mon Sep 17 00:00:00 2001 From: Peter Tackage Date: Sat, 24 Sep 2016 22:40:06 +0200 Subject: [PATCH 10/13] Simplify statement --- src/main/java/io/reactivex/plugins/RxJavaPlugins.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/io/reactivex/plugins/RxJavaPlugins.java b/src/main/java/io/reactivex/plugins/RxJavaPlugins.java index 5ecdbc98d6..215c1370b4 100644 --- a/src/main/java/io/reactivex/plugins/RxJavaPlugins.java +++ b/src/main/java/io/reactivex/plugins/RxJavaPlugins.java @@ -994,8 +994,7 @@ static R apply(BiFunction f, T t, U u) { static T call(Callable t) { try { T result = t.call(); - ObjectHelper.requireNonNull(result, "Callable result can't be null"); - return result; + return ObjectHelper.requireNonNull(result, "Callable result can't be null"); } catch (Throwable ex) { throw ExceptionHelper.wrapOrThrow(ex); } From c191e3190fbd65fe43acb4214fad0b80253b8edd Mon Sep 17 00:00:00 2001 From: Peter Tackage Date: Sat, 24 Sep 2016 22:56:44 +0200 Subject: [PATCH 11/13] Use holder pattern for default Scheduler instances --- .../io/reactivex/schedulers/Schedulers.java | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/reactivex/schedulers/Schedulers.java b/src/main/java/io/reactivex/schedulers/Schedulers.java index 8ce1e57e7f..9f5fd75038 100644 --- a/src/main/java/io/reactivex/schedulers/Schedulers.java +++ b/src/main/java/io/reactivex/schedulers/Schedulers.java @@ -45,25 +45,41 @@ public final class Schedulers { static final Scheduler NEW_THREAD; + static final class SingleHolder { + static final Scheduler DEFAULT = new SingleScheduler(); + } + + static final class ComputationHolder { + static final Scheduler DEFAULT = new ComputationScheduler(); + } + + static final class IoHolder { + static final Scheduler DEFAULT = new IoScheduler(); + } + + static final class NewThreadHolder { + static final Scheduler DEFAULT = NewThreadScheduler.instance(); + } + static { SINGLE = RxJavaPlugins.initSingleScheduler(new Callable() { @Override public Scheduler call() throws Exception { - return new SingleScheduler(); + return SingleHolder.DEFAULT; } }); COMPUTATION = RxJavaPlugins.initComputationScheduler(new Callable() { @Override public Scheduler call() throws Exception { - return new ComputationScheduler(); + return ComputationHolder.DEFAULT; } }); IO = RxJavaPlugins.initIoScheduler(new Callable() { @Override public Scheduler call() throws Exception { - return new IoScheduler(); + return IoHolder.DEFAULT; } }); @@ -72,7 +88,7 @@ public Scheduler call() throws Exception { NEW_THREAD = RxJavaPlugins.initNewThreadScheduler(new Callable() { @Override public Scheduler call() throws Exception { - return NewThreadScheduler.instance(); + return NewThreadHolder.DEFAULT; } }); } From fff28b247a3445830ca38db7461f2735a3dfa437 Mon Sep 17 00:00:00 2001 From: Peter Tackage Date: Sun, 25 Sep 2016 09:58:01 +0200 Subject: [PATCH 12/13] Use correct scheduler when verifying reset --- src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java b/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java index e11b6ff7a1..9c78206a00 100644 --- a/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java +++ b/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java @@ -295,7 +295,7 @@ public void overrideSingleScheduler() { RxJavaPlugins.reset(); } // make sure the reset worked - assertNotSame(ImmediateThinScheduler.INSTANCE, Schedulers.computation()); + assertNotSame(ImmediateThinScheduler.INSTANCE, Schedulers.single()); } @Test From a273507bc8d66c671b878b496f491f5e29f4c8b1 Mon Sep 17 00:00:00 2001 From: Peter Tackage Date: Sun, 25 Sep 2016 12:57:54 +0200 Subject: [PATCH 13/13] Make onInitHandler functions lazy and enforce non null. --- .../io/reactivex/plugins/RxJavaPlugins.java | 110 +++++++-------- .../reactivex/plugins/RxJavaPluginsTest.java | 126 ++++++++++-------- 2 files changed, 120 insertions(+), 116 deletions(-) diff --git a/src/main/java/io/reactivex/plugins/RxJavaPlugins.java b/src/main/java/io/reactivex/plugins/RxJavaPlugins.java index 215c1370b4..18c605bd68 100644 --- a/src/main/java/io/reactivex/plugins/RxJavaPlugins.java +++ b/src/main/java/io/reactivex/plugins/RxJavaPlugins.java @@ -33,13 +33,13 @@ public final class RxJavaPlugins { static volatile Function onScheduleHandler; - static volatile Function onInitComputationHandler; + static volatile Function, Scheduler> onInitComputationHandler; - static volatile Function onInitSingleHandler; + static volatile Function, Scheduler> onInitSingleHandler; - static volatile Function onInitIoHandler; + static volatile Function, Scheduler> onInitIoHandler; - static volatile Function onInitNewThreadHandler; + static volatile Function, Scheduler> onInitNewThreadHandler; static volatile Function onComputationHandler; @@ -123,7 +123,7 @@ public static Consumer getErrorHandler() { * Returns the current hook function. * @return the hook function, may be null */ - public static Function getInitComputationSchedulerHandler() { + public static Function, Scheduler> getInitComputationSchedulerHandler() { return onInitComputationHandler; } @@ -131,7 +131,7 @@ public static Function getInitComputationSchedulerHandler( * Returns the current hook function. * @return the hook function, may be null */ - public static Function getInitIoSchedulerHandler() { + public static Function, Scheduler> getInitIoSchedulerHandler() { return onInitIoHandler; } @@ -139,7 +139,7 @@ public static Function getInitIoSchedulerHandler() { * Returns the current hook function. * @return the hook function, may be null */ - public static Function getInitNewThreadSchedulerHandler() { + public static Function, Scheduler> getInitNewThreadSchedulerHandler() { return onInitNewThreadHandler; } @@ -147,7 +147,7 @@ public static Function getInitNewThreadSchedulerHandler() * Returns the current hook function. * @return the hook function, may be null */ - public static Function getInitSingleSchedulerHandler() { + public static Function, Scheduler> getInitSingleSchedulerHandler() { return onInitSingleHandler; } @@ -186,61 +186,61 @@ public static Function getSingleSchedulerHandler() { /** * Calls the associated hook function. * @param defaultScheduler a {@link Callable} which returns the hook's input value - * @return the value returned by the hook + * @return the value returned by the hook, not null * @throws NullPointerException if the callable parameter or its result are null */ public static Scheduler initComputationScheduler(Callable defaultScheduler) { ObjectHelper.requireNonNull(defaultScheduler, "Scheduler Callable can't be null"); - Function f = onInitComputationHandler; + Function, Scheduler> f = onInitComputationHandler; if (f == null) { - return call(defaultScheduler); + return callRequireNonNull(defaultScheduler); } - return apply(f, defaultScheduler); // JIT will skip this + return applyRequireNonNull(f, defaultScheduler); // JIT will skip this } /** * Calls the associated hook function. * @param defaultScheduler a {@link Callable} which returns the hook's input value - * @return the value returned by the hook + * @return the value returned by the hook, not null * @throws NullPointerException if the callable parameter or its result are null */ public static Scheduler initIoScheduler(Callable defaultScheduler) { ObjectHelper.requireNonNull(defaultScheduler, "Scheduler Callable can't be null"); - Function f = onInitIoHandler; + Function, Scheduler> f = onInitIoHandler; if (f == null) { - return call(defaultScheduler); + return callRequireNonNull(defaultScheduler); } - return apply(f, defaultScheduler); + return applyRequireNonNull(f, defaultScheduler); } /** * Calls the associated hook function. * @param defaultScheduler a {@link Callable} which returns the hook's input value - * @return the value returned by the hook + * @return the value returned by the hook, not null * @throws NullPointerException if the callable parameter or its result are null */ public static Scheduler initNewThreadScheduler(Callable defaultScheduler) { ObjectHelper.requireNonNull(defaultScheduler, "Scheduler Callable can't be null"); - Function f = onInitNewThreadHandler; + Function, Scheduler> f = onInitNewThreadHandler; if (f == null) { - return call(defaultScheduler); + return callRequireNonNull(defaultScheduler); } - return apply(f, defaultScheduler); + return applyRequireNonNull(f, defaultScheduler); } /** * Calls the associated hook function. * @param defaultScheduler a {@link Callable} which returns the hook's input value - * @return the value returned by the hook + * @return the value returned by the hook, not null * @throws NullPointerException if the callable parameter or its result are null */ public static Scheduler initSingleScheduler(Callable defaultScheduler) { ObjectHelper.requireNonNull(defaultScheduler, "Scheduler Callable can't be null"); - Function f = onInitSingleHandler; + Function, Scheduler> f = onInitSingleHandler; if (f == null) { - return call(defaultScheduler); + return callRequireNonNull(defaultScheduler); } - return apply(f, defaultScheduler); + return applyRequireNonNull(f, defaultScheduler); } /** @@ -403,9 +403,9 @@ public static void setErrorHandler(Consumer handler) { /** * Sets the specific hook function. - * @param handler the hook function to set, null allowed + * @param handler the hook function to set, null allowed, but the function may not return null */ - public static void setInitComputationSchedulerHandler(Function handler) { + public static void setInitComputationSchedulerHandler(Function, Scheduler> handler) { if (lockdown) { throw new IllegalStateException("Plugins can't be changed anymore"); } @@ -414,9 +414,9 @@ public static void setInitComputationSchedulerHandler(Function handler) { + public static void setInitIoSchedulerHandler(Function, Scheduler> handler) { if (lockdown) { throw new IllegalStateException("Plugins can't be changed anymore"); } @@ -425,9 +425,9 @@ public static void setInitIoSchedulerHandler(Function hand /** * Sets the specific hook function. - * @param handler the hook function to set, null allowed + * @param handler the hook function to set, null allowed, but the function may not return null */ - public static void setInitNewThreadSchedulerHandler(Function handler) { + public static void setInitNewThreadSchedulerHandler(Function, Scheduler> handler) { if (lockdown) { throw new IllegalStateException("Plugins can't be changed anymore"); } @@ -436,9 +436,9 @@ public static void setInitNewThreadSchedulerHandler(Function handler) { + public static void setInitSingleSchedulerHandler(Function, Scheduler> handler) { if (lockdown) { throw new IllegalStateException("Plugins can't be changed anymore"); } @@ -944,26 +944,6 @@ static R apply(Function f, T t) { } } - /** - * Wraps the call to the function in try-catch and propagates thrown - * checked exceptions as RuntimeException. Takes a {@link Callable} to provide the function input value. - * @param the input type - * @param the output type - * @param f the function to call, not null (not verified) - * @param t the {@link Callable} parameter value to the function, not null (not verified). Cannot return null. - * @return the result of the function call, not null - * @throws NullPointerException if the callable returns null - */ - static R apply(Function f, Callable t) { - try { - T result = t.call(); - ObjectHelper.requireNonNull(result, "Callable result can't be null"); - return f.apply(result); - } catch (Throwable ex) { - throw ExceptionHelper.wrapOrThrow(ex); - } - } - /** * Wraps the call to the function in try-catch and propagates thrown * checked exceptions as RuntimeException. @@ -984,22 +964,32 @@ static R apply(BiFunction f, T t, U u) { } /** - * Wraps the call to the callable in try-catch and propagates thrown - * checked exceptions as RuntimeException. - * @param the input type - * @param t the callable, not null (not verified). Cannot return null. + * Wraps the call to the Scheduler creation callable in try-catch and propagates thrown + * checked exceptions as RuntimeException and enforces that result is not null. + * @param s the {@link Callable} which returns a {@link Scheduler}, not null (not verified). Cannot return null * @return the result of the callable call, not null - * @throws NullPointerException if the callable returns null + * @throws NullPointerException if the callable parameter returns null */ - static T call(Callable t) { + static Scheduler callRequireNonNull(Callable s) { try { - T result = t.call(); - return ObjectHelper.requireNonNull(result, "Callable result can't be null"); + return ObjectHelper.requireNonNull(s.call(), "Scheduler Callable result can't be null"); } catch (Throwable ex) { throw ExceptionHelper.wrapOrThrow(ex); } } + /** + * Wraps the call to the Scheduler creation function in try-catch and propagates thrown + * checked exceptions as RuntimeException and enforces that result is not null. + * @param f the function to call, not null (not verified). Cannot return null + * @param s the parameter value to the function + * @return the result of the function call, not null + * @throws NullPointerException if the function parameter returns null + */ + static Scheduler applyRequireNonNull(Function, Scheduler> f, Callable s) { + return ObjectHelper.requireNonNull(apply(f, s), "Scheduler Callable result can't be null"); + } + /** Helper class, no instances. */ private RxJavaPlugins() { throw new IllegalStateException("No instances!"); diff --git a/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java b/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java index 9c78206a00..d387ef4241 100644 --- a/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java +++ b/src/test/java/io/reactivex/plugins/RxJavaPluginsTest.java @@ -337,6 +337,13 @@ public void overrideNewThreadScheduler() { assertNotSame(ImmediateThinScheduler.INSTANCE, Schedulers.newThread()); } + Function, Scheduler> initReplaceWithImmediate = new Function, Scheduler>() { + @Override + public Scheduler apply(Callable t) { + return ImmediateThinScheduler.INSTANCE; + } + }; + @Test public void overrideInitSingleScheduler() { final Scheduler s = Schedulers.single(); // make sure the Schedulers is initialized @@ -347,7 +354,7 @@ public Scheduler call() throws Exception { } }; try { - RxJavaPlugins.setInitSingleSchedulerHandler(replaceWithImmediate); + RxJavaPlugins.setInitSingleSchedulerHandler(initReplaceWithImmediate); assertSame(ImmediateThinScheduler.INSTANCE, RxJavaPlugins.initSingleScheduler(c)); } finally { @@ -367,7 +374,7 @@ public Scheduler call() throws Exception { } }; try { - RxJavaPlugins.setInitComputationSchedulerHandler(replaceWithImmediate); + RxJavaPlugins.setInitComputationSchedulerHandler(initReplaceWithImmediate); assertSame(ImmediateThinScheduler.INSTANCE, RxJavaPlugins.initComputationScheduler(c)); } finally { @@ -387,7 +394,7 @@ public Scheduler call() throws Exception { } }; try { - RxJavaPlugins.setInitIoSchedulerHandler(replaceWithImmediate); + RxJavaPlugins.setInitIoSchedulerHandler(initReplaceWithImmediate); assertSame(ImmediateThinScheduler.INSTANCE, RxJavaPlugins.initIoScheduler(c)); } finally { @@ -407,7 +414,7 @@ public Scheduler call() throws Exception { } }; try { - RxJavaPlugins.setInitNewThreadSchedulerHandler(replaceWithImmediate); + RxJavaPlugins.setInitNewThreadSchedulerHandler(initReplaceWithImmediate); assertSame(ImmediateThinScheduler.INSTANCE, RxJavaPlugins.initNewThreadScheduler(c)); } finally { @@ -439,22 +446,8 @@ public void overrideInitSingleSchedulerCrashes() { RxJavaPlugins.initSingleScheduler(nullResultCallable); fail("Should have thrown NullPointerException"); } catch (NullPointerException npe) { - assertEquals("Callable result can't be null", npe.getMessage()); - } - - // fail when Callable result is null and initHandler set - try { - RxJavaPlugins.setInitSingleSchedulerHandler(replaceWithImmediate); - RxJavaPlugins.initSingleScheduler(nullResultCallable); - fail("Should have thrown NullPointerException"); - } catch (NullPointerException npe) { - assertEquals("Callable result can't be null", npe.getMessage()); - } finally { - RxJavaPlugins.reset(); + assertEquals("Scheduler Callable result can't be null", npe.getMessage()); } - - // make sure the reset worked - assertNotSame(ImmediateThinScheduler.INSTANCE, Schedulers.single()); } @Test @@ -472,22 +465,8 @@ public void overrideInitComputationSchedulerCrashes() { RxJavaPlugins.initComputationScheduler(nullResultCallable); fail("Should have thrown NullPointerException"); } catch (NullPointerException npe) { - assertEquals("Callable result can't be null", npe.getMessage()); + assertEquals("Scheduler Callable result can't be null", npe.getMessage()); } - - // fail when Callable result is null and initHandler set - try { - RxJavaPlugins.setInitComputationSchedulerHandler(replaceWithImmediate); - RxJavaPlugins.initComputationScheduler(nullResultCallable); - fail("Should have thrown NullPointerException"); - } catch (NullPointerException npe) { - assertEquals("Callable result can't be null", npe.getMessage()); - } finally { - RxJavaPlugins.reset(); - } - - // make sure the reset worked - assertNotSame(ImmediateThinScheduler.INSTANCE, Schedulers.computation()); } @Test @@ -505,22 +484,8 @@ public void overrideInitIoSchedulerCrashes() { RxJavaPlugins.initIoScheduler(nullResultCallable); fail("Should have thrown NullPointerException"); } catch (NullPointerException npe) { - assertEquals("Callable result can't be null", npe.getMessage()); + assertEquals("Scheduler Callable result can't be null", npe.getMessage()); } - - // fail when Callable result is null and initHandler set - try { - RxJavaPlugins.setInitIoSchedulerHandler(replaceWithImmediate); - RxJavaPlugins.initIoScheduler(nullResultCallable); - fail("Should have thrown NullPointerException"); - } catch (NullPointerException npe) { - assertEquals("Callable result can't be null", npe.getMessage()); - } finally { - RxJavaPlugins.reset(); - } - - // make sure the reset worked - assertNotSame(ImmediateThinScheduler.INSTANCE, Schedulers.io()); } @Test @@ -539,16 +504,65 @@ public void overrideInitNewThreadSchedulerCrashes() { RxJavaPlugins.initNewThreadScheduler(nullResultCallable); fail("Should have thrown NullPointerException"); } catch (NullPointerException npe) { - assertEquals("Callable result can't be null", npe.getMessage()); + assertEquals("Scheduler Callable result can't be null", npe.getMessage()); + } + } + + Callable unsafeDefault = new Callable() { + @Override + public Scheduler call() throws Exception { + throw new AssertionError("Default Scheduler instance should not have been evaluated"); } + }; - // fail when Callable result is null and initHandler set + @Test + public void testDefaultSingleSchedulerIsInitializedLazily() { + // unsafe default Scheduler Callable should not be evaluated try { - RxJavaPlugins.setInitNewThreadSchedulerHandler(replaceWithImmediate); - RxJavaPlugins.initNewThreadScheduler(nullResultCallable); - fail("Should have thrown NullPointerException"); - } catch (NullPointerException npe) { - assertEquals("Callable result can't be null", npe.getMessage()); + RxJavaPlugins.setInitSingleSchedulerHandler(initReplaceWithImmediate); + RxJavaPlugins.initSingleScheduler(unsafeDefault); + } finally { + RxJavaPlugins.reset(); + } + + // make sure the reset worked + assertNotSame(ImmediateThinScheduler.INSTANCE, Schedulers.single()); + } + + @Test + public void testDefaultIoSchedulerIsInitializedLazily() { + // unsafe default Scheduler Callable should not be evaluated + try { + RxJavaPlugins.setInitIoSchedulerHandler(initReplaceWithImmediate); + RxJavaPlugins.initIoScheduler(unsafeDefault); + } finally { + RxJavaPlugins.reset(); + } + + // make sure the reset worked + assertNotSame(ImmediateThinScheduler.INSTANCE, Schedulers.io()); + } + + @Test + public void testDefaultComputationSchedulerIsInitializedLazily() { + // unsafe default Scheduler Callable should not be evaluated + try { + RxJavaPlugins.setInitComputationSchedulerHandler(initReplaceWithImmediate); + RxJavaPlugins.initComputationScheduler(unsafeDefault); + } finally { + RxJavaPlugins.reset(); + } + + // make sure the reset worked + assertNotSame(ImmediateThinScheduler.INSTANCE, Schedulers.computation()); + } + + @Test + public void testDefaultNewThreadSchedulerIsInitializedLazily() { + // unsafe default Scheduler Callable should not be evaluated + try { + RxJavaPlugins.setInitNewThreadSchedulerHandler(initReplaceWithImmediate); + RxJavaPlugins.initNewThreadScheduler(unsafeDefault); } finally { RxJavaPlugins.reset(); }