package com.github.czyzby.kiwi.util.gdx.asset.lazy;
import com.badlogic.gdx.utils.Disposable;
import com.github.czyzby.kiwi.util.common.Nullables;
import com.github.czyzby.kiwi.util.gdx.asset.lazy.provider.ObjectProvider;
/** Wraps around an object, allowing to have a final reference to a lazy-initialized object. Adds a very small overhead,
* without the usual boilerplate that lazy objects require. Should be used for objects that are expensive to create and
* rarely (or - at least - not always) needed to ensure that they are created only when necessary. Concurrent use might
* result in multiple provider method calls if a non-concurrent lazy variant is used. Note that this wrapper should not
* be used for nullable objects - if the wrapped object is null, lazy considers that it was not initiated yet, so make
* sure that your providers never return null.
*
* @author MJ */
public class Lazy<Type> {
private ObjectProvider<? extends Type> provider;
private Type object;
/** Constructs an empty lazy object with no provider. Stored variable has to be set manually. */
public Lazy() {
this(null);
}
/** @param provider will provide wrapped object on first call. */
public Lazy(final ObjectProvider<? extends Type> provider) {
this.provider = provider;
}
/** @return a new instance of empty provider which can be managed manually. */
public static <Type> Lazy<Type> empty() {
return new Lazy<Type>();
}
/** @param provider will provide wrapped object on first call. */
public static <Type> Lazy<Type> providedBy(final ObjectProvider<? extends Type> provider) {
return new Lazy<Type>(provider);
}
/** @param provider will provide wrapped object on first call. Thread-safe. */
public static <Type> ConcurrentLazy<Type> concurrentProvidedBy(final ObjectProvider<? extends Type> provider) {
return new ConcurrentLazy<Type>(provider);
}
/** @param provider will provide wrapped disposable object on first call. */
public static <Type extends Disposable> DisposableLazy<Type> disposableProvidedBy(
final ObjectProvider<? extends Type> provider) {
return new DisposableLazy<Type>(provider);
}
/** @param provider will provide wrapped disposable object on first call. Thread-safe. */
public static <Type extends Disposable> ConcurrentDisposableLazy<Type> concurrentDisposableProvidedBy(
final ObjectProvider<? extends Type> provider) {
return new ConcurrentDisposableLazy<Type>(provider);
}
/** @return lazy-initiated object instance. Is never null, as long as the provider is properly created. */
public final Type get() {
if (object == null) {
object = getObjectInstance();
provider = null;
}
return object;
}
/** @return lazy-initiated object instance or null if not initiated. Will never invoke lazy object creation: this
* method avoids the null check and returns the current reference to the wrapped object as-is. */
public final Type getIfCreated() {
return object;
}
/** @param alternative will be returned if object is not initiated.
* @return lazy-initiated object instance if it was already created or the passed alternative. Will never invoke
* lazy object initiation. */
public final Type getOrElse(final Type alternative) {
return object == null ? alternative : object;
}
/** Allows to manually set the object, not relying on a provider. Use with care - throws IllegalStateException if
* variable is already not null. Although discouraged, using of this method can be preferred if extra overhead of
* the provider is not an option.
*
* @param object will be set as wrapped object.
* @throws IllegalStateException if wrapped object is already present. */
public void set(final Type object) throws IllegalStateException {
if (this.object != null) {
throw new IllegalStateException("Cannot set lazy variable - already initiated.");
}
this.object = object;
}
/** @return object instance provided by provider. Should be called once, upon object initiation. Cannot return
* null. */
protected Type getObjectInstance() {
validateProvider();
return provider.provide();
}
protected void validateProvider() {
if (provider == null) {
throw new IllegalStateException(
"Variable was not set and there is no provider - unable to retrieve lazy variable.");
}
}
/** @return direct reference to lazy provider. */
protected ObjectProvider<? extends Type> getProvider() {
return provider;
}
/** @return direct reference to lazy object. */
protected Type getObject() {
return object;
}
/** @return true if object is not null. */
public boolean isInitialized() {
return object != null;
}
@Override
public boolean equals(final Object object) {
return object instanceof Lazy<?> && Nullables.areEqual(this.object, ((Lazy<?>) object).object);
}
/** @return 0 if object was not initiated. Wrapped object's hash code if its present. */
@Override
public int hashCode() {
if (object == null) {
return 0;
}
return object.hashCode();
}
@Override
public String toString() {
return "Lazy [" + object + "]";
}
}