/* * esodata - data structures and other things, of varying utility * Copyright 2022, Ben Culkin * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package bjc.data; import java.util.function.Function; import java.util.function.Supplier; import java.util.function.UnaryOperator; import bjc.data.internals.BoundLazy; import bjc.funcdata.FunctionalList; import bjc.funcdata.ListEx; /** * A holder that holds a means to create a value, but doesn't actually compute * the value until it's needed. * * @author ben * * @param * The type of the value being held. */ public class Lazy implements Holder { /* The supplier of the type. */ private Supplier valueSupplier; /* The actual type value. */ private ContainedType heldValue; /* Whether the value has been created. */ private boolean valueMaterialized; /* The list of pending actions on the value. */ private ListEx> actions = new FunctionalList<>(); /** * Create a new lazy value from the specified seed value. * * @param value * The seed value to use. */ public Lazy(final ContainedType value) { heldValue = value; valueMaterialized = true; } /** * Create a new lazy value from the specified value source. * * @param supp * The source of a value to use. */ public Lazy(final Supplier supp) { valueSupplier = new SingleSupplier<>(supp); valueMaterialized = false; } /* Create a new value from a supplier and a list of actions. */ private Lazy(final Supplier supp, final ListEx> pendingActions) { valueSupplier = supp; actions = pendingActions; } @Override public Holder bind(final Function> binder) { final ListEx> pendingActions = new FunctionalList<>(); for (UnaryOperator action : actions) { pendingActions.add(action); } final Supplier supplier = () -> { if (valueMaterialized) return heldValue; else return valueSupplier.get(); }; return new BoundLazy<>(() -> new Lazy<>(supplier, pendingActions), binder); } @Override public Function> lift(final Function func) { return val -> new Lazy<>(func.apply(val)); } @Override public Holder map(final Function mapper) { final ListEx> pendingActions = new FunctionalList<>(); for (UnaryOperator action : actions) { pendingActions.add(action); } return new Lazy<>(() -> { ContainedType currVal = heldValue; if (!valueMaterialized) { currVal = valueSupplier.get(); } return pendingActions.reduceAux(currVal, UnaryOperator::apply, value -> mapper.apply(value)); }); } @Override public String toString() { if (valueMaterialized) { if (actions.isEmpty()) { return String.format("value[v='%s']", heldValue); } else { return String.format("value[v='%s'] (has %d pending transforms)", heldValue, actions.getSize()); } } if (actions.isEmpty()) { return"(unmaterialized)"; } else { return String.format("(unmaterialized; has %d pending transforms", actions.getSize()); } } @Override public Holder transform(final UnaryOperator transformer) { actions.add(transformer); return this; } @Override public UnwrappedType unwrap(final Function unwrapper) { if (!valueMaterialized) { heldValue = valueSupplier.get(); valueMaterialized = true; } for (UnaryOperator action : actions) { heldValue = action.apply(heldValue); } actions = new FunctionalList<>(); return unwrapper.apply(heldValue); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (actions == null ? 0 : actions.hashCode()); result = prime * result + (heldValue == null ? 0 : heldValue.hashCode()); result = prime * result + (valueMaterialized ? 1231 : 1237); return result; } @Override public boolean equals(final Object obj) { if (this == obj) return true; if (obj == null) return false; if (!(obj instanceof Lazy)) return false; final Lazy other = (Lazy) obj; if (valueMaterialized != other.valueMaterialized) return false; if (valueMaterialized) { if (heldValue == null) { if (other.heldValue != null) return false; } else if (!heldValue.equals(other.heldValue)) { return false; } } else { return false; } if (actions == null) { if (other.actions != null) return false; } else if (actions.getSize() > 0 || other.actions.getSize() > 0) { return false; } return true; } /** * Create a new lazy container with an already present value. * * @param The type of the contained value. * * @param val The value for the lazy container. * * @return A new lazy container holding that value. */ public static Lazy lazy(final ContainedType val) { return new Lazy<>(val); } /** * Create a new lazy container with a suspended value. * * @param The type of the contained value. * * @param supp The suspended value for the lazy container. * * @return A new lazy container that will un-suspend the value when necessary. */ public static Lazy lazy(final Supplier supp) { return new Lazy<>(supp); } }