335 lines
12 KiB
Java
335 lines
12 KiB
Java
/*
|
|
* Minecraft Forge
|
|
* Copyright (c) 2016-2020.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation version 2.1
|
|
* of the License.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
package net.minecraftforge.common.util;
|
|
|
|
import java.util.HashSet;
|
|
import java.util.Objects;
|
|
import java.util.Optional;
|
|
import java.util.Set;
|
|
import java.util.concurrent.atomic.AtomicReference;
|
|
|
|
import javax.annotation.Nonnull;
|
|
import javax.annotation.Nullable;
|
|
import javax.annotation.ParametersAreNonnullByDefault;
|
|
|
|
import org.apache.logging.log4j.Level;
|
|
import org.apache.logging.log4j.LogManager;
|
|
import org.apache.logging.log4j.Logger;
|
|
|
|
import mcp.MethodsReturnNonnullByDefault;
|
|
import net.minecraftforge.common.capabilities.Capability;
|
|
|
|
/**
|
|
* This object encapsulates a lazy value, with typical transformation operations
|
|
* (map/ifPresent) available, much like {@link Optional}.
|
|
* <p>
|
|
* It also provides the ability to listen for invalidation, via
|
|
* {@link #addListener(NonNullConsumer)}. This method is invoked when the provider of
|
|
* this object calls {@link #invalidate()}.
|
|
* <p>
|
|
* To create an instance of this class, use {@link #of(NonNullSupplier)}. Note
|
|
* that this accepts a {@link NonNullSupplier}, so the result of the supplier
|
|
* must never be null.
|
|
* <p>
|
|
* The empty instance can be retrieved with {@link #empty()}.
|
|
*
|
|
* @param <T> The type of the optional value.
|
|
*/
|
|
@ParametersAreNonnullByDefault
|
|
@MethodsReturnNonnullByDefault
|
|
public class LazyOptional<T>
|
|
{
|
|
private final NonNullSupplier<T> supplier;
|
|
private AtomicReference<T> resolved;
|
|
private Set<NonNullConsumer<LazyOptional<T>>> listeners = new HashSet<>();
|
|
private boolean isValid = true;
|
|
|
|
private static final @Nonnull LazyOptional<Void> EMPTY = new LazyOptional<>(null);
|
|
private static final Logger LOGGER = LogManager.getLogger();
|
|
|
|
/**
|
|
* Construct a new {@link LazyOptional} that wraps the given
|
|
* {@link NonNullSupplier}.
|
|
*
|
|
* @param instanceSupplier The {@link NonNullSupplier} to wrap. Cannot return
|
|
* null, but can be null itself. If null, this method
|
|
* returns {@link #empty()}.
|
|
*/
|
|
public static <T> LazyOptional<T> of(final @Nullable NonNullSupplier<T> instanceSupplier)
|
|
{
|
|
return instanceSupplier == null ? empty() : new LazyOptional<>(instanceSupplier);
|
|
}
|
|
|
|
/**
|
|
* @return The singleton empty instance
|
|
*/
|
|
public static <T> LazyOptional<T> empty()
|
|
{
|
|
return EMPTY.cast();
|
|
}
|
|
|
|
/**
|
|
* This method hides an unchecked cast to the inferred type. Only use this if
|
|
* you are sure the type should match. For capabilities, generally
|
|
* {@link Capability#orEmpty(Capability, LazyOptional)} should be used.
|
|
*
|
|
* @return This {@link LazyOptional}, cast to the inferred generic type
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
public <X> LazyOptional<X> cast()
|
|
{
|
|
return (LazyOptional<X>)this;
|
|
}
|
|
|
|
private LazyOptional(@Nullable NonNullSupplier<T> instanceSupplier)
|
|
{
|
|
this.supplier = instanceSupplier;
|
|
}
|
|
|
|
private @Nullable T getValue()
|
|
{
|
|
if (!isValid)
|
|
return null;
|
|
if (resolved != null)
|
|
return resolved.get();
|
|
|
|
if (supplier != null)
|
|
{
|
|
resolved = new AtomicReference<>(null);
|
|
T temp = supplier.get();
|
|
if (temp == null)
|
|
{
|
|
LOGGER.catching(Level.WARN, new NullPointerException("Supplier should not return null value"));
|
|
return null;
|
|
}
|
|
resolved.set(temp);
|
|
return resolved.get();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private T getValueUnsafe()
|
|
{
|
|
T ret = getValue();
|
|
if (ret == null)
|
|
{
|
|
throw new IllegalStateException("LazyOptional is empty or otherwise returned null from getValue() unexpectedly");
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Check if this {@link LazyOptional} is non-empty.
|
|
*
|
|
* @return {@code true} if this {@link LazyOptional} is non-empty, i.e. holds a
|
|
* non-null supplier
|
|
*/
|
|
public boolean isPresent()
|
|
{
|
|
return supplier != null && isValid;
|
|
}
|
|
|
|
/**
|
|
* If non-empty, invoke the specified {@link NonNullConsumer} with the object,
|
|
* otherwise do nothing.
|
|
*
|
|
* @param consumer The {@link NonNullConsumer} to run if this optional is non-empty.
|
|
* @throws NullPointerException if {@code consumer} is null and this {@link LazyOptional} is non-empty
|
|
*/
|
|
public void ifPresent(NonNullConsumer<? super T> consumer)
|
|
{
|
|
Objects.requireNonNull(consumer);
|
|
T val = getValue();
|
|
if (isValid && val != null)
|
|
consumer.accept(val);
|
|
}
|
|
|
|
/**
|
|
* If a this {@link LazyOptional} is non-empty, return a new
|
|
* {@link LazyOptional} encapsulating the mapping function. Otherwise, returns
|
|
* {@link #empty()}.
|
|
* <p>
|
|
* The supplier inside this object is <strong>NOT</strong> resolved.
|
|
*
|
|
* @apiNote This method supports post-processing on optional values, without the
|
|
* need to explicitly check for a return status.
|
|
*
|
|
* @apiNote The returned value does not receive invalidation messages from the original {@link LazyOptional}.
|
|
* If you need the invalidation, you will need to manage them yourself.
|
|
*
|
|
* @param mapper A mapping function to apply to the mod object, if present
|
|
* @return A {@link LazyOptional} describing the result of applying a mapping
|
|
* function to the value of this {@link LazyOptional}, if a value is
|
|
* present, otherwise an empty {@link LazyOptional}
|
|
* @throws NullPointerException if {@code mapper} is null.
|
|
*/
|
|
public <U> LazyOptional<U> lazyMap(NonNullFunction<? super T, ? extends U> mapper)
|
|
{
|
|
Objects.requireNonNull(mapper);
|
|
return isPresent() ? of(() -> mapper.apply(getValueUnsafe())) : empty();
|
|
}
|
|
|
|
/**
|
|
* If a this {@link LazyOptional} is non-empty, return a new
|
|
* {@link Optional} encapsulating the mapped value. Otherwise, returns
|
|
* {@link Optional#empty()}.
|
|
*
|
|
* @apiNote This method explicitly resolves the value of the {@link LazyOptional}.
|
|
* For a non-resolving mapper that will lazily run the mapping, use {@link #lazyMap(NonNullFunction)}.
|
|
*
|
|
* @param mapper A mapping function to apply to the mod object, if present
|
|
* @return An {@link Optional} describing the result of applying a mapping
|
|
* function to the value of this {@link Optional}, if a value is
|
|
* present, otherwise an empty {@link Optional}
|
|
* @throws NullPointerException if {@code mapper} is null.
|
|
*/
|
|
public <U> Optional<U> map(NonNullFunction<? super T, ? extends U> mapper)
|
|
{
|
|
Objects.requireNonNull(mapper);
|
|
return isPresent() ? Optional.of(mapper.apply(getValueUnsafe())) : Optional.empty();
|
|
}
|
|
|
|
/**
|
|
* Resolve the contained supplier if non-empty, and filter it by the given
|
|
* {@link NonNullPredicate}, returning empty if false.
|
|
* <p>
|
|
* <em>It is important to note that this method is <strong>not</strong> lazy, as
|
|
* it must resolve the value of the supplier to validate it with the
|
|
* predicate.</em>
|
|
*
|
|
* @param predicate A {@link NonNullPredicate} to apply to the result of the
|
|
* contained supplier, if non-empty
|
|
* @return An {@link Optional} containing the result of the contained
|
|
* supplier, if and only if the passed {@link NonNullPredicate} returns
|
|
* true, otherwise an empty {@link Optional}
|
|
* @throws NullPointerException If {@code predicate} is null and this
|
|
* {@link Optional} is non-empty
|
|
*/
|
|
public Optional<T> filter(NonNullPredicate<? super T> predicate)
|
|
{
|
|
Objects.requireNonNull(predicate);
|
|
final T value = getValue(); // To keep the non-null contract we have to evaluate right now. Should we allow this function at all?
|
|
return value != null && predicate.test(value) ? Optional.of(value) : Optional.empty();
|
|
}
|
|
|
|
/**
|
|
* Resolves the value of this LazyOptional, turning it into a standard non-lazy {@link Optional<T>}
|
|
* @return The resolved optional.
|
|
*/
|
|
public Optional<T> resolve()
|
|
{
|
|
return isPresent() ? Optional.of(getValueUnsafe()) : Optional.empty();
|
|
}
|
|
|
|
/**
|
|
* Resolve the contained supplier if non-empty and return the result, otherwise return
|
|
* {@code other}.
|
|
*
|
|
* @param other the value to be returned if this {@link LazyOptional} is empty
|
|
* @return the result of the supplier, if non-empty, otherwise {@code other}
|
|
*/
|
|
public T orElse(T other)
|
|
{
|
|
T val = getValue();
|
|
return val != null ? val : other;
|
|
}
|
|
|
|
/**
|
|
* Resolve the contained supplier if non-empty and return the result, otherwise return the
|
|
* result of {@code other}.
|
|
*
|
|
* @param other A {@link NonNullSupplier} whose result is returned if this
|
|
* {@link LazyOptional} is empty
|
|
* @return The result of the supplier, if non-empty, otherwise the result of
|
|
* {@code other.get()}
|
|
* @throws NullPointerException If {@code other} is null and this
|
|
* {@link LazyOptional} is non-empty
|
|
*/
|
|
public T orElseGet(NonNullSupplier<? extends T> other)
|
|
{
|
|
T val = getValue();
|
|
return val != null ? val : other.get();
|
|
}
|
|
|
|
/**
|
|
* Resolve the contained supplier if non-empty and return the result, otherwise throw the
|
|
* exception created by the provided {@link NonNullSupplier}.
|
|
*
|
|
* @apiNote A method reference to the exception constructor with an empty
|
|
* argument list can be used as the supplier. For example,
|
|
* {@code IllegalStateException::new}
|
|
*
|
|
* @param <X> Type of the exception to be thrown
|
|
* @param exceptionSupplier The {@link NonNullSupplier} which will return the
|
|
* exception to be thrown
|
|
* @return The result of the supplier
|
|
* @throws X If this {@link LazyOptional} is empty
|
|
* @throws NullPointerException If {@code exceptionSupplier} is null and this
|
|
* {@link LazyOptional} is empty
|
|
*/
|
|
public <X extends Throwable> T orElseThrow(NonNullSupplier<? extends X> exceptionSupplier) throws X
|
|
{
|
|
T val = getValue();
|
|
if (val != null)
|
|
return val;
|
|
throw exceptionSupplier.get();
|
|
}
|
|
|
|
/**
|
|
* Register a {@link NonNullConsumer listener} that will be called when this {@link LazyOptional} becomes invalid (via {@link #invalidate()}).
|
|
* <p>
|
|
* If this {@link LazyOptional} is empty, the listener will be called immediately.
|
|
*/
|
|
public void addListener(NonNullConsumer<LazyOptional<T>> listener)
|
|
{
|
|
if (isPresent())
|
|
{
|
|
this.listeners.add(listener);
|
|
}
|
|
else
|
|
{
|
|
listener.accept(this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invalidate this {@link LazyOptional}, making it unavailable for further use,
|
|
* and notifying any {@link #addListener(NonNullConsumer) listeners} that this
|
|
* has become invalid and they should update.
|
|
* <p>
|
|
* This would typically be used with capability objects. For example, a TE would
|
|
* call this, if they are covered with a microblock panel, thus cutting off pipe
|
|
* connectivity to this side.
|
|
* <p>
|
|
* Also should be called for all when a TE is invalidated (for example, when
|
|
* the TE is removed or unloaded), or a world/chunk unloads, or a entity dies,
|
|
* etc... This allows modders to keep a cache of capability objects instead of
|
|
* re-checking them every tick.
|
|
*/
|
|
public void invalidate()
|
|
{
|
|
if (this.isValid)
|
|
{
|
|
this.isValid = false;
|
|
this.listeners.forEach(e -> e.accept(this));
|
|
}
|
|
}
|
|
}
|