/*
* 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);
}
}