org.codefilarete.tool.Nullable Maven / Gradle / Ivy
package org.codefilarete.tool;
import javax.annotation.ParametersAreNonnullByDefault;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import org.codefilarete.tool.function.Functions;
import org.codefilarete.tool.function.ThrowingConsumer;
import org.codefilarete.tool.function.ThrowingFunction;
/**
* Class that tries to fullfill use cases on which {@link java.util.Optional} is insufficient
*
* @author Guillaume Mary
*/
@ParametersAreNonnullByDefault
public class Nullable implements Supplier {
/**
* Shortcut for new Nullable(value)
*
* @param value a nullable value
* @param value type
* @return new Nullable(value)
*/
public static Nullable nullable(@javax.annotation.Nullable T value) {
return new Nullable<>(value);
}
/**
* Shortcut for new Nullable(value)
*
* @param value a nullable value
* @param value type
* @return new Nullable(value)
*/
public static Nullable nullable(Supplier value) {
return new Nullable<>(value);
}
/**
* Shortcut for new Nullable(input) + {@link #map(Function)}
*
* @param input a nullable value
* @param f a function to be applied on the input
* @param value type
* @return new Nullable(input).map(f)
*/
public static Nullable nullable(@javax.annotation.Nullable I input, Function f) {
return nullable(input).map(f);
}
/**
* Shortcut for new Nullable(input) + {@link #map(Function)}
*
* @param input a nullable value
* @param f a function to be applied on the input
* @param value type
* @return new Nullable(input).map(f)
*/
public static Nullable nullable(Supplier input, Function f) {
return nullable(input).map(f);
}
/**
* Shortcut for new Nullable(input) + {@link #map(Function)}
*
* @param input a nullable value
* @param f1 a function to be applied on the input
* @param f2 a second function to be applied on the result of the first function
* @param value type
* @return new Nullable(input).map(f1).map(f2)
*/
public static Nullable nullable(@javax.annotation.Nullable I input, Function f1, Function f2) {
return nullable(input).map(f1).map(f2);
}
/**
* Shortcut for new Nullable(input) + {@link #map(Function)}
*
* @param input a nullable value
* @param f1 a function to be applied on the input
* @param f2 a second function to be applied on the result of the first function
* @param value type
* @return new Nullable(input).map(f1).map(f2)
*/
public static Nullable nullable(Supplier input, Function f1, Function f2) {
return nullable(input).map(f1).map(f2);
}
/**
* @param type of the {@link Nullable}
* @return a {@link Nullable} of null
*/
public static Nullable empty() {
return Nullable.nullable(() -> null);
}
/** The payload, may be null itself or owned value may be null */
private Supplier value;
/**
* Constructor from a nullable value
* @param value a nullable object
*/
private Nullable(@javax.annotation.Nullable T value) {
this.value = () -> value;
}
/**
* Constructor from a non null supplier which may supply a null value
* @param value a non null supplier
*/
private Nullable(Supplier value) {
this.value = value;
}
/**
* @return true if internal value is not null
*/
public boolean isPresent() {
return value.get() != null;
}
/**
* Return current value: null or not
*
* @return internal value
*/
@Override
@javax.annotation.Nullable
public T get() {
return value == null ? null : value.get();
}
/**
* Replaces value by given one
*
* @param value a new value, may be null
* @return this
*/
public Nullable set(@javax.annotation.Nullable T value) {
this.value = () -> value;
return this;
}
/**
* Gives another value if current is null
*
* @param anotherValue the other value to return in case of current one is null
* @return {@link #get} or anotherValue if non present value
*/
@javax.annotation.Nullable
public T getOr(@javax.annotation.Nullable T anotherValue) {
return getOr(() -> anotherValue);
}
/**
* Gives another value if current is null
*
* @param anotherValue the supplier called in case of current null value
* @return {@link #get} or anotherValue.get() if non present value
*/
public T getOr(Supplier anotherValue) {
return !isPresent() ? anotherValue.get() : get();
}
/**
* Gives the current value or throws the given {@link Throwable} in case of missing value.
* Shortcut for {@link #elseThrow(Throwable)}.get()
*
* @param throwable the provider of {@link Throwable} to be thrown
* @param the {@link Throwable} type
* @return this (if value is present)
* @throws E the given throwable
*/
public T getOrThrow(Supplier throwable) throws E {
return elseThrow(throwable).get();
}
/**
* Changes internal value by another if current is null
*
* @param otherValue the replacing value
* @return this
*/
public Nullable elseSet(@javax.annotation.Nullable T otherValue) {
return elseSet(() -> otherValue);
}
/**
* Changes internal value by another if current is null
*
* @param otherValue a replacing {@link java.util.function.Supplier}
* @return this
*/
public Nullable elseSet(Supplier otherValue) {
if (!isPresent()) {
value = otherValue;
}
return this;
}
/**
* Will throw the given {@link Throwable} if current value is null
*
* @param throwable the {@link Throwable} to be thrown
* @param the {@link Throwable} type
* @return this (if value is present)
* @throws E the given throwable
*/
public Nullable elseThrow(E throwable) throws E {
return this.elseThrow(() -> throwable);
}
/**
* Will throw supplied {@link Throwable} if current value is null.
* Tips to avoid invocation conflict with {@link #elseThrow(Throwable)} : one should specify generics before method call sush as:
*
* Nullable.empty().<IOException>elseThrow(IOException::new);
*
*
* @param throwableSupplier the {@link Supplier} that will give the {@link Throwable} to be thrown
* @param the {@link Throwable} type
* @return this (if value is present)
* @throws E the given throwable
*/
public Nullable elseThrow(Supplier throwableSupplier) throws E {
// NB we don't use ifPresent because it's quite too much for this case (and requires to return null in given function, ugly)
if (!isPresent()) {
throw throwableSupplier.get();
} else {
return this;
}
}
/**
* Applies a function on current value if present
*
* @param mapper a function to be applied on value
* @param the function returned type
* @return a {@link Nullable} of the function result or of null if value wasn't present
*/
public Nullable map(Function super T, ? extends O> mapper) {
return nullable(ifPresent(mapper::apply));
}
/**
* Same as {@link #map(Function)} with a throw clause
*
* @param function a function to be applied on value
* @param the function returned type
* @param le type d'exception
* @return a {@link Nullable} of the function result or of null if value wasn't present
* @throws E type of exception thrown by the function
*/
public Nullable mapThrower(ThrowingFunction super T, ? extends O, E> function) throws E {
return nullable(ifPresent(function));
}
/**
* Consumes the value if present
*
* @param consumer a value consumer
* @return this
*/
public Nullable invoke(Consumer consumer) {
return ifPresent(consumer::accept);
}
/**
* Same as {@link #invoke(Consumer)} with a throw clause
*
* @param consumer a function to be applied on value
* @param le type d'exception
* @return this
* @throws E type of exception thrown by the function
*/
public Nullable invokeThrower(ThrowingConsumer super T, E> consumer) throws E {
return ifPresent(consumer);
}
/**
* Tests the value if present, if not returns an empty {@link Nullable}
*
* @param predicate a predicate on value type
* @return this if present and predicate matches, else a {@link Nullable} of null
*/
public Nullable filter(Predicate super T> predicate) {
if (isPresent() && predicate.test(get())) {
return this;
} else {
return empty();
}
}
/**
* Tests the value if present and returns the result
*
* @param predicate a predicate on value type, invoked only if value is present
* @return {@link Predicate} result wrapped into a {@link Nullable}
*/
public Nullable test(Predicate super T> predicate) {
return Nullable.nullable(ifPresent(Functions.toFunction(predicate)::apply));
}
/** Applies the function if value is present */
private O ifPresent(ThrowingFunction super T, ? extends O, E> mapper) throws E {
return isPresent() ? mapper.apply(get()) : null;
}
/** Applies the consumer if value is present */
private Nullable ifPresent(ThrowingConsumer super T, E> consumer) throws E {
if (isPresent()) {
consumer.accept(get());
}
return this;
}
}