/* 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.reactive; import android.support.annotation.CallSuper; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.reactivecascade.i.IActionOne; import com.reactivecascade.i.IActionOneR; import com.reactivecascade.i.IAltFuture; import com.reactivecascade.i.IReactiveSource; import com.reactivecascade.i.IReactiveValue; import com.reactivecascade.i.IThreadType; import com.reactivecascade.i.NotCallOrigin; import com.reactivecascade.util.AssertUtil; import com.reactivecascade.util.RCLog; import java.util.concurrent.atomic.AtomicReference; /** * Thread-safe reactive display of a variable getValue. Add one or more {@link IActionOne} * actions to update the display when the variable fires. Usually these can be added as Lambda expressions * referencing the UI element you would like to track the variable's getValue in an eventually-consistent * manner. * <p> * Note the all <code>get()</code>-style actions will return the latest getValue. Therefore asynchronous * calls may not result in all values * </p> * <p> * Bindings are thread safe. All reactiveTargets will refire concurrently if the {@link com.reactivecascade.i.IThreadType} * allows, but individual reactiveTargets will never be called concurrently or out-of-sequence. Multiple * changes to the bound getValue within a short time relative to the current speed of the * {@link com.reactivecascade.i.IThreadType} may coalesce into a single headFunctionalChainLink refire of only * the most recent getValue. Bound functions must be idempotent. Repeat firing of the same getValue * is filter under most but not all circumstances. This possibility is related to the use of * {@link java.lang.ref.WeakReference} of the previously fired getValue of each headFunctionalChainLink to minimize * memory load. * </p> */ @NotCallOrigin public class ReactiveValue<T> extends Subscription<T, T> implements IReactiveValue<T> { //TODO Check that reactive chains which are not yet asserted observe "cold" behavior until first assertion. Use ZEN<T> for clarity and null support? @SuppressWarnings("unchecked") private final AtomicReference<T> mValueAR = new AtomicReference<>((T) IAltFuture.VALUE_NOT_AVAILABLE); /** * Create a new AtomicValue * * @param name */ public ReactiveValue(@NonNull String name) { this(name, null, null, null); } /** * Create a new AtomicValue * * @param name * @param initialValue */ public ReactiveValue(@NonNull String name, @Nullable final T initialValue) { this(name, null, null, null); } /** * Create a new AtomicValue * * @param name * @param threadType * @param inputMapping * @param onError */ @SuppressWarnings("unchecked") public ReactiveValue(@NonNull String name, @Nullable IThreadType threadType, @Nullable IActionOneR<T, T> inputMapping, @Nullable IActionOne<Exception> onError) { super(name, null, threadType, inputMapping != null ? inputMapping : out -> out, onError); fire((T) IAltFuture.VALUE_NOT_AVAILABLE); } /** * Run all reactive functional chains bound to this {@link ReactiveValue}. * <p> * Normally you do not need to call this, it is called for you. Instead, call * {@link #set(Object)} to assert a new from. * <p> * You can also link this to receive multiple reactive updates as a * down-chain {@link IReactiveSource#subscribe(IActionOne)} * to receive and store reactive values. * <p> * You can also link into a active chain to receive individually constructed and fired updates using * <code> * <pre> * myAltFuture.subscribe(from -> myAtomicValue.set(from)) * </pre> * </code> * <p> * Both of these methods will automatically call <code>fire()</code> for you. * <p> * You may want to <code>fire()</code> manually on app startup after all your initial reactive chains are constructed. * This will heat up the reactive chain to initial state by flushing current values through the system. * <p> * All methods and receivers within a reactive chain are <em>supposed</em> to be idempotent to * multiple firing events. This * does not however mean the calls are free or give a good user experience and from as in the * case of requesting data multiple times from a server. You have been warned. */ @NotCallOrigin @CallSuper public void fire() { fire(mValueAR.get()); } @CallSuper @NonNull @Override // IAtomicValue, IGettable public T get() { T t = safeGet(); if (t == IAltFuture.VALUE_NOT_AVAILABLE) { throw new IllegalStateException("Can not get(), ReactiveValue is not yet asserted"); } return t; } @CallSuper @NonNull @Override // ISafeGettable public T safeGet() { return mValueAR.get(); } /** * Set the from in a thread-safe manner. * <p> * If set to <code>null</code>, the variable goes 'cold' and will not fire until set to a non-null from * * @param value the new from asserted * @return <code>true</code> if the asserted from is different from the previous from */ @CallSuper @Override // ISettable public void set(@NonNull T value) { T previousValue = AssertUtil.assertNotNull(mValueAR.getAndSet(value)); boolean valueChanged = !(value == previousValue || (value.equals(previousValue)) || previousValue.equals(value)); if (valueChanged) { RCLog.v(this, "Successful set(" + value + "), about to fire()"); fire(value); } else { // The from has not changed RCLog.v(this, "set() from=" + value + " was already the from, so no change"); } } @CallSuper @Override // IAtomicValue public boolean compareAndSet(@Nullable T expected, @Nullable T update) { final boolean success = this.mValueAR.compareAndSet(expected, update); if (success) { if (update != null) { RCLog.v(this, "Successful compareAndSet(" + expected + ", " + update + "), will fire"); fire(update); } else { RCLog.v(this, "Successful compareAndSet(" + expected + ", null). " + Class.class.getSimpleName() + " is now 'cold' and will not fire until set to a non-null from"); } } else { RCLog.d(this, "compareAndSet(" + expected + ", " + update + ") FAILED. The current from is " + get()); } return success; } @NonNull @Override // IReactiveValue public T getAndSet(@NonNull T value) { T t = mValueAR.getAndSet(value); if (t == null) { throw new IllegalStateException("Can not getAndSet(), ReactiveValue is not yet asserted"); } return t; } @NonNull @Override // ISafeGettable public String toString() { return safeGet().toString(); } }