package bjc.utils.data.lazy; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; import bjc.utils.data.IHolder; import bjc.utils.funcdata.FunctionalList; import bjc.utils.funcdata.IFunctionalList; /** * Holds a single value of a specific type. This is used for indirect * references to data, and more specifically for accessing non-final * variables from a lambda. AKA the identity monad * * This is a lazy variant of {@link IHolder} * * @author ben * * @param * The type of the data being held */ public class LazyHolder implements IHolder, ILazy { private static final class LazyHolderHolder implements IHolder { private Supplier> holderSource; private IHolder holder; private IFunctionalList> actions = new FunctionalList<>(); public LazyHolderHolder(Supplier> source) { holderSource = source; } @Override public void doWith(Consumer action) { actions.add((val) -> { action.accept(val); return val; }); } @Override public IHolder map(Function transformer) { // TODO implement me throw new UnsupportedOperationException( "Mapping is not yet supported on bound holders"); } @Override public IHolder transform(Function transformer) { actions.add(transformer); return this; } @Override public E unwrap(Function unwrapper) { if (holder == null) { holder = holderSource.get(); } if (!actions.isEmpty()) { actions.forEach((transform) -> { holder.transform(transform); }); } return holder.unwrap(unwrapper); } @Override public IHolder bind(Function> binder) { return new LazyHolderHolder<>(() -> { return binder.apply(unwrap((val) -> val)); }); } @Override public String toString() { if (holderSource == null) { if (holder == null) { return "(null)"; } return holder.toString(); } if (holder == null) { return "(unmaterialized values)"; } return holder.toString(); } } private static final class LazyHolderSupplier implements Supplier { private IFunctionalList> pendingActions; private Function pendingTransform; private T2 heldValue; private Supplier heldSource; public LazyHolderSupplier(IFunctionalList> actons, Function transform, T2 heldValue, Supplier heldSource) { // Resolve latent bug I just realized. After a map, adding new // actions to the original holder could've resulted in changes // to all unactualized mapped values from that holder pendingActions = new FunctionalList<>(); for (Function action : actons.toIterable()) { pendingActions.add(action); } this.pendingTransform = transform; this.heldValue = heldValue; this.heldSource = heldSource; } @Override public NewT get() { if (heldValue == null) { return pendingActions.reduceAux(heldSource.get(), Function::apply, pendingTransform::apply); } return pendingActions.reduceAux(heldValue, Function::apply, pendingTransform::apply); } } /** * List of queued actions to be performed on realized values */ private IFunctionalList> actions = new FunctionalList<>(); /** * The value internally held by this lazy holder */ private T heldValue; /** * The source for a value held by this lazy holder */ private Supplier heldSource; /** * Create a new lazy holder with the given supplier * * @param source * The supplier for a value when it is neededs */ public LazyHolder(Supplier source) { if (source == null) { throw new NullPointerException("Source must be non-null"); } heldSource = source; heldValue = null; } /** * Create a new lazy holder with the given value * * @param value * The value held in the holder */ public LazyHolder(T value) { heldValue = value; } @Override public void doWith(Consumer action) { if (action == null) { throw new NullPointerException("Action must be non-null"); } transform((value) -> { // Do the action with the value action.accept(value); // Return the untransformed value return value; }); } @Override public IHolder map(Function transform) { if (transform == null) { throw new NullPointerException("Transform must be non-null"); } // Don't actually map until we need to return new LazyHolder<>(new LazyHolderSupplier<>(actions, transform, heldValue, heldSource)); } @Override public IHolder transform(Function transform) { if (transform == null) { throw new NullPointerException("Transform must be non-null"); } // Queue the transform until we need to apply it actions.add(transform); return this; } @Override public E unwrap(Function unwrapper) { if (unwrapper == null) { throw new NullPointerException("Unwrapper must be null"); } // Actualize ourselves if (heldValue == null) { heldValue = heldSource.get(); } // Apply all pending transforms actions.forEach((action) -> heldValue = action.apply(heldValue)); return unwrapper.apply(heldValue); } @Override public boolean isMaterialized() { if (heldSource != null) { // We're materialized if a value exists return heldValue == null; } // We're materialized by default return true; } @Override public boolean hasPendingActions() { return actions.isEmpty(); } @Override public void materialize() { // Only materialize if we haven't already if (!isMaterialized()) { heldValue = heldSource.get(); } } @Override public void applyPendingActions() { materialize(); actions.forEach((action) -> { heldValue = action.apply(heldValue); }); } @Override public IHolder bind(Function> binder) { return new LazyHolderHolder<>(() -> { return binder.apply(unwrap((val) -> val)); }); } @Override public String toString() { if (isMaterialized()) { if (hasPendingActions()) { return heldValue.toString() + " (has pending actions)"; } return heldValue.toString(); } return "(unmaterialized value)"; } }