1 package org.mozilla.geckoview;
2 
3 import org.mozilla.gecko.annotation.WrapForJNI;
4 import org.mozilla.gecko.mozglue.JNIObject;
5 import org.mozilla.gecko.util.IXPCOMEventTarget;
6 import org.mozilla.gecko.util.ThreadUtils;
7 import org.mozilla.gecko.util.XPCOMEventTarget;
8 
9 import android.os.Handler;
10 import android.os.Looper;
11 import android.os.SystemClock;
12 import androidx.annotation.AnyThread;
13 import androidx.annotation.NonNull;
14 import androidx.annotation.Nullable;
15 import androidx.collection.SimpleArrayMap;
16 
17 import java.util.ArrayList;
18 import java.util.Arrays;
19 import java.util.Collections;
20 import java.util.List;
21 import java.util.ListIterator;
22 import java.util.concurrent.CancellationException;
23 import java.util.concurrent.TimeoutException;
24 
25 /**
26  * GeckoResult is a class that represents an asynchronous result. The result is initially pending,
27  * and at a later time, the result may be completed with {@link #complete a value} or {@link
28  * #completeExceptionally an exception} depending on the outcome of the asynchronous operation. For
29  * example,<pre>
30  * public GeckoResult&lt;Integer&gt; divide(final int dividend, final int divisor) {
31  *     final GeckoResult&lt;Integer&gt; result = new GeckoResult&lt;&gt;();
32  *     (new Thread(() -&gt; {
33  *         if (divisor != 0) {
34  *             result.complete(dividend / divisor);
35  *         } else {
36  *             result.completeExceptionally(new ArithmeticException("Dividing by zero"));
37  *         }
38  *     })).start();
39  *     return result;
40  * }</pre>
41  * <p>
42  * To retrieve the completed value or exception, use one of the {@link #then} methods to register
43  * listeners on the result. Listeners are run on the thread where the GeckoResult is created if a
44  * {@link Looper} is present. For example,
45  * to retrieve a completed value,<pre>
46  * divide(42, 2).then(new GeckoResult.OnValueListener&lt;Integer, Void&gt;() {
47  *     &#64;Override
48  *     public GeckoResult&lt;Void&gt; onValue(final Integer value) {
49  *         // value == 21
50  *     }
51  * }, new GeckoResult.OnExceptionListener&lt;Void&gt;() {
52  *     &#64;Override
53  *     public GeckoResult&lt;Void&gt; onException(final Throwable exception) {
54  *         // Not called
55  *     }
56  * });</pre>
57  * <p>
58  * And to retrieve a completed exception,<pre>
59  * divide(42, 0).then(new GeckoResult.OnValueListener&lt;Integer, Void&gt;() {
60  *     &#64;Override
61  *     public GeckoResult&lt;Void&gt; onValue(final Integer value) {
62  *         // Not called
63  *     }
64  * }, new GeckoResult.OnExceptionListener&lt;Void&gt;() {
65  *     &#64;Override
66  *     public GeckoResult&lt;Void&gt; onException(final Throwable exception) {
67  *         // exception instanceof ArithmeticException
68  *     }
69  * });</pre>
70  * <p>
71  * {@link #then} calls may be chained to complete multiple asynchonous operations in sequence.
72  * This example takes an integer, converts it to a String, and appends it to another String,<pre>
73  * divide(42, 2).then(new GeckoResult.OnValueListener&lt;Integer, String&gt;() {
74  *     &#64;Override
75  *     public GeckoResult&lt;String&gt; onValue(final Integer value) {
76  *         return GeckoResult.fromValue(value.toString());
77  *     }
78  * }).then(new GeckoResult.OnValueListener&lt;String, String&gt;() {
79  *     &#64;Override
80  *     public GeckoResult&lt;String&gt; onValue(final String value) {
81  *         return GeckoResult.fromValue("42 / 2 = " + value);
82  *     }
83  * }).then(new GeckoResult.OnValueListener&lt;String, Void&gt;() {
84  *     &#64;Override
85  *     public GeckoResult&lt;Void&gt; onValue(final String value) {
86  *         // value == "42 / 2 = 21"
87  *         return null;
88  *     }
89  * });</pre>
90  * <p>
91  * Chaining works with exception listeners as well. For example,<pre>
92  * divide(42, 0).then(new GeckoResult.OnExceptionListener&lt;String&gt;() {
93  *     &#64;Override
94  *     public GeckoResult&lt;Void&gt; onException(final Throwable exception) {
95  *         return "foo";
96  *     }
97  * }).then(new GeckoResult.OnValueListener&lt;String, Void&gt;() {
98  *     &#64;Override
99  *     public GeckoResult&lt;Void&gt; onValue(final String value) {
100  *         // value == "foo"
101  *     }
102  * });</pre>
103  * <p>
104  * A completed value/exception will propagate down the chain even if an intermediate step does not
105  * have a value/exception listener. For example,<pre>
106  * divide(42, 0).then(new GeckoResult.OnValueListener&lt;Integer, String&gt;() {
107  *     &#64;Override
108  *     public GeckoResult&lt;String&gt; onValue(final Integer value) {
109  *         // Not called
110  *     }
111  * }).then(new GeckoResult.OnExceptionListener&lt;Void&gt;() {
112  *     &#64;Override
113  *     public GeckoResult&lt;Void&gt; onException(final Throwable exception) {
114  *         // exception instanceof ArithmeticException
115  *     }
116  * });</pre>
117  * <p>
118  * However, any propagated value will be coerced to null. For example,<pre>
119  * divide(42, 2).then(new GeckoResult.OnExceptionListener&lt;String&gt;() {
120  *     &#64;Override
121  *     public GeckoResult&lt;String&gt; onException(final Throwable exception) {
122  *         // Not called
123  *     }
124  * }).then(new GeckoResult.OnValueListener&lt;String, Void&gt;() {
125  *     &#64;Override
126  *     public GeckoResult&lt;Void&gt; onValue(final String value) {
127  *         // value == null
128  *     }
129  * });</pre>
130  * <p>
131  * If a GeckoResult is created on a thread without a {@link Looper},
132  * {@link #then(OnValueListener, OnExceptionListener)} is unusable (and will throw
133  * {@link IllegalThreadStateException}). In this scenario, the value is only
134  * available via {@link #poll(long)}. Alternatively, you may also chain the GeckoResult to one with a
135  * {@link Handler} via {@link #withHandler(Handler)}. You may then use
136  * {@link #then(OnValueListener, OnExceptionListener)} on the returned GeckoResult normally.
137  * <p>
138  * Any exception thrown by a listener are automatically used to complete the result. At the end of
139  * every chain, there is an implicit exception listener that rethrows any uncaught and unhandled
140  * exception as {@link UncaughtException}. The following example will cause {@link
141  * UncaughtException} to be thrown because {@code BazException} is uncaught and unhandled at the
142  * end of the chain,<pre>
143  * GeckoResult.fromValue(42).then(new GeckoResult.OnValueListener&lt;Integer, Void&gt;() {
144  *     &#64;Override
145  *     public GeckoResult&lt;Void&gt; onValue(final Integer value) throws FooException {
146  *         throw new FooException();
147  *     }
148  * }).then(new GeckoResult.OnExceptionListener&lt;Void&gt;() {
149  *     &#64;Override
150  *     public GeckoResult&lt;Void&gt; onException(final Throwable exception) throws Exception {
151  *         // exception instanceof FooException
152  *         throw new BarException();
153  *     }
154  * }).then(new GeckoResult.OnExceptionListener&lt;Void&gt;() {
155  *     &#64;Override
156  *     public GeckoResult&lt;Void&gt; onException(final Throwable exception) throws Throwable {
157  *         // exception instanceof BarException
158  *         return new BazException();
159  *     }
160  * });</pre>
161  *
162  * @param <T> The type of the value delivered via the GeckoResult.
163  */
164 @AnyThread
165 public class GeckoResult<T> {
166     private static final String LOGTAG = "GeckoResult";
167 
168     private interface Dispatcher {
dispatch(Runnable r)169         void dispatch(Runnable r);
170     }
171 
172     private static class HandlerDispatcher implements Dispatcher {
HandlerDispatcher(final Handler h)173         HandlerDispatcher(final Handler h) {
174             mHandler = h;
175         }
dispatch(final Runnable r)176         public void dispatch(final Runnable r) {
177             mHandler.post(r);
178         }
179         @Override
equals(final Object other)180         public boolean equals(final Object other) {
181             if (!(other instanceof HandlerDispatcher)) {
182                 return false;
183             }
184             return mHandler.equals(((HandlerDispatcher)other).mHandler);
185         }
186         @Override
hashCode()187         public int hashCode() {
188             return mHandler.hashCode();
189         }
190 
191         Handler mHandler;
192     }
193 
194     private static class XPCOMEventTargetDispatcher implements Dispatcher {
195         private IXPCOMEventTarget mEventTarget;
196 
XPCOMEventTargetDispatcher(final IXPCOMEventTarget eventTarget)197         public XPCOMEventTargetDispatcher(final IXPCOMEventTarget eventTarget) {
198             mEventTarget = eventTarget;
199         }
200 
201         @Override
dispatch(final Runnable r)202         public void dispatch(final Runnable r) {
203             mEventTarget.execute(r);
204         }
205     }
206 
207     private static class DirectDispatcher implements Dispatcher {
dispatch(final Runnable r)208         public void dispatch(final Runnable r) {
209             r.run();
210         }
211         static DirectDispatcher sInstance = new DirectDispatcher();
DirectDispatcher()212         private DirectDispatcher() {}
213 
214     }
215 
216     public static final class UncaughtException extends RuntimeException {
217         @SuppressWarnings("checkstyle:javadocmethod")
UncaughtException(final Throwable cause)218         public UncaughtException(final Throwable cause) {
219             super(cause);
220         }
221     }
222 
223     /**
224      * Interface used to delegate cancellation operations for a {@link GeckoResult}.
225      */
226     @AnyThread
227     public interface CancellationDelegate {
228 
229         /**
230          * This method should attempt to cancel the in-progress operation for the result
231          * to which this instance was attached. See {@link GeckoResult#cancel()} for more
232          * details.
233          *
234          * @return A {@link GeckoResult} resolving to "true" if cancellation was successful,
235          * "false" otherwise.
236          */
cancel()237         default @NonNull GeckoResult<Boolean> cancel() {
238             return GeckoResult.fromValue(false);
239         }
240     }
241 
242     /**
243      * A GeckoResult that resolves to AllowOrDeny.ALLOW
244      * @deprecated use {@link #allow} instead.
245      */
246     @Deprecated
247     @DeprecationSchedule(id = "allowdeny", version = 92)
248     public static final GeckoResult<AllowOrDeny> ALLOW = GeckoResult.fromValue(AllowOrDeny.ALLOW);
249 
250     /**
251      * A GeckoResult that resolves to AllowOrDeny.DENY
252      * @deprecated use {@link #deny} instead.
253      */
254     @Deprecated
255     @DeprecationSchedule(id = "allowdeny", version = 92)
256     public static final GeckoResult<AllowOrDeny> DENY = GeckoResult.fromValue(AllowOrDeny.DENY);
257 
258     /**
259      * @return a {@link GeckoResult} that resolves to {@link AllowOrDeny#DENY}
260      */
261     @AnyThread
262     @NonNull
deny()263     public static GeckoResult<AllowOrDeny> deny() {
264         return GeckoResult.fromValue(AllowOrDeny.DENY);
265     }
266 
267     /**
268      * @return a {@link GeckoResult} that resolves to {@link AllowOrDeny#ALLOW}
269      */
270     @AnyThread
271     @NonNull
allow()272     public static GeckoResult<AllowOrDeny> allow() {
273         return GeckoResult.fromValue(AllowOrDeny.ALLOW);
274     }
275 
276     // The default dispatcher for listeners on this GeckoResult. Other dispatchers can be specified
277     // when the listener is registered.
278     private final Dispatcher mDispatcher;
279     private boolean mComplete;
280     private T mValue;
281     private Throwable mError;
282     private boolean mIsUncaughtError;
283     private SimpleArrayMap<Dispatcher, ArrayList<Runnable>> mListeners = new SimpleArrayMap<>();
284 
285     private GeckoResult<?> mParent;
286     private CancellationDelegate mCancellationDelegate;
287 
288     /**
289      * Construct an incomplete GeckoResult. Call {@link #complete(Object)} or
290      * {@link #completeExceptionally(Throwable)} in order to fulfill the result.
291      */
292     @WrapForJNI
GeckoResult()293     public GeckoResult() {
294         if (ThreadUtils.isOnUiThread()) {
295             mDispatcher = new HandlerDispatcher(ThreadUtils.getUiHandler());
296         } else if (Looper.myLooper() != null) {
297             mDispatcher = new HandlerDispatcher(new Handler());
298         } else if (XPCOMEventTarget.launcherThread().isOnCurrentThread()) {
299             mDispatcher = new XPCOMEventTargetDispatcher(XPCOMEventTarget.launcherThread());
300         } else {
301             mDispatcher = null;
302         }
303     }
304 
305     /**
306      * Construct an incomplete GeckoResult. Call {@link #complete(Object)} or
307      * {@link #completeExceptionally(Throwable)} in order to fulfill the result.
308      *
309      * @param handler This {@link Handler} will be used for dispatching
310      *                listeners registered via {@link #then(OnValueListener, OnExceptionListener)}.
311      */
GeckoResult(final Handler handler)312     public GeckoResult(final Handler handler) {
313         mDispatcher = new HandlerDispatcher(handler);
314     }
315 
316     /**
317      * This constructs a result that is chained to the specified result.
318      *
319      * @param from The {@link GeckoResult} to copy.
320      */
GeckoResult(final GeckoResult<T> from)321     public GeckoResult(final GeckoResult<T> from) {
322         this();
323         completeFrom(from);
324     }
325 
326     /**
327      * Construct a result that is completed with the specified value.
328      *
329      * @param value The value used to complete the newly created result.
330      * @param <U> Type for the result.
331      * @return The completed {@link GeckoResult}
332      */
333     @WrapForJNI
fromValue(@ullable final U value)334     public static @NonNull <U> GeckoResult<U> fromValue(@Nullable final U value) {
335         final GeckoResult<U> result = new GeckoResult<>();
336         result.complete(value);
337         return result;
338     }
339 
340     /**
341      * Construct a result that is completed with the specified {@link Throwable}.
342      * May not be null.
343      *
344      * @param error The exception used to complete the newly created result.
345      * @param <T> Type for the result if the result had been completed without exception.
346      * @return The completed {@link GeckoResult}
347      */
348     @WrapForJNI
fromException(@onNull final Throwable error)349     public static @NonNull <T> GeckoResult<T> fromException(@NonNull final Throwable error) {
350         final GeckoResult<T> result = new GeckoResult<>();
351         result.completeExceptionally(error);
352         return result;
353     }
354 
355     @Override
hashCode()356     public synchronized int hashCode() {
357         int result = 17;
358         result = 31 * result + (mComplete ? 1 : 0);
359         result = 31 * result + (mValue != null ? mValue.hashCode() : 0);
360         result = 31 * result + (mError != null ? mError.hashCode() : 0);
361         return result;
362     }
363 
364     // This can go away once we can rely on java.util.Objects.equals() (API 19)
objectEquals(final Object a, final Object b)365     private static boolean objectEquals(final Object a, final Object b) {
366         return a == b || (a != null && a.equals(b));
367     }
368 
369     @Override
equals(final Object other)370     public synchronized boolean equals(final Object other) {
371         if (other instanceof GeckoResult<?>) {
372             final GeckoResult<?> result = (GeckoResult<?>)other;
373             return result.mComplete == mComplete &&
374                     objectEquals(result.mError, mError) &&
375                     objectEquals(result.mValue, mValue);
376         }
377 
378         return false;
379     }
380 
381     /**
382      * Convenience method for {@link #then(OnValueListener, OnExceptionListener)}.
383      *
384      * @param valueListener An instance of {@link OnValueListener}, called when the
385      *                      {@link GeckoResult} is completed with a value.
386      * @param <U> Type of the new result that is returned by the listener.
387      * @return A new {@link GeckoResult} that the listener will complete.
388      */
then(@onNull final OnValueListener<T, U> valueListener)389     public @NonNull <U> GeckoResult<U> then(@NonNull final OnValueListener<T, U> valueListener) {
390         return then(valueListener, null);
391     }
392 
393     /**
394      * Convenience method for {@link #map(OnValueMapper, OnExceptionMapper)}.
395      *
396      * @param valueMapper An instance of {@link OnValueMapper}, called when
397      *                    the {@link GeckoResult} is completed with a value.
398      * @param <U> Type of the new value that is returned by the mapper.
399      * @return A new {@link GeckoResult} that will contain the mapped value.
400      */
map(@ullable final OnValueMapper<T, U> valueMapper)401     public @NonNull <U> GeckoResult<U> map(@Nullable final OnValueMapper<T, U> valueMapper) {
402         return map(valueMapper, null);
403     }
404 
405     /**
406      * Transform the value and error of this {@link GeckoResult}.
407      *
408      * @param valueMapper An instance of {@link OnValueMapper}, called when
409      *                    the {@link GeckoResult} is completed with a value.
410      * @param exceptionMapper An instance of {@link OnExceptionMapper}, called
411      *                        when the {@link GeckoResult} is completed with an
412      *                        exception.
413      * @param <U> Type of the new value that is returned by the mapper.
414      * @return A new {@link GeckoResult} that will contain the mapped value.
415      */
map(@ullable final OnValueMapper<T, U> valueMapper, @Nullable final OnExceptionMapper exceptionMapper)416     public @NonNull <U> GeckoResult<U> map(@Nullable final OnValueMapper<T, U> valueMapper,
417                                            @Nullable final OnExceptionMapper exceptionMapper) {
418         final OnValueListener<T, U> valueListener = valueMapper != null
419                 ? value -> GeckoResult.fromValue(valueMapper.onValue(value))
420                 : null;
421         final OnExceptionListener<U> exceptionListener = exceptionMapper != null
422                 ? error -> GeckoResult.fromException(exceptionMapper.onException(error))
423                 : null;
424         return then(valueListener, exceptionListener);
425     }
426 
427     /**
428      * Convenience method for {@link #then(OnValueListener, OnExceptionListener)}.
429      *
430      * @param exceptionListener An instance of {@link OnExceptionListener}, called when the
431      *                          {@link GeckoResult} is completed with an {@link Exception}.
432      * @param <U> Type of the new result that is returned by the listener.
433      * @return A new {@link GeckoResult} that the listener will complete.
434      */
exceptionally(@onNull final OnExceptionListener<U> exceptionListener)435     public @NonNull <U> GeckoResult<U> exceptionally(@NonNull final OnExceptionListener<U> exceptionListener) {
436         return then(null, exceptionListener);
437     }
438 
439     /**
440      * Replacement for {@link java.util.function.Consumer} for devices with minApi &lt; 24.
441      *
442      * @param <T> the type of the input for this consumer.
443      */
444     // TODO: Remove this when we move to min API 24
445     public interface Consumer<T> {
446         /**
447          * Run this consumer for the given input.
448          *
449          * @param t the input value.
450          */
451         @AnyThread
accept(@ullable T t)452         void accept(@Nullable T t);
453     }
454 
455     /**
456      * Convenience method for {@link #accept(Consumer, Consumer)}.
457      *
458      * @param valueListener An instance of {@link Consumer}, called when the
459      *                      {@link GeckoResult} is completed with a value.
460      * @return A new {@link GeckoResult} that the listeners will complete.
461      */
accept(@ullable final Consumer<T> valueListener)462     public @NonNull GeckoResult<Void> accept(@Nullable final Consumer<T> valueListener) {
463         return accept(valueListener, null);
464     }
465 
466     /**
467      * Adds listeners to be called when the {@link GeckoResult} is completed either with
468      * a value or {@link Throwable}. Listeners will be invoked on the {@link Looper} returned from
469      * {@link #getLooper()}. If null, this method will throw {@link IllegalThreadStateException}.
470      *
471      * If the result is already complete when this method is called, listeners will be invoked in
472      * a future {@link Looper} iteration.
473      *
474      * @param valueConsumer An instance of {@link Consumer}, called when the
475      *                      {@link GeckoResult} is completed with a value.
476      * @param exceptionConsumer An instance of {@link Consumer}, called when the
477      *                          {@link GeckoResult} is completed with an {@link Throwable}.
478      * @return A new {@link GeckoResult} that the listeners will complete.
479      */
accept(@ullable final Consumer<T> valueConsumer, @Nullable final Consumer<Throwable> exceptionConsumer)480     public @NonNull GeckoResult<Void> accept(@Nullable final Consumer<T> valueConsumer,
481                                              @Nullable final Consumer<Throwable> exceptionConsumer) {
482         final OnValueListener<T, Void> valueListener = valueConsumer == null ? null :
483             value -> {
484                 valueConsumer.accept(value);
485                 return null;
486             };
487 
488         final OnExceptionListener<Void> exceptionListener = exceptionConsumer == null ? null :
489             value -> {
490                 exceptionConsumer.accept(value);
491                 return null;
492             };
493 
494         return then(valueListener, exceptionListener);
495     }
496 
getOrAccept(@ullable final Consumer<T> valueConsumer)497     /* package */ @NonNull GeckoResult<Void> getOrAccept(@Nullable final Consumer<T> valueConsumer) {
498         return getOrAccept(valueConsumer, null);
499     }
500 
getOrAccept(@ullable final Consumer<T> valueConsumer, @Nullable final Consumer<Throwable> exceptionConsumer)501     /* package */ @NonNull GeckoResult<Void> getOrAccept(@Nullable final Consumer<T> valueConsumer,
502                                                          @Nullable final Consumer<Throwable> exceptionConsumer) {
503         if (haveValue() && valueConsumer != null) {
504             valueConsumer.accept(mValue);
505             return GeckoResult.fromValue(null);
506         }
507 
508         if (haveError() && exceptionConsumer != null) {
509             exceptionConsumer.accept(mError);
510             return GeckoResult.fromValue(null);
511         }
512 
513         return accept(valueConsumer, exceptionConsumer);
514     }
515 
516     /**
517      * Adds listeners to be called when the {@link GeckoResult} is completed either with
518      * a value or {@link Throwable}. Listeners will be invoked on the {@link Looper} returned from
519      * {@link #getLooper()}. If null, this method will throw {@link IllegalThreadStateException}.
520      *
521      * If the result is already complete when this method is called, listeners will be invoked in
522      * a future {@link Looper} iteration.
523      *
524      * @param valueListener An instance of {@link OnValueListener}, called when the
525      *                      {@link GeckoResult} is completed with a value.
526      * @param exceptionListener An instance of {@link OnExceptionListener}, called when the
527      *                          {@link GeckoResult} is completed with an {@link Throwable}.
528      * @param <U> Type of the new result that is returned by the listeners.
529      * @return A new {@link GeckoResult} that the listeners will complete.
530      */
then(@ullable final OnValueListener<T, U> valueListener, @Nullable final OnExceptionListener<U> exceptionListener)531     public @NonNull <U> GeckoResult<U> then(@Nullable final OnValueListener<T, U> valueListener,
532                                             @Nullable final OnExceptionListener<U> exceptionListener) {
533         if (mDispatcher == null) {
534             throw new IllegalThreadStateException("Must have a Handler");
535         }
536 
537         return thenInternal(mDispatcher, valueListener, exceptionListener);
538     }
539 
thenInternal(@onNull final Dispatcher dispatcher, @Nullable final OnValueListener<T, U> valueListener, @Nullable final OnExceptionListener<U> exceptionListener)540     private @NonNull <U> GeckoResult<U> thenInternal(@NonNull final Dispatcher dispatcher,
541                                                      @Nullable final OnValueListener<T, U> valueListener,
542                                                      @Nullable final OnExceptionListener<U> exceptionListener) {
543         if (valueListener == null && exceptionListener == null) {
544             throw new IllegalArgumentException("At least one listener should be non-null");
545         }
546 
547         final GeckoResult<U> result = new GeckoResult<U>();
548         result.mParent = this;
549         thenInternal(dispatcher, () -> {
550             try {
551                 if (haveValue()) {
552                     result.completeFrom(valueListener != null ? valueListener.onValue(mValue)
553                                                               : null);
554                 } else if (!haveError()) {
555                     // Listener called without completion?
556                     throw new AssertionError();
557                 } else if (exceptionListener != null) {
558                     result.completeFrom(exceptionListener.onException(mError));
559                 } else {
560                     result.mIsUncaughtError = mIsUncaughtError;
561                     result.completeExceptionally(mError);
562                 }
563             } catch (final Throwable e) {
564                 if (!result.mComplete) {
565                     result.mIsUncaughtError = true;
566                     result.completeExceptionally(e);
567                 } else if (e instanceof RuntimeException) {
568                     // This should only be UncaughtException, but we rethrow all RuntimeExceptions
569                     // to avoid squelching logic errors in GeckoResult itself.
570                     throw (RuntimeException) e;
571                 }
572             }
573         });
574         return result;
575     }
576 
thenInternal(@onNull final Dispatcher dispatcher, @NonNull final Runnable listener)577     private synchronized void thenInternal(@NonNull final Dispatcher dispatcher, @NonNull final Runnable listener) {
578         if (mComplete) {
579             dispatcher.dispatch(listener);
580         } else {
581             if (!mListeners.containsKey(dispatcher)) {
582                 mListeners.put(dispatcher, new ArrayList<>(1));
583             }
584             mListeners.get(dispatcher).add(listener);
585         }
586     }
587 
588     @WrapForJNI
nativeThen(@onNull final GeckoCallback accept, @NonNull final GeckoCallback reject)589     private void nativeThen(@NonNull final GeckoCallback accept, @NonNull final GeckoCallback reject) {
590         // NB: We could use the lambda syntax here, but given all the layers
591         // of abstraction it's helpful to see the types written explicitly.
592         thenInternal(DirectDispatcher.sInstance, new OnValueListener<T, Void>() {
593             @Override
594             public GeckoResult<Void> onValue(final T value) {
595                 accept.call(value);
596                 return null;
597             }
598         }, new OnExceptionListener<Void>() {
599             @Override
600             public GeckoResult<Void> onException(final Throwable exception) {
601                 reject.call(exception);
602                 return null;
603             }
604         });
605     }
606 
607     /**
608      * @return Get the {@link Looper} that will be used to schedule listeners registered via
609      *         {@link #then(OnValueListener, OnExceptionListener)}.
610      */
getLooper()611     public @Nullable Looper getLooper() {
612         if (mDispatcher == null || !(mDispatcher instanceof HandlerDispatcher)) {
613             return null;
614         }
615 
616         return ((HandlerDispatcher)mDispatcher).mHandler.getLooper();
617     }
618 
619     /**
620      * Returns a new GeckoResult that will be completed by this instance. Listeners registered
621      * via {@link #then(OnValueListener, OnExceptionListener)} will be run on the specified
622      * {@link Handler}.
623      *
624      * @param handler A {@link Handler} where listeners will be run. May be null.
625      * @return A new GeckoResult.
626      */
withHandler(final @Nullable Handler handler)627     public @NonNull GeckoResult<T> withHandler(final @Nullable Handler handler) {
628         final GeckoResult<T> result = new GeckoResult<>(handler);
629         result.completeFrom(this);
630         return result;
631     }
632 
633     /**
634      * Returns a {@link GeckoResult} that is completed when the given {@link GeckoResult}
635      * instances are complete.
636      *
637      * The returned {@link GeckoResult} will resolve with the list of values from the inputs.
638      * The list is guaranteed to be in the same order as the inputs.
639      *
640      * If any of the {@link GeckoResult} fails, the returned result will fail.
641      *
642      * If no inputs are provided, the returned {@link GeckoResult} will complete with the value
643      * <code>null</code>.
644      *
645      * @param pending the input {@link GeckoResult}s.
646      * @param <V> type of the {@link GeckoResult}'s values.
647      * @return a {@link GeckoResult} that will complete when all of the inputs are completed or
648      *         when at least one of the inputs fail.
649      */
650     @SuppressWarnings("varargs")
651     @SafeVarargs
652     @NonNull
allOf(final @NonNull GeckoResult<V> ... pending)653     public static <V> GeckoResult<List<V>> allOf(final @NonNull GeckoResult<V> ... pending) {
654         return allOf(Arrays.asList(pending));
655     }
656 
657     /**
658      * Returns a {@link GeckoResult} that is completed when the given {@link GeckoResult}
659      * instances are complete.
660      *
661      * The returned {@link GeckoResult} will resolve with the list of values from the inputs.
662      * The list is guaranteed to be in the same order as the inputs.
663      *
664      * If any of the {@link GeckoResult} fails, the returned result will fail.
665      *
666      * If no inputs are provided, the returned {@link GeckoResult} will complete with the value
667      * <code>null</code>.
668      *
669      * @param pending the input {@link GeckoResult}s.
670      * @param <V> type of the {@link GeckoResult}'s values.
671      * @return a {@link GeckoResult} that will complete when all of the inputs are completed or
672      *         when at least one of the inputs fail.
673      */
674     @NonNull
allOf( final @Nullable List<GeckoResult<V>> pending)675     public static <V> GeckoResult<List<V>> allOf(
676             final @Nullable List<GeckoResult<V>> pending) {
677         if (pending == null) {
678             return GeckoResult.fromValue(null);
679         }
680 
681         return new AllOfResult<>(pending);
682     }
683 
684     private static class AllOfResult<V> extends GeckoResult<List<V>> {
685         private boolean mFailed = false;
686         private int mResultCount = 0;
687         private final List<V> mAccumulator;
688         private final List<GeckoResult<V>> mPending;
689 
AllOfResult(final @NonNull List<GeckoResult<V>> pending)690         public AllOfResult(final @NonNull List<GeckoResult<V>> pending) {
691             // Initialize the list with nulls so we can fill it in the same order as the input list
692             mAccumulator = new ArrayList<>(Collections.nCopies(pending.size(), null));
693             mPending = pending;
694 
695             // If the input list is empty, there's nothing to do
696             if (pending.size() == 0) {
697                 complete(mAccumulator);
698                 return;
699             }
700 
701             // We use iterators so we can access the index and preserve the list order
702             final ListIterator<GeckoResult<V>> it = pending.listIterator();
703             while (it.hasNext()) {
704                 final int index = it.nextIndex();
705                 it.next().accept(
706                     value -> onResult(value, index),
707                         this::onError);
708             }
709         }
710 
onResult(final V value, final int index)711         private void onResult(final V value, final int index) {
712             if (mFailed) {
713                 // Some other element in the list already failed, nothing to do here
714                 return;
715             }
716 
717             mResultCount++;
718             mAccumulator.set(index, value);
719 
720             if (mResultCount == mPending.size()) {
721                 complete(mAccumulator);
722             }
723         }
724 
onError(final Throwable error)725         private void onError(final Throwable error) {
726             mFailed = true;
727             completeExceptionally(error);
728         }
729     }
730 
dispatchLocked()731     private void dispatchLocked() {
732         if (!mComplete) {
733             throw new IllegalStateException("Cannot dispatch unless result is complete");
734         }
735 
736         if (mListeners.isEmpty()) {
737             if (mIsUncaughtError) {
738                 // We have no listeners to forward the uncaught exception to;
739                 // rethrow the exception to make it visible.
740                 throw new UncaughtException(mError);
741             }
742             return;
743         }
744 
745         if (mDispatcher == null) {
746             throw new AssertionError("Shouldn't have listeners with null dispatcher");
747         }
748 
749         for (int i = 0; i < mListeners.size(); ++i) {
750             final Dispatcher dispatcher = mListeners.keyAt(i);
751             final ArrayList<Runnable> jobs = mListeners.valueAt(i);
752             dispatcher.dispatch(() -> {
753                 for (final Runnable job : jobs) {
754                     job.run();
755                 }
756             });
757         }
758         mListeners.clear();
759     }
760 
761     /**
762      * Completes this result based on another result.
763      *
764      * @param other The result that this result should mirror
765      */
completeFrom(final GeckoResult<T> other)766     private void completeFrom(final GeckoResult<T> other) {
767         if (other == null) {
768             complete(null);
769             return;
770         }
771 
772         this.mCancellationDelegate = other.mCancellationDelegate;
773         other.thenInternal(DirectDispatcher.sInstance, () -> {
774             if (other.haveValue()) {
775                 complete(other.mValue);
776             } else {
777                 mIsUncaughtError = other.mIsUncaughtError;
778                 completeExceptionally(other.mError);
779             }
780         });
781     }
782 
783     /**
784      * Return the value of this result, waiting for it to be completed
785      * if necessary. If the result is completed with an exception
786      * it will be rethrown here.
787      * <p>
788      * You must not call this method if the current thread has a {@link Looper} due to
789      * the possibility of a deadlock. If this occurs, {@link IllegalStateException}
790      * is thrown.
791      *
792      * @return The value of this result.
793      * @throws Throwable The {@link Throwable} contained in this result, if any.
794      * @throws IllegalThreadStateException if this method is called on a thread that has a {@link Looper}.
795      */
poll()796     public synchronized @Nullable T poll() throws Throwable {
797         if (Looper.myLooper() != null) {
798             throw new IllegalThreadStateException("Cannot poll indefinitely from thread with Looper");
799         }
800 
801         return poll(Long.MAX_VALUE);
802     }
803 
804     /**
805      * Return the value of this result, waiting for it to be completed
806      * if necessary. If the result is completed with an exception
807      * it will be rethrown here.
808      *
809      * Caution is advised if the caller is on a thread with a {@link Looper}, as it's possible to
810      * effectively deadlock in cases when the work is being completed on the calling thread. It's
811      * preferable to use {@link #then(OnValueListener, OnExceptionListener)} in such circumstances,
812      * but if you must use this method consider a small timeout value.
813      *
814      * @param timeoutMillis Number of milliseconds to wait for the result
815      *                      to complete.
816      * @return The value of this result.
817      * @throws Throwable The {@link Throwable} contained in this result, if any.
818      * @throws TimeoutException if we wait more than timeoutMillis before the result
819      *                          is completed.
820      */
poll(final long timeoutMillis)821     public synchronized @Nullable T poll(final long timeoutMillis) throws Throwable {
822         final long start = SystemClock.uptimeMillis();
823         long remaining = timeoutMillis;
824         while (!mComplete && remaining > 0) {
825             try {
826                 wait(remaining);
827             } catch (final InterruptedException e) {
828             }
829 
830             remaining = timeoutMillis - (SystemClock.uptimeMillis() - start);
831         }
832 
833         if (!mComplete) {
834             throw new TimeoutException();
835         }
836 
837         if (haveError()) {
838             throw mError;
839         }
840 
841         return mValue;
842     }
843 
844     /**
845      * Complete the result with the specified value. IllegalStateException is thrown
846      * if the result is already complete.
847      *
848      * @param value The value used to complete the result.
849      * @throws IllegalStateException If the result is already completed.
850      */
851     @WrapForJNI
complete(final @Nullable T value)852     public synchronized void complete(final @Nullable T value) {
853         if (mComplete) {
854             throw new IllegalStateException("result is already complete");
855         }
856 
857         mValue = value;
858         mComplete = true;
859 
860         dispatchLocked();
861         notifyAll();
862     }
863 
864     /**
865      * Complete the result with the specified {@link Throwable}. IllegalStateException is thrown
866      * if the result is already complete.
867      *
868      * @param exception The {@link Throwable} used to complete the result.
869      * @throws IllegalStateException If the result is already completed.
870      */
871     @WrapForJNI
completeExceptionally(@onNull final Throwable exception)872     public synchronized void completeExceptionally(@NonNull final Throwable exception) {
873         if (mComplete) {
874             throw new IllegalStateException("result is already complete");
875         }
876 
877         if (exception == null) {
878             throw new IllegalArgumentException("Throwable must not be null");
879         }
880 
881         mError = exception;
882         mComplete = true;
883 
884         dispatchLocked();
885         notifyAll();
886     }
887 
888     /**
889      * An interface used to deliver values to listeners of a {@link GeckoResult}
890      * @param <T> Type of the value delivered via {@link #onValue(Object)}
891      * @param <U> Type of the value for the result returned from {@link #onValue(Object)}
892      */
893     public interface OnValueListener<T, U> {
894         /**
895          * Called when a {@link GeckoResult} is completed with a value. Will be
896          * called on the same thread where the GeckoResult was created or on
897          * the {@link Handler} provided via {@link #withHandler(Handler)}.
898          *
899          * @param value The value of the {@link GeckoResult}
900          * @return Result used to complete the next result in the chain. May be null.
901          * @throws Throwable Exception used to complete next result in the chain.
902          */
903         @AnyThread
onValue(@ullable T value)904         @Nullable GeckoResult<U> onValue(@Nullable T value) throws Throwable;
905     }
906 
907     /**
908      * An interface used to map {@link GeckoResult} values.
909      *
910      * @param <T> Type of the value delivered via {@link #onValue}
911      * @param <U> Type of the new value returned by {@link #onValue}
912      */
913     public interface OnValueMapper<T, U> {
914         /**
915          * Called when a {@link GeckoResult} is completed with a value.
916          * Will be called on the same thread where the GeckoResult was created
917          * or on the {@link Handler} provided via {@link #withHandler(Handler)}.
918          *
919          * @param value The value of the {@link GeckoResult}
920          * @return Value used to complete the next result in the chain. May be null.
921          * @throws Throwable Exception used to complete next result in the chain.
922          */
923         @AnyThread
onValue(@ullable T value)924         @Nullable U onValue(@Nullable T value) throws Throwable;
925     }
926 
927     /**
928      * An interface used to map {@link GeckoResult} exceptions.
929      */
930     public interface OnExceptionMapper {
931         /**
932          * Called when a {@link GeckoResult} is completed with an exception.
933          * Will be called on the same thread where the GeckoResult was created
934          * or on the {@link Handler} provided via {@link #withHandler(Handler)}.
935          *
936          * @param exception Exception that completed the result.
937          * @return Exception used to complete the next result in the chain. May be null.
938          * @throws Throwable Exception used to complete next result in the chain.
939          */
940         @AnyThread
onException(@onNull Throwable exception)941         @Nullable Throwable onException(@NonNull Throwable exception) throws Throwable;
942     }
943 
944     /**
945      * An interface used to deliver exceptions to listeners of a {@link GeckoResult}
946      *
947      * @param <V> Type of the vale for the result returned from {@link #onException(Throwable)}
948      */
949     public interface OnExceptionListener<V> {
950         /**
951          * Called when a {@link GeckoResult} is completed with an exception.
952          * Will be called on the same thread where the GeckoResult was created
953          * or on the {@link Handler} provided via {@link #withHandler(Handler)}.
954          *
955          * @param exception Exception that completed the result.
956          * @return Result used to complete the next result in the chain. May be null.
957          * @throws Throwable Exception used to complete next result in the chain.
958          */
959         @AnyThread
onException(@onNull Throwable exception)960         @Nullable GeckoResult<V> onException(@NonNull Throwable exception) throws Throwable;
961     }
962 
963     @WrapForJNI
964     private static class GeckoCallback extends JNIObject {
call(Object arg)965         private native void call(Object arg);
966 
967         @Override
disposeNative()968         protected native void disposeNative();
969     }
970 
971 
haveValue()972     private boolean haveValue() {
973         return mComplete && mError == null;
974     }
975 
haveError()976     private boolean haveError() {
977         return mComplete && mError != null;
978     }
979 
980     /**
981      * Attempts to cancel the operation associated with this result.
982      *
983      * If this result has a {@link CancellationDelegate} attached via
984      * {@link #setCancellationDelegate(CancellationDelegate)}, the return value
985      * will be the result of calling {@link CancellationDelegate#cancel()} on that instance.
986      * Otherwise, if this result is chained to another result
987      * (via return value from {@link OnValueListener}), we will walk up the chain until
988      * a CancellationDelegate is found and run it. If no CancellationDelegate is found,
989      * a result resolving to "false" will be returned.
990      *
991      * If this result is already complete, the returned result will always resolve to false.
992      *
993      * If the returned result resolves to true, this result will be completed
994      * with a {@link CancellationException}.
995      *
996      * @return A GeckoResult resolving to a boolean indicating success or failure of the cancellation attempt.
997      */
cancel()998     public synchronized @NonNull GeckoResult<Boolean> cancel() {
999         if (haveValue() || haveError()) {
1000             return GeckoResult.fromValue(false);
1001         }
1002 
1003         if (mCancellationDelegate != null) {
1004             return mCancellationDelegate.cancel().then(value -> {
1005                 if (value) {
1006                     try {
1007                         this.completeExceptionally(new CancellationException());
1008                     } catch (final IllegalStateException e) {
1009                         // Can't really do anything about this.
1010                     }
1011                 }
1012                 return GeckoResult.fromValue(value);
1013             });
1014         }
1015 
1016         if (mParent != null) {
1017             return mParent.cancel();
1018         }
1019 
1020         return GeckoResult.fromValue(false);
1021     }
1022 
1023     /**
1024      * Sets the instance of {@link CancellationDelegate} that will be invoked by
1025      * {@link #cancel()}.
1026      *
1027      * @param delegate an instance of CancellationDelegate.
1028      */
1029     public void setCancellationDelegate(final @Nullable CancellationDelegate delegate) {
1030         mCancellationDelegate = delegate;
1031     }
1032 }
1033