1 /* 2 * Copyright 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.pdf.data; 18 19 import androidx.annotation.RestrictTo; 20 import androidx.annotation.WorkerThread; 21 import androidx.pdf.util.ObservableValue; 22 import androidx.pdf.util.Preconditions; 23 24 import org.jspecify.annotations.NonNull; 25 import org.jspecify.annotations.Nullable; 26 27 import java.util.ArrayList; 28 import java.util.Arrays; 29 import java.util.Collection; 30 import java.util.HashSet; 31 import java.util.Set; 32 import java.util.concurrent.Semaphore; 33 34 /** Basic {@link FutureValue}s and related helpers. */ 35 @RestrictTo(RestrictTo.Scope.LIBRARY) 36 public class FutureValues { FutureValues()37 private FutureValues() { 38 } 39 40 /** Creates a {@link FutureValue} with the given value. */ newImmediateValue(T value)41 public static <T> @NonNull FutureValue<T> newImmediateValue(T value) { 42 return new ImmediateValue<T>(value); 43 } 44 45 /** 46 * Creates a {@link FutureValue} with an immediate failure (the passed value). 47 * 48 * @param error the exception to be delivered on fail. 49 */ immediateFail(final @NonNull Exception error)50 public static <T> @NonNull FutureValue<T> immediateFail(final @NonNull Exception error) { 51 return callback -> callback.failed(error); 52 } 53 54 /** Creates a {@link FutureValue} that decouples setting and reading progress and results. */ newSettableValue()55 public static <T> @NonNull SettableFutureValue<T> newSettableValue() { 56 return new SettableFutureValue<T>(); 57 } 58 59 /** A {@link FutureValue.Callback} wrapper interface around a {@link SettableFutureValue}. */ setterCallback( final @NonNull SettableFutureValue<T> targetFuture)60 public static <T> FutureValue.@NonNull Callback<T> setterCallback( 61 final @NonNull SettableFutureValue<T> targetFuture) { 62 return new FutureValue.Callback<T>() { 63 64 @Override 65 public void available(T value) { 66 targetFuture.set(value); 67 } 68 69 @Override 70 public void failed(@NonNull Throwable thrown) { 71 targetFuture.fail(thrown); 72 } 73 74 @Override 75 public void progress(float progress) { 76 targetFuture.progress(progress); 77 } 78 }; 79 } 80 81 /** 82 * Creates a {@link FutureValue} that converts the results of a source {@link FutureValue} on 83 * the fly. If the conversion fails, the resulting future will report a failure with the 84 * corresponding exception. 85 * 86 * @param sourceFuture the source {@link FutureValue}. 87 * @param converter the {@link Converter} used to convert results from source to returned 88 */ 89 public static <F, T> @NonNull FutureValue<T> convert(final @NonNull FutureValue<F> sourceFuture, 90 final @NonNull Converter<F, T> converter) { 91 return new FutureValue<T>() { 92 @Override 93 public void get(final FutureValue.Callback<T> target) { 94 final FutureValue.Callback<F> convertCallback = new FutureValue.Callback<F>() { 95 @Override 96 public void progress(float progress) { 97 target.progress(progress); 98 } 99 100 @Override 101 public void failed(@NonNull Throwable thrown) { 102 target.failed(thrown); 103 } 104 105 @Override 106 public void available(F sourceValue) { 107 try { 108 target.available(converter.convert(sourceValue)); 109 } catch (Exception e) { 110 target.failed(e); 111 } 112 } 113 }; 114 115 sourceFuture.get(convertCallback); 116 } 117 }; 118 } 119 120 /** Creates a new Converter that chains 2 other converters. */ 121 public static <F, T, V> @NonNull Converter<F, T> combine( 122 final @NonNull Converter<F, V> converter1, final @NonNull Converter<V, T> converter2) { 123 return new Converter<F, T>() { 124 @Override 125 public T convert(F from) { 126 return converter2.convert(converter1.convert(from)); 127 } 128 }; 129 } 130 131 /** 132 * Observe an {@link ObservableValue} as a {@link FutureValue}: wait for it to next hit one of 133 * the given target values. It will start observing the observable value after 134 * {@link FutureValue#get} is called. The Future might complete immediately (if the observable 135 * value already equals the target), in the future (when it changes to it), or never. It never 136 * fails (Note that there is not time-out to trigger an automatic fail in case the condition 137 * never materializes). 138 * 139 * <p>This {@link FutureValue} cannot be used more than once (i.e. a second call to {@link 140 * FutureValue#get} is illegal). 141 * 142 * @param obs the observable value. 143 * @param target the value(s) that will trigger the {@link FutureValue}'s completion. 144 * @return A {@link FutureValue} that completes when the observable value changes to target. 145 */ 146 @SafeVarargs 147 public static <T> @NonNull FutureValue<T> observeAsFuture(final @NonNull ObservableValue<T> obs, 148 T @NonNull ... target) { 149 Preconditions.checkNotNull(obs); 150 final Set<T> targetSet = new HashSet<>(Arrays.asList(target)); 151 return new FutureValue<T>() { 152 153 /** 154 * key is null as long as this Future hasn't been used (before 155 * {@link FutureValue#get} is called). It becomes non-null after that (holds the 156 * {@link ValueObserver}'s registration key if applicable). 157 */ 158 private @Nullable Object mKey; 159 160 @Override 161 public void get(final FutureValue.Callback<T> callback) { 162 Preconditions.checkState(mKey == null, "Can't reuse this future twice: " + mKey); 163 if (targetSet.contains(obs.get())) { 164 mKey = Boolean.TRUE; 165 callback.available(obs.get()); 166 } else { 167 mKey = obs.addObserver((oldValue, newValue) -> { 168 if (targetSet.contains(newValue)) { 169 obs.removeObserver(mKey); 170 mKey = Boolean.FALSE; 171 callback.available(newValue); 172 } 173 }); 174 } 175 } 176 }; 177 } 178 179 /** 180 * An object that can be used for converting an object of type F to one of type T. 181 * 182 * @param <F> original type of the object 183 * @param <T> type to be converted to 184 */ 185 public interface Converter<F, T> { 186 187 /** Converts object of type F to type T */ 188 T convert(F from); 189 } 190 191 /** 192 * A convenient base implementation of {@link FutureValue.Callback} that does nothing. It 193 * assumes no failures are reported and thus handles them. 194 * Subclasses should override {@link #failed} if failures are expected. 195 * 196 * @param <T> the type of result 197 */ 198 public static class SimpleCallback<T> implements FutureValue.Callback<T> { 199 200 @Override 201 public void available(T value) { 202 } 203 204 @Override 205 public void failed(@NonNull Throwable thrown) { 206 } 207 208 @Override 209 public void progress(float progress) { 210 } 211 212 @Override 213 public @NonNull String toString() { 214 return "SimpleCallback (unspecified)"; 215 } 216 } 217 218 /** 219 * Allows for a blocking get. 220 * 221 * <p>Useful for when waiting for an execution to complete. 222 * 223 * @param <T> the type of result 224 */ 225 public static class BlockingCallback<T> extends SimpleCallback<T> { 226 Semaphore mSemaphore = new Semaphore(0); 227 T mSuccess = null; 228 Throwable mFail = null; 229 230 @Override 231 public void available(T value) { 232 mSuccess = value; 233 mSemaphore.release(); 234 } 235 236 @Override 237 public void failed(@NonNull Throwable thrown) { 238 mFail = thrown; 239 mSemaphore.release(); 240 } 241 242 /** Acquires semaphore until data comes through callback. */ 243 @WorkerThread 244 public T getBlocking() { 245 try { 246 mSemaphore.acquire(); // wait until data comes through the callback. 247 } catch (InterruptedException e) { 248 throw new IllegalStateException(e); 249 } finally { 250 // Release as someone else is probably now waiting 251 mSemaphore.release(); 252 } 253 if (mFail != null) { 254 throw new IllegalStateException(mFail); 255 } 256 return mSuccess; 257 } 258 } 259 260 /** 261 * A simple {@link FutureValue} that is constructed with a value and returns it immediately when 262 * {@link #get(Callback)} is called. 263 */ 264 private static class ImmediateValue<T> implements FutureValue<T> { 265 private final T mValue; 266 267 ImmediateValue(T value) { 268 this.mValue = value; 269 } 270 271 @Override 272 public void get(Callback<T> callback) { 273 callback.available(mValue); 274 } 275 } 276 277 /** 278 * A {@link FutureValue} which wraps a {@link Supplier} for another {@link FutureValue}. This 279 * is designed so that a computation which returns a {@link FutureValue} is not started until 280 * {@link #get(Callback)} is called for the first time (a "lazy" future). 281 * 282 * @param <T> the type of the value 283 */ 284 public static class DeferredFutureValue<T> implements FutureValue<T> { 285 286 /** 287 * Represents an operation which should not be started until {@link #get(Callback)} is 288 * called for the first time. 289 */ 290 private final Supplier<FutureValue<T>> mComputation; 291 292 public DeferredFutureValue(@NonNull Supplier<FutureValue<T>> computation) { 293 this.mComputation = computation; 294 } 295 296 @Override 297 public void get(@Nullable Callback<T> callback) { 298 try { 299 mComputation.supply(progress -> { 300 }).get(callback); 301 } catch (Exception e) { 302 // Most errors should be piped through the computation's Future to the callback. 303 // This captures errors thrown during the supply. 304 callback.failed(e); 305 } 306 } 307 } 308 309 /** 310 * A {@link FutureValue} that accepts multiple {@link FutureValue.Callback}s and can be set. 311 * 312 * @param <T> the type of the value to be returned 313 */ 314 public static class SettableFutureValue<T> implements FutureValue<T> { 315 private final Collection<Callback<T>> mCallbacks = new ArrayList<>(2); 316 private T mValue; 317 private Throwable mThrown; 318 319 /** 320 * Set the successful value resulting from the asynchronous operation. Any callbacks will be 321 * called immediately. The value will be retained to call any new callbacks. All callback 322 * references will be removed. 323 * 324 * @param value The result of the asynchronous operation. 325 */ 326 public void set(T value) { 327 Preconditions.checkNotNull(value); 328 checkNotSet(value.toString()); 329 this.mValue = value; 330 for (Callback<T> callback : mCallbacks) { 331 callback.available(value); 332 } 333 mCallbacks.clear(); 334 } 335 336 /** 337 * Notify all the callbacks of a progress update. This implementation will not store 338 * intermediate progress values. So if a callback is registered after the last progress 339 * value has been sent, it will not get the previous progress values. Progress is no longer 340 * reported after this future completes (i.e. if {@link #isSet()}). 341 * 342 * @param progress is a measure of how much progress has been done [0-1]. 343 */ 344 public void progress(float progress) { 345 if (!isSet()) { 346 for (Callback<T> callback : mCallbacks) { 347 callback.progress(progress); 348 } 349 } 350 } 351 352 /** 353 * Set the exception thrown while getting the value. Any callbacks will be called 354 * immediately. The exception will be retained to call any new callbacks. All callback 355 * references will be removed. 356 * 357 * @param thrown The problem encountered while getting the result of the asynchronous 358 * operation. 359 */ 360 public void fail(@NonNull Throwable thrown) { 361 checkNotSet(thrown.toString()); 362 this.mThrown = Preconditions.checkNotNull(thrown); 363 for (Callback<T> callback : mCallbacks) { 364 callback.failed(thrown); 365 } 366 mCallbacks.clear(); 367 } 368 369 /** 370 * Wraps a {@link String} error message in an {@link Exception}. Otherwise, it has the same 371 * behaviour as {@link SettableFutureValue#fail(Throwable)}. 372 */ 373 public void fail(@NonNull String errorMessage) { 374 fail(new Exception(errorMessage)); 375 } 376 377 @Override 378 public void get(@Nullable Callback<T> callback) { 379 if (mValue != null) { 380 callback.available(mValue); 381 } else if (mThrown != null) { 382 callback.failed(mThrown); 383 } else { 384 Preconditions.checkNotNull(callback); 385 mCallbacks.add(callback); 386 } 387 } 388 389 public boolean isSet() { 390 return (mValue != null || mThrown != null); 391 } 392 393 private void checkNotSet(String newValue) { 394 Preconditions.checkState(mValue == null, 395 String.format("Value has already been set (%s) : %s", mValue, newValue)); 396 Preconditions.checkState(mThrown == null, 397 String.format("Exception was already set (%s) : %s", mThrown, newValue)); 398 } 399 } 400 } 401