From dfb42a3946b6c1d20fd42d554027fef45f4ec70d Mon Sep 17 00:00:00 2001 From: David Vacca Date: Tue, 30 May 2023 13:13:36 -0700 Subject: [PATCH] Prepare bolts to be safe to use it from Kotlin Summary: In this diff I analyzed bolts library to ensure it's ready to be used from kotlin. I won't convert bolts to kotlin, but this is necessary to be able to convert its callsites (ReactHost) to kotlin bypass-github-export-checks changelog: [internal] internal Reviewed By: fkgozali Differential Revision: D46194127 fbshipit-source-id: 609e356230b1c87fe26571b811d23430d0168276 --- .../internal/bolts/AggregateException.java | 24 +++++++++++-------- .../internal/bolts/AndroidExecutors.java | 16 +++++++------ .../internal/bolts/BoltsExecutors.java | 18 +++++++------- .../internal/bolts/CancellationToken.java | 5 ++-- .../bolts/CancellationTokenRegistration.java | 8 ++++--- .../bolts/CancellationTokenSource.java | 19 ++++++++------- .../bridgeless/internal/bolts/Capture.java | 10 ++++---- .../internal/bolts/Continuation.java | 6 ++++- .../internal/bolts/ExecutorException.java | 4 +++- .../internal/bolts/TaskCompletionSource.java | 15 +++++++----- .../bolts/UnobservedErrorNotifier.java | 6 +++-- .../bolts/UnobservedTaskException.java | 4 +++- 12 files changed, 82 insertions(+), 53 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/AggregateException.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/AggregateException.java index 99d0351547da04..93294b3a5faa6c 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/AggregateException.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/AggregateException.java @@ -7,6 +7,8 @@ package com.facebook.react.bridgeless.internal.bolts; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import java.io.PrintStream; import java.io.PrintWriter; import java.util.ArrayList; @@ -24,7 +26,7 @@ public class AggregateException extends Exception { private static final String DEFAULT_MESSAGE = "There were multiple errors."; - private List innerThrowables; + @NonNull private final List innerThrowables; /** * Constructs a new {@code AggregateException} with the current stack trace, the specified detail @@ -33,7 +35,7 @@ public class AggregateException extends Exception { * @param detailMessage The detail message for this exception. * @param innerThrowables The exceptions that are the cause of the current exception. */ - public AggregateException(String detailMessage, Throwable[] innerThrowables) { + public AggregateException(@NonNull String detailMessage, @NonNull Throwable[] innerThrowables) { this(detailMessage, Arrays.asList(innerThrowables)); } @@ -44,11 +46,13 @@ public AggregateException(String detailMessage, Throwable[] innerThrowables) { * @param detailMessage The detail message for this exception. * @param innerThrowables The exceptions that are the cause of the current exception. */ - public AggregateException(String detailMessage, List innerThrowables) { + public AggregateException( + @NonNull String detailMessage, @Nullable List innerThrowables) { super( detailMessage, innerThrowables != null && innerThrowables.size() > 0 ? innerThrowables.get(0) : null); - this.innerThrowables = Collections.unmodifiableList(innerThrowables); + this.innerThrowables = + Collections.unmodifiableList(innerThrowables != null ? innerThrowables : new ArrayList<>()); } /** @@ -57,7 +61,7 @@ public AggregateException(String detailMessage, List innerT * * @param innerThrowables The exceptions that are the cause of the current exception. */ - public AggregateException(List innerThrowables) { + public AggregateException(@Nullable List innerThrowables) { this(DEFAULT_MESSAGE, innerThrowables); } @@ -65,12 +69,12 @@ public AggregateException(List innerThrowables) { * Returns a read-only {@link List} of the {@link Throwable} instances that caused the current * exception. */ - public List getInnerThrowables() { + public @NonNull List getInnerThrowables() { return innerThrowables; } @Override - public void printStackTrace(PrintStream err) { + public void printStackTrace(@NonNull PrintStream err) { super.printStackTrace(err); int currentIndex = -1; @@ -85,7 +89,7 @@ public void printStackTrace(PrintStream err) { } @Override - public void printStackTrace(PrintWriter err) { + public void printStackTrace(@NonNull PrintWriter err) { super.printStackTrace(err); int currentIndex = -1; @@ -101,7 +105,7 @@ public void printStackTrace(PrintWriter err) { /** @deprecated Please use {@link #getInnerThrowables()} instead. */ @Deprecated - public List getErrors() { + public @NonNull List getErrors() { List errors = new ArrayList(); if (innerThrowables == null) { return errors; @@ -119,7 +123,7 @@ public List getErrors() { /** @deprecated Please use {@link #getInnerThrowables()} instead. */ @Deprecated - public Throwable[] getCauses() { + public @NonNull Throwable[] getCauses() { return innerThrowables.toArray(new Throwable[innerThrowables.size()]); } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/AndroidExecutors.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/AndroidExecutors.java index 3d87da1bc3a27e..f623bd40106003 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/AndroidExecutors.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/AndroidExecutors.java @@ -11,6 +11,7 @@ import android.os.Build; import android.os.Handler; import android.os.Looper; +import androidx.annotation.NonNull; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; @@ -32,11 +33,12 @@ * 0 and maxPoolSize is Integer.MAX_VALUE. This is dangerous because it can create an unchecked * amount of threads. */ -/* package */ final class AndroidExecutors { +/* package */ +final class AndroidExecutors { private static final AndroidExecutors INSTANCE = new AndroidExecutors(); - private final Executor uiThread; + @NonNull private final Executor uiThread; private AndroidExecutors() { uiThread = new UIThreadExecutor(); @@ -64,7 +66,7 @@ private AndroidExecutors() { * * @return the newly created thread pool */ - public static ExecutorService newCachedThreadPool() { + public static @NonNull ExecutorService newCachedThreadPool() { ThreadPoolExecutor executor = new ThreadPoolExecutor( CORE_POOL_SIZE, @@ -89,7 +91,7 @@ public static ExecutorService newCachedThreadPool() { * @param threadFactory the factory to use when creating new threads * @return the newly created thread pool */ - public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { + public static @NonNull ExecutorService newCachedThreadPool(@NonNull ThreadFactory threadFactory) { ThreadPoolExecutor executor = new ThreadPoolExecutor( CORE_POOL_SIZE, @@ -114,21 +116,21 @@ public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { * @param value true if should time out, else false */ @SuppressLint("NewApi") - public static void allowCoreThreadTimeout(ThreadPoolExecutor executor, boolean value) { + public static void allowCoreThreadTimeout(@NonNull ThreadPoolExecutor executor, boolean value) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { executor.allowCoreThreadTimeOut(value); } } /** An {@link java.util.concurrent.Executor} that executes tasks on the UI thread. */ - public static Executor uiThread() { + public static @NonNull Executor uiThread() { return INSTANCE.uiThread; } /** An {@link java.util.concurrent.Executor} that runs tasks on the UI thread. */ private static class UIThreadExecutor implements Executor { @Override - public void execute(Runnable command) { + public void execute(@NonNull Runnable command) { new Handler(Looper.getMainLooper()).post(command); } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/BoltsExecutors.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/BoltsExecutors.java index 0f3fc9c354ad7d..035871fce0b5e1 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/BoltsExecutors.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/BoltsExecutors.java @@ -7,6 +7,7 @@ package com.facebook.react.bridgeless.internal.bolts; +import androidx.annotation.NonNull; import java.util.Locale; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; @@ -14,7 +15,8 @@ import java.util.concurrent.ScheduledExecutorService; /** Collection of {@link Executor}s to use in conjunction with {@link Task}. */ -/* package */ final class BoltsExecutors { +/* package */ +final class BoltsExecutors { private static final BoltsExecutors INSTANCE = new BoltsExecutors(); @@ -26,9 +28,9 @@ private static boolean isAndroidRuntime() { return javaRuntimeName.toLowerCase(Locale.US).contains("android"); } - private final ExecutorService background; - private final ScheduledExecutorService scheduled; - private final Executor immediate; + private final @NonNull ExecutorService background; + private final @NonNull ScheduledExecutorService scheduled; + private final @NonNull Executor immediate; private BoltsExecutors() { background = @@ -40,11 +42,11 @@ private BoltsExecutors() { } /** An {@link java.util.concurrent.Executor} that executes tasks in parallel. */ - public static ExecutorService background() { + public static @NonNull ExecutorService background() { return INSTANCE.background; } - /* package */ static ScheduledExecutorService scheduled() { + /* package */ static @NonNull ScheduledExecutorService scheduled() { return INSTANCE.scheduled; } @@ -53,7 +55,7 @@ public static ExecutorService background() { * stack runs too deep, at which point it will delegate to {@link BoltsExecutors#background} in * order to trim the stack. */ - /* package */ static Executor immediate() { + /* package */ static @NonNull Executor immediate() { return INSTANCE.immediate; } @@ -102,7 +104,7 @@ private int decrementDepth() { } @Override - public void execute(Runnable command) { + public void execute(@NonNull Runnable command) { int depth = incrementDepth(); try { if (depth <= MAX_DEPTH) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/CancellationToken.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/CancellationToken.java index 5baba3bd66fa83..d85c74e3fc1a22 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/CancellationToken.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/CancellationToken.java @@ -7,6 +7,7 @@ package com.facebook.react.bridgeless.internal.bolts; +import androidx.annotation.NonNull; import java.util.Locale; import java.util.concurrent.CancellationException; @@ -29,7 +30,7 @@ public class CancellationToken { private final CancellationTokenSource tokenSource; - /* package */ CancellationToken(CancellationTokenSource tokenSource) { + /* package */ CancellationToken(@NonNull CancellationTokenSource tokenSource) { this.tokenSource = tokenSource; } @@ -49,7 +50,7 @@ public boolean isCancellationRequested() { * @return a {@link CancellationTokenRegistration} instance that can be used to unregister the * action. */ - public CancellationTokenRegistration register(Runnable action) { + public @NonNull CancellationTokenRegistration register(@NonNull Runnable action) { return tokenSource.register(action); } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/CancellationTokenRegistration.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/CancellationTokenRegistration.java index 67099fe7237746..8edb0a9f5b4a2d 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/CancellationTokenRegistration.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/CancellationTokenRegistration.java @@ -7,6 +7,8 @@ package com.facebook.react.bridgeless.internal.bolts; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import java.io.Closeable; /** @@ -17,12 +19,12 @@ public class CancellationTokenRegistration implements Closeable { private final Object lock = new Object(); - private CancellationTokenSource tokenSource; - private Runnable action; + private @Nullable CancellationTokenSource tokenSource; + private @Nullable Runnable action; private boolean closed; /* package */ CancellationTokenRegistration( - CancellationTokenSource tokenSource, Runnable action) { + @NonNull CancellationTokenSource tokenSource, @NonNull Runnable action) { this.tokenSource = tokenSource; this.action = action; } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/CancellationTokenSource.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/CancellationTokenSource.java index 4b20b3ff0560ad..59970ff30b17c1 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/CancellationTokenSource.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/CancellationTokenSource.java @@ -7,6 +7,8 @@ package com.facebook.react.bridgeless.internal.bolts; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import java.io.Closeable; import java.util.ArrayList; import java.util.List; @@ -27,9 +29,9 @@ public class CancellationTokenSource implements Closeable { private final Object lock = new Object(); - private final List registrations = new ArrayList<>(); - private final ScheduledExecutorService executor = BoltsExecutors.scheduled(); - private ScheduledFuture scheduledCancellation; + private final @NonNull List registrations = new ArrayList<>(); + private final @NonNull ScheduledExecutorService executor = BoltsExecutors.scheduled(); + @Nullable private ScheduledFuture scheduledCancellation; private boolean cancellationRequested; private boolean closed; @@ -48,7 +50,7 @@ public boolean isCancellationRequested() { } /** @return the token that can be passed to asynchronous method to control cancellation. */ - public CancellationToken getToken() { + public @NonNull CancellationToken getToken() { synchronized (lock) { throwIfClosed(); return new CancellationToken(this); @@ -84,7 +86,7 @@ public void cancelAfter(final long delay) { cancelAfter(delay, TimeUnit.MILLISECONDS); } - private void cancelAfter(long delay, TimeUnit timeUnit) { + private void cancelAfter(long delay, @NonNull TimeUnit timeUnit) { if (delay < -1) { throw new IllegalArgumentException("Delay must be >= -1"); } @@ -137,7 +139,8 @@ public void close() { } } - /* package */ CancellationTokenRegistration register(Runnable action) { + /* package */ @NonNull + CancellationTokenRegistration register(@NonNull Runnable action) { CancellationTokenRegistration ctr; synchronized (lock) { throwIfClosed(); @@ -165,7 +168,7 @@ public void close() { } } - /* package */ void unregister(CancellationTokenRegistration registration) { + /* package */ void unregister(@NonNull CancellationTokenRegistration registration) { synchronized (lock) { throwIfClosed(); registrations.remove(registration); @@ -178,7 +181,7 @@ public void close() { // to be synchronized with state changes you should provide external synchronization. // If this is invoked without external synchronization there is a probability the token becomes // cancelled concurrently. - private void notifyListeners(List registrations) { + private void notifyListeners(@NonNull List registrations) { for (CancellationTokenRegistration registration : registrations) { registration.runAction(); } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/Capture.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/Capture.java index 1689c354bb9e46..e2e0ddf0a213f9 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/Capture.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/Capture.java @@ -7,25 +7,27 @@ package com.facebook.react.bridgeless.internal.bolts; +import androidx.annotation.Nullable; + /** * Provides a class that can be used for capturing variables in an anonymous class implementation. * * @param */ public class Capture { - private T value; + private @Nullable T value; public Capture() {} - public Capture(T value) { + public Capture(@Nullable T value) { this.value = value; } - public T get() { + public @Nullable T get() { return value; } - public void set(T value) { + public void set(@Nullable T value) { this.value = value; } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/Continuation.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/Continuation.java index c271f9d70c3248..f11a224caf1ce0 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/Continuation.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/Continuation.java @@ -7,6 +7,9 @@ package com.facebook.react.bridgeless.internal.bolts; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + /** * A function to be called after a task completes. * @@ -16,5 +19,6 @@ * @see Task */ public interface Continuation { - TContinuationResult then(Task task) throws Exception; + @Nullable + TContinuationResult then(@NonNull Task task) throws Exception; } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/ExecutorException.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/ExecutorException.java index 146acdbc1e635a..e6d4990238a9a0 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/ExecutorException.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/ExecutorException.java @@ -7,13 +7,15 @@ package com.facebook.react.bridgeless.internal.bolts; +import androidx.annotation.Nullable; + /** * This is a wrapper class for emphasizing that task failed due to bad {@code Executor}, rather than * the continuation block it self. */ public class ExecutorException extends RuntimeException { - public ExecutorException(Exception e) { + public ExecutorException(@Nullable Exception e) { super("An exception was thrown by an Executor", e); } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/TaskCompletionSource.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/TaskCompletionSource.java index a82623161e9634..0c4566e85fea25 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/TaskCompletionSource.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/TaskCompletionSource.java @@ -7,6 +7,9 @@ package com.facebook.react.bridgeless.internal.bolts; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + /** * Allows safe orchestration of a task's completion, preventing the consumer from prematurely * completing the task. Essentially, it represents the producer side of a Task, providing @@ -15,7 +18,7 @@ */ public class TaskCompletionSource { - private final Task task; + @NonNull private final Task task; /** * Creates a TaskCompletionSource that orchestrates a Task. This allows the creator of a task to @@ -26,7 +29,7 @@ public TaskCompletionSource() { } /** @return the Task associated with this TaskCompletionSource. */ - public Task getTask() { + public @NonNull Task getTask() { return task; } @@ -36,12 +39,12 @@ public boolean trySetCancelled() { } /** Sets the result on the Task if the Task hasn't already been completed. */ - public boolean trySetResult(TResult result) { + public boolean trySetResult(@Nullable TResult result) { return task.trySetResult(result); } /** Sets the error on the Task if the Task hasn't already been completed. */ - public boolean trySetError(Exception error) { + public boolean trySetError(@Nullable Exception error) { return task.trySetError(error); } @@ -53,14 +56,14 @@ public void setCancelled() { } /** Sets the result of the Task, throwing if the Task has already been completed. */ - public void setResult(TResult result) { + public void setResult(@Nullable TResult result) { if (!trySetResult(result)) { throw new IllegalStateException("Cannot set the result of a completed task."); } } /** Sets the error of the Task, throwing if the Task has already been completed. */ - public void setError(Exception error) { + public void setError(@Nullable Exception error) { if (!trySetError(error)) { throw new IllegalStateException("Cannot set the error on a completed task."); } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/UnobservedErrorNotifier.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/UnobservedErrorNotifier.java index 808c497adeceec..b6aebef51e3e13 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/UnobservedErrorNotifier.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/UnobservedErrorNotifier.java @@ -7,15 +7,17 @@ package com.facebook.react.bridgeless.internal.bolts; +import androidx.annotation.Nullable; + /** * This class is used to retain a faulted task until either its error is observed or it is * finalized. If it is finalized with a task, then the uncaught exception handler is exected with an * UnobservedTaskException. */ class UnobservedErrorNotifier { - private Task task; + @Nullable private Task task; - public UnobservedErrorNotifier(Task task) { + public UnobservedErrorNotifier(@Nullable Task task) { this.task = task; } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/UnobservedTaskException.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/UnobservedTaskException.java index 793f49e5a8b4b8..b418c9a40fe8b1 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/UnobservedTaskException.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/UnobservedTaskException.java @@ -7,9 +7,11 @@ package com.facebook.react.bridgeless.internal.bolts; +import androidx.annotation.Nullable; + /** Used to signify that a Task's error went unobserved. */ public class UnobservedTaskException extends RuntimeException { - public UnobservedTaskException(Throwable cause) { + public UnobservedTaskException(@Nullable Throwable cause) { super(cause); } }