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<Integer> divide(final int dividend, final int divisor) { 31 * final GeckoResult<Integer> result = new GeckoResult<>(); 32 * (new Thread(() -> { 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<Integer, Void>() { 47 * @Override 48 * public GeckoResult<Void> onValue(final Integer value) { 49 * // value == 21 50 * } 51 * }, new GeckoResult.OnExceptionListener<Void>() { 52 * @Override 53 * public GeckoResult<Void> 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<Integer, Void>() { 60 * @Override 61 * public GeckoResult<Void> onValue(final Integer value) { 62 * // Not called 63 * } 64 * }, new GeckoResult.OnExceptionListener<Void>() { 65 * @Override 66 * public GeckoResult<Void> 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<Integer, String>() { 74 * @Override 75 * public GeckoResult<String> onValue(final Integer value) { 76 * return GeckoResult.fromValue(value.toString()); 77 * } 78 * }).then(new GeckoResult.OnValueListener<String, String>() { 79 * @Override 80 * public GeckoResult<String> onValue(final String value) { 81 * return GeckoResult.fromValue("42 / 2 = " + value); 82 * } 83 * }).then(new GeckoResult.OnValueListener<String, Void>() { 84 * @Override 85 * public GeckoResult<Void> 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<String>() { 93 * @Override 94 * public GeckoResult<Void> onException(final Throwable exception) { 95 * return "foo"; 96 * } 97 * }).then(new GeckoResult.OnValueListener<String, Void>() { 98 * @Override 99 * public GeckoResult<Void> 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<Integer, String>() { 107 * @Override 108 * public GeckoResult<String> onValue(final Integer value) { 109 * // Not called 110 * } 111 * }).then(new GeckoResult.OnExceptionListener<Void>() { 112 * @Override 113 * public GeckoResult<Void> 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<String>() { 120 * @Override 121 * public GeckoResult<String> onException(final Throwable exception) { 122 * // Not called 123 * } 124 * }).then(new GeckoResult.OnValueListener<String, Void>() { 125 * @Override 126 * public GeckoResult<Void> 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<Integer, Void>() { 144 * @Override 145 * public GeckoResult<Void> onValue(final Integer value) throws FooException { 146 * throw new FooException(); 147 * } 148 * }).then(new GeckoResult.OnExceptionListener<Void>() { 149 * @Override 150 * public GeckoResult<Void> onException(final Throwable exception) throws Exception { 151 * // exception instanceof FooException 152 * throw new BarException(); 153 * } 154 * }).then(new GeckoResult.OnExceptionListener<Void>() { 155 * @Override 156 * public GeckoResult<Void> 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 < 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