/* This file is part of Reactive Cascade which is released under The MIT License. See license.md , https://github.com/futurice/cascade and http://reactivecascade.com for details. This is open source for the common good. Please contribute improvements by pull request or contact paulirotta@gmail.com */ package com.reactivecascade.functional; import android.support.annotation.CallSuper; import android.support.annotation.CheckResult; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.reactivecascade.Async; import com.reactivecascade.BuildConfig; import com.reactivecascade.i.CallOrigin; import com.reactivecascade.i.IAction; import com.reactivecascade.i.IActionOne; import com.reactivecascade.i.IActionOneR; import com.reactivecascade.i.IActionR; import com.reactivecascade.i.IAltFuture; import com.reactivecascade.i.IReactiveTarget; import com.reactivecascade.i.ISettableAltFuture; import com.reactivecascade.i.IThreadType; import com.reactivecascade.i.NotCallOrigin; import com.reactivecascade.util.AssertUtil; import com.reactivecascade.util.Origin; import com.reactivecascade.util.RCLog; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; /** * The common base class for default implementations such as {@link SettableAltFuture} and {@link RunnableAltFuture}. * Most developers will not need to concern themselves with this abstract class. * <p> * {@link RunnableAltFuture} overrides this class. * TODO You may also use a {@link SettableAltFuture} to inject data where the from is determined from entirely outside of the current chain hierarchy. * This is currently an experimental feature so be warned, your results and chain behaviour may vary. Additional * testing is on the long list. * <p> * You may prefer to use {@link ImmutableValue} that a similar need in some cases. That is a * slightly faster, simpler implementation than {@link SettableAltFuture}. * <p> * TODO Would it be helpful for debugging to store and pass forward a reference to the object which originally detected the problem? It might help with filtering what mOnFireAction you want to do mOnError */ @NotCallOrigin public abstract class AbstractAltFuture<IN, OUT> extends Origin implements IAltFuture<IN, OUT> { /** * The state returned by a function which has no value, but is finished running. * <p> * In some functional styles this is (somewhat confusingly) a "Null" object passed along the chain. * We prefer to name each state explicity for debuggability and disambiguation. */ public static final State COMPLETE = new AbstractState() { @NonNull @Override public String toString() { return "COMPLETE"; } }; /* * TODO It should be possible to refactor and eliminate the FORKED state in production builds for performance, using only ZEN plus a single state change * This would however result in more debugging difficulty and the loss of certain broken logic tests so * it should be configurable for production builds. Library users debugging their logic would not know at they time * of .fork() if the operation has already been forked due to an error in their code. They * would only find out much later. Perhaps this is acceptable in production since the final state * change can occur only once, but impure functions would exert their side effect multiple times if * forked multiple times. If the debug pattern remains clear and side effects are idempotent this * might be worth the performance gained. */ protected static final State FORKED = new AbstractState() { @NonNull @Override public String toString() { return "FORKED"; } }; protected final AtomicReference<Object> stateAR = new AtomicReference<>(VALUE_NOT_AVAILABLE); @NonNull protected final IThreadType threadType; protected final CopyOnWriteArraySet<IAltFuture<OUT, ?>> downchainAltFutures = new CopyOnWriteArraySet<>(); // Callable split IThreadType actions to start after this mOnFireAction completes private final AtomicReference<IAltFuture<?, ? extends IN>> upchainAltFutureAR = new AtomicReference<>(); /** * Create, from is not yet determined * * @param threadType on which this alt future will evaluate and fire downchain events */ public AbstractAltFuture(@NonNull final IThreadType threadType) { this.threadType = threadType; } @Override // IAltFuture @CallOrigin @CallSuper public boolean cancel(@NonNull String reason) { AltFutureStateCancelled state = new AltFutureStateCancelled(reason); if (stateAR.compareAndSet(VALUE_NOT_AVAILABLE, state) || stateAR.compareAndSet(FORKED, state)) { RCLog.d(this, "Cancelled: reason=" + reason); return true; } Object s = stateAR.get(); if (s instanceof StateCancelled) { RCLog.d(this, "Ignoring duplicate cancel(\"" + reason + "\"). state=" + s); } else { RCLog.d(this, "Ignoring cancel(\"" + reason + "\"). state=" + s); } return false; } @Override // IAltFuture public boolean cancel(@NonNull StateError stateError) { Object state = this.stateAR.get(); StateCancelled stateCancelled = new StateCancelled() { private final ImmutableValue<String> mOrigin = RCLog.originAsync(); @NonNull @Override public ImmutableValue<String> getOrigin() { return mOrigin; } @NonNull @Override public String getReason() { return "Cancelled by upchain error=" + getStateError(); } @Nullable @Override public StateError getStateError() { return stateError; } }; if (stateAR.compareAndSet(VALUE_NOT_AVAILABLE, stateCancelled) || stateAR.compareAndSet(FORKED, stateCancelled)) { RCLog.d(this, "Cancelled from state " + state); final Exception e = forEachThen(ignore -> onCancelled(stateCancelled)); if (e != null) { RCLog.throwRuntimeException(this, "Problem executing onCancelled() downchain actions", e); } return true; } RCLog.d(this, "Ignoring cancel(" + stateError + "). state=" + stateAR.get()); return false; } @Override // IAltFuture public boolean isCancelled() { return isCancelled(stateAR.get()); } private boolean isCancelled(@NonNull Object objectThatMayBeAState) { return objectThatMayBeAState instanceof StateCancelled; } @Override // IAltFuture public final boolean isDone() { return isDone(stateAR.get()); } protected boolean isDone(@NonNull Object state) { return state != VALUE_NOT_AVAILABLE && state != FORKED; } @Override // IAltFuture public final boolean isForked() { return isForked(stateAR.get()); } protected boolean isForked(@NonNull Object state) { return state != VALUE_NOT_AVAILABLE; } @Override // IAltFuture @NonNull public IAltFuture<IN, OUT> fork() { IAltFuture<?, ? extends IN> previousAltFuture = getUpchain(); if (previousAltFuture != null && !previousAltFuture.isDone()) { RCLog.v(this, "Previous IAltFuture not forked, searching upchain: " + previousAltFuture); previousAltFuture.fork(); return this; } Object s = null; if (Async.USE_FORKED_STATE ? !stateAR.compareAndSet(VALUE_NOT_AVAILABLE, FORKED) : (s = stateAR.get()) != VALUE_NOT_AVAILABLE) { if (s == null) { s = stateAR.get(); } if (s instanceof StateCancelled || s instanceof StateError) { RCLog.v(getOrigin(), "Can not fork(), RunnableAltFuture was cancelled: " + s); return this; } RCLog.i(getOrigin(), "Possibly a legitimate race condition. Ignoring duplicate fork(), already fork()ed or set(): " + s); return this; } doFork(); return this; } protected abstract void doFork(); /** * Implementations of {@link #fork()} must call this when completed. It reduces the window of time * in which past intermediate calculation values in a active chain are held in memory. It is * the equivalent of the (illegal) statement: * <code>{@link #setUpchain(IAltFuture)}</code> to null. * <p> * This may not be done until {@link #isDone()} == true, such as when the {@link #fork()} has completed. */ protected final void clearPreviousAltFuture() { AssertUtil.assertTrue(isDone()); this.upchainAltFutureAR.lazySet(null); } @Override // IAltFuture @Nullable public final IAltFuture<?, ? extends IN> getUpchain() { return this.upchainAltFutureAR.get(); } @Override // IAltFuture public void setUpchain(@NonNull IAltFuture<?, ? extends IN> altFuture) { boolean set = this.upchainAltFutureAR.compareAndSet(null, altFuture); if (!set) { RCLog.v(this, "Second setUpchain(), merging two chains. Neither can proceed past this point until both burn to this point."); } } @Override // IAltFuture @NonNull @SuppressWarnings("unchecked") public OUT get() { Object state = stateAR.get(); if (!isDone(state)) { RCLog.throwIllegalStateException(this, getOrigin(), "Attempt to get() RunnableAltFuture that is not yet finished. state=" + state); } if (isCancelled(state)) { RCLog.throwIllegalStateException(this, getOrigin(), "Attempt to get() RunnableAltFuture that is cancelled: state=" + state); } return (OUT) state; } @Override // IAltFuture @NonNull @SuppressWarnings("unchecked") public OUT safeGet() { Object state = stateAR.get(); if (!isDone(state) || isCancelled(state)) { return (OUT) VALUE_NOT_AVAILABLE; } return (OUT) state; } @Override // IAltFuture @NonNull public final IThreadType getThreadType() { return this.threadType; } /** * Perform some action on an instantaneous snapshot of the list of .subscribe() down-chain actions * * @param action * @throws Exception */ protected Exception forEachThen(@NonNull IActionOne<IAltFuture<OUT, ?>> action) { Exception exception = null; for (IAltFuture<OUT, ?> altFuture : downchainAltFutures) { try { action.call(altFuture); } catch (Exception e) { if (exception == null) { exception = e; } RCLog.e(this, "Problem with forEachThen(): " + e); } } return exception; } @NotCallOrigin @NonNull @SuppressWarnings("unchecked") @Override // IAltFuture public ISettableAltFuture<OUT> onError(@NonNull IActionOne<Exception> onErrorAction) { return (ISettableAltFuture<OUT>) then(new OnErrorAltFuture<OUT>(threadType, onErrorAction)); } @NotCallOrigin @NonNull @SuppressWarnings("unchecked") @Override // IAltFuture public ISettableAltFuture<OUT> onCancelled(@NonNull IActionOne<String> onCancelledAction) { return (ISettableAltFuture<OUT>) then(new OnCancelledAltFuture<OUT>(threadType, onCancelledAction)); } @NotCallOrigin @Override // IAltFuture public void onError(@NonNull StateError stateError) throws Exception { RCLog.d(this, "Handling onError(): " + stateError); if (!this.stateAR.compareAndSet(VALUE_NOT_AVAILABLE, stateError) || (Async.USE_FORKED_STATE && !this.stateAR.compareAndSet(FORKED, stateError))) { RCLog.i(this, "Will not repeat onError() because IAltFuture state is already determined: " + stateAR.get()); return; } Exception e = forEachThen(af -> { af.onError(stateError); }); if (e != null) { throw e; } } @Override // IAltFuture public void onCancelled(@NonNull StateCancelled stateCancelled) throws Exception { RCLog.v(this, "Handling onCancelled for reason=" + stateCancelled); if (!this.stateAR.compareAndSet(VALUE_NOT_AVAILABLE, stateCancelled) && !this.stateAR.compareAndSet(FORKED, stateCancelled)) { RCLog.i(this, "Can not onCancelled because IAltFuture state is already determined: " + stateAR.get()); return; } Exception e = forEachThen(altFuture -> { altFuture.onCancelled(stateCancelled); }); if (e != null) { throw e; } } //----------------------------------- .then() actions --------------------------------------------- protected void doThen() { AssertUtil.assertTrue("doThen(): state=" + stateAR.get(), isDone()); Exception e = forEachThen(IAltFuture::fork); if (e != null) { throw new IllegalStateException("Problem completing downchain actions", e); } } /** * Continue downchain actions on the specified {@link IThreadType} * * @param theadType the thread execution group to change to for the next chain operation * @return the previous chain link masked to reflect the new {@link IThreadType} */ @NonNull @Override @CheckResult(suggest = IAltFuture.CHECK_RESULT_SUGGESTION) public IAltFuture<?, OUT> on(@NonNull IThreadType theadType) { if (theadType == threadType) { return this; } return then(new SettableAltFuture<>(theadType)); } @NonNull @Override public IAltFuture<OUT, OUT> then(@NonNull IAction<OUT> action) { return then(new RunnableAltFuture<OUT, OUT>(threadType, action)); } @Override // IAltFuture @NonNull @CheckResult(suggest = IAltFuture.CHECK_RESULT_SUGGESTION) public IAltFuture<OUT, OUT> then(@NonNull IActionOne<OUT> action) { return then(new RunnableAltFuture<OUT, OUT>(threadType, action)); } @NonNull @Override @SuppressWarnings("unchecked") public ISettableAltFuture<OUT> then(@NonNull IActionOne<OUT>... actions) { AssertUtil.assertTrue("then(IActionOne...) with empty list of upchain things to await makes no sense", actions.length > 0); AssertUtil.assertTrue("then(IActionOne...) with single item in the list of upchain things to await is confusing. Use .then() instead", actions.length != 1); IAltFuture<OUT, OUT>[] altFutures = new RunnableAltFuture[actions.length]; for (int i = 0; i < actions.length; i++) { final IActionOne<OUT> a = actions[i]; altFutures[i] = then(new RunnableAltFuture<>(threadType, a::call)); } return await(altFutures); } @Override // IAltFuture @NonNull @CheckResult(suggest = IAltFuture.CHECK_RESULT_SUGGESTION) public <DOWNCHAIN_OUT> IAltFuture<OUT, DOWNCHAIN_OUT> then(@NonNull IActionR<DOWNCHAIN_OUT> action) { return then(new RunnableAltFuture<>(threadType, action)); } @Override // IAltFuture @NonNull @CheckResult(suggest = IAltFuture.CHECK_RESULT_SUGGESTION) public <DOWNCHAIN_OUT> IAltFuture<OUT, DOWNCHAIN_OUT> then(@NonNull IAltFuture<OUT, DOWNCHAIN_OUT> altFuture) { altFuture.setUpchain(this); this.downchainAltFutures.add(altFuture); if (isDone()) { // altFuture.map((IActionOne) v -> { // visualize(mOrigin.getName(), v.toString(), "RunnableAltFuture"); // }); altFuture.fork(); } return altFuture; } @NonNull @Override @SuppressWarnings("unchecked") public ISettableAltFuture<OUT> then(@NonNull IAction<? extends OUT>... actions) { AssertUtil.assertTrue("then(IActionOne...) with empty list of upchain things to await makes no sense", actions.length == 0); AssertUtil.assertTrue("then(IActionOne...) with single item in the list of upchain things to await is confusing. Use .then() instead", actions.length == 1); IAltFuture<?, ? extends OUT>[] altFutures = new RunnableAltFuture[actions.length]; for (int i = 0; i < actions.length; i++) { final IAction<? extends OUT> a = actions[i]; altFutures[i] = then(new RunnableAltFuture<>(threadType, a)); } return await(altFutures); } @Override // IAltFuture @NonNull @CheckResult(suggest = IAltFuture.CHECK_RESULT_SUGGESTION) public <DOWNCHAIN_OUT> IAltFuture<OUT, DOWNCHAIN_OUT> map(@NonNull IActionOneR<OUT, DOWNCHAIN_OUT> action) { return then(new RunnableAltFuture<>(threadType, action)); } @NonNull @Override @SuppressWarnings("unchecked") public <DOWNCHAIN_OUT> IAltFuture<OUT, DOWNCHAIN_OUT>[] map(@NonNull IActionOneR<OUT, DOWNCHAIN_OUT>... actions) { AssertUtil.assertTrue("map(IActionOneR...) with empty list of upchain things to await makes no sense", actions.length == 0); AssertUtil.assertTrue("map(IActionOneR...) with single item in the list of upchain things to await is confusing. Use .then() instead", actions.length == 1); IAltFuture<OUT, DOWNCHAIN_OUT>[] altFutures = new IAltFuture[actions.length]; for (int i = 0; i < actions.length; i++) { IActionOneR<OUT, DOWNCHAIN_OUT> a = actions[i]; altFutures[i] = new RunnableAltFuture<>(threadType, a); } return altFutures; } @NonNull @Override @SuppressWarnings("unchecked") public ISettableAltFuture<OUT> sleep(long sleepTime, @NonNull TimeUnit timeUnit) { ISettableAltFuture<OUT> outAltFuture = new SettableAltFuture<>(threadType); outAltFuture.setUpchain(this); final IAltFuture<?, ?> ignore = this.then(() -> { Async.TIMER.schedule(() -> { outAltFuture.set(get()); }, sleepTime, timeUnit); }); return outAltFuture; } // @NonNull // @CheckResult(suggest = IAltFuture.CHECK_RESULT_SUGGESTION) // @Override // IAltFuture // public ISettableAltFuture<OUT> await(@NonNull IAltFuture<?, ?> altFuture) { // ISettableAltFuture<OUT> outAltFuture = new SettableAltFuture<>(threadType); // // outAltFuture.setUpchain(this); // final IAltFuture<?, ?> ignore = altFuture.then(() -> { // outAltFuture.set(get()); // }); // // return outAltFuture; // } @NonNull @Override // IAltFuture public ISettableAltFuture<OUT> await(@NonNull IAltFuture<?, ?>... altFutures) { AssertUtil.assertTrue("await(IAltFuture...) with empty list of upchain things to await makes no sense", altFutures.length > 0); AssertUtil.assertTrue("await(IAltFuture...) with single item in the list of upchain things to await is confusing. Use .then() instead", altFutures.length != 1); ISettableAltFuture<OUT> outAltFuture = new SettableAltFuture<>(threadType); AtomicInteger downCounter = new AtomicInteger(altFutures.length); outAltFuture.setUpchain(this); for (IAltFuture<?, ?> upchainAltFuture : altFutures) { IAltFuture<?, ?> ignore = upchainAltFuture.then(() -> { if (downCounter.decrementAndGet() == 0) { outAltFuture.set(get()); } }); } return outAltFuture; } @Override // IAltFuture @NonNull @CheckResult(suggest = IAltFuture.CHECK_RESULT_SUGGESTION) public IAltFuture<IN, IN> filter(@NonNull IActionOneR<IN, Boolean> action) { return new RunnableAltFuture<>(threadType, in -> { if (!action.call(in)) { cancel("Filtered: " + in); } return in; } ); } // @Override // IAltFuture // @NonNull // @CheckResult(suggest = IAltFuture.CHECK_RESULT_SUGGESTION) // public IAltFuture<List<IN>, List<OUT>> map(@NonNull final IActionOneR<IN, OUT> action) { // return new RunnableAltFuture<>(threadType, // (List<IN> listIN) -> { // //TODO Mapping is single-threaded even for long lists or complex transforms // //TODO Idea: create the list of things to call(), and offer that to other threads in the ThreadType if they have freetime to help out // final List<OUT> outputList = new ArrayList<>(listIN.size()); // for (IN IN : listIN) { // outputList.add(action.call(IN)); // } // return outputList; // } // ); // } // @Override // IAltFuture // @NonNull // @CheckResult(suggest = IAltFuture.CHECK_RESULT_SUGGESTION) // public IAltFuture<List<IN>, List<IN>> filter(@NonNull final IActionOneR<IN, Boolean> action) { // return new RunnableAltFuture<>(threadType, // (List<IN> listIN) -> { // final List<IN> outputList = new ArrayList<>(listIN.size()); // for (IN IN : listIN) { // if (action.call(IN)) { // outputList.add(IN); // } // } // return outputList; // } // ); // } @Override // IAltFuture @NonNull @CheckResult(suggest = IAltFuture.CHECK_RESULT_SUGGESTION) public IAltFuture<OUT, OUT> set(@NonNull IReactiveTarget<OUT> reactiveTarget) { return then(reactiveTarget::fire); } protected static abstract class AbstractState extends Origin implements IAltFuture.State { } //=============================== End .then() actions ======================================== @NotCallOrigin protected final class AltFutureStateCancelled extends Origin implements StateCancelled { final String reason; AltFutureStateCancelled(@NonNull String reason) { if (BuildConfig.DEBUG && reason.length() == 0) { throw new IllegalArgumentException("You must specify the cancellation reason to keep debugging sane"); } this.reason = reason; com.reactivecascade.util.RCLog.d(this, "Moving to StateCancelled:\n" + this.reason); } /** * The reason this task was cancelled. This is for debug purposes. * * @return */ @NonNull @Override // StateCancelled public String getReason() { return reason; } /** * If the cancellation is because of an error state change elsewhere, provide the details * of that original cause also. * * @return */ @Nullable @Override public StateError getStateError() { return null; } @Override // Object @NonNull public String toString() { return "CANCELLED: reason=" + reason; } } /** * An atomic state change marking also the reason for entering the exception state */ @NotCallOrigin protected final class AltFutureStateError extends Origin implements StateError { @NonNull final String reason; @NonNull final Exception e; public AltFutureStateError(@NonNull String reason, @NonNull Exception e) { this.reason = reason; this.e = e; RCLog.e(this, "Moving to StateError:\n" + this.reason, e); } @Override // State @NonNull public Exception getException() { return e; } @Override // Object @NonNull public String toString() { return "ERROR: reason=" + reason + " error=" + e; } } /** * The on-cancelled action in a chain will be launched asynchonosly * <p> * Cancelled notifications are not consumed. All downchain items will also receive the * {@link #onCancelled(StateCancelled)} notification call synchronously. * <p> * Cancellation may occur from any thread. In the event of concurrent cancellation, {@link #onCancelled(StateCancelled)} * will be called exactly one time. */ public static class OnCancelledAltFuture<T> extends SettableAltFuture<T> { @NonNull private final IActionOne<String> mOnCancelledAction; /** * Constructor * * @param threadType the thread pool to run this command on * @param action a function that receives one input and no return from */ @SuppressWarnings("unchecked") public OnCancelledAltFuture(@NonNull IThreadType threadType, @NonNull IActionOne<String> action) { super(threadType); this.mOnCancelledAction = action; } @NotCallOrigin @Override // IAltFuture public void onCancelled(@NonNull StateCancelled stateCancelled) throws Exception { RCLog.d(this, "Handling onCancelled(): " + stateCancelled); if (!this.stateAR.compareAndSet(VALUE_NOT_AVAILABLE, stateCancelled) || (Async.USE_FORKED_STATE && !this.stateAR.compareAndSet(FORKED, stateCancelled))) { RCLog.i(this, "Will not onCancelled() because IAltFuture state is already determined: " + stateAR.get()); return; } threadType .from(stateCancelled.getReason()) .then(mOnCancelledAction) .fork(); Exception e = forEachThen(af -> { af.onCancelled(stateCancelled); }); if (e != null) { throw e; } } } }