Nullable.java
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
*/
public class Nullable<T> implements Supplier<T> {
/**
* Shortcut for new Nullable(value)
*
* @param value a nullable value
* @param <T> value type
* @return new Nullable(value)
*/
public static <T> Nullable<T> nullable(@javax.annotation.Nullable T value) {
return new Nullable<>(value);
}
/**
* Shortcut for new Nullable(value)
*
* @param value a nullable value
* @param <T> value type
* @return new Nullable(value)
*/
public static <T> Nullable<T> nullable(Supplier<T> 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 <I> value type
* @return new Nullable(input).map(f)
*/
public static <I, O> Nullable<O> nullable(@javax.annotation.Nullable I input, Function<I, O> 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 <I> value type
* @return new Nullable(input).map(f)
*/
public static <I, O> Nullable<O> nullable(Supplier<I> input, Function<I, O> 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 <I> value type
* @return new Nullable(input).map(f1).map(f2)
*/
public static <I, O, A> Nullable<O> nullable(@javax.annotation.Nullable I input, Function<I, A> f1, Function<A, O> 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 <I> value type
* @return new Nullable(input).map(f1).map(f2)
*/
public static <I, O, A> Nullable<O> nullable(Supplier<I> input, Function<I, A> f1, Function<A, O> f2) {
return nullable(input).map(f1).map(f2);
}
/**
* @param <T> type of the {@link Nullable}
* @return a {@link Nullable} of null
*/
public static <T> Nullable<T> empty() {
return Nullable.nullable(() -> null);
}
/** The payload, may be null itself or owned value may be null */
private Supplier<T> 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<T> value) {
this.value = value;
}
/**
* @return true if internal value is not null
*/
public boolean isPresent() {
return value.get() != null;
}
/**
* @return true if internal value is null
*/
public boolean isAbsent() {
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<T> set(@javax.annotation.Nullable T value) {
this.value = () -> value;
return this;
}
public Nullable<T> setIfAbsent(@javax.annotation.Nullable T value) {
if (isAbsent()) {
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<T> 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 <E> the {@link Throwable} type
* @return this (if value is present)
* @throws E the given throwable
*/
public <E extends Throwable> T getOrThrow(Supplier<E> 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<T> 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<T> elseSet(Supplier<T> 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 <E> the {@link Throwable} type
* @return this (if value is present)
* @throws E the given throwable
*/
public <E extends Throwable> Nullable<T> elseThrow(E throwable) throws E {
return this.<E>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:
* <code>
* Nullable.empty().<IOException>elseThrow(IOException::new);
* </code>
*
* @param throwableSupplier the {@link Supplier} that will give the {@link Throwable} to be thrown
* @param <E> the {@link Throwable} type
* @return this (if value is present)
* @throws E the given throwable
*/
public <E extends Throwable> Nullable<T> elseThrow(Supplier<E> 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 and changes the content of this instance by the new value.
*
* @param mapper a function to be applied on value
* @param <O> the function returned type
* @return this instance cast with mapper value type
*/
public <O> Nullable<O> map(Function<? super T, ? extends O> mapper) {
set((T) ifPresent(mapper::apply));
return (Nullable<O>) this;
}
/**
* Same as {@link #map(Function)} with a throw clause
*
* @param function a function to be applied on value
* @param <O> the function returned type
* @param <E> 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 <O, E extends Exception> Nullable<O> 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<T> invoke(Consumer<T> consumer) {
return ifPresent(consumer::accept);
}
/**
* Same as {@link #invoke(Consumer)} with a throw clause
*
* @param consumer a function to be applied on value
* @param <E> le type d'exception
* @return this
* @throws E type of exception thrown by the function
*/
public <E extends Exception> Nullable<T> 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<T> 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<Boolean> test(Predicate<? super T> predicate) {
return Nullable.nullable(ifPresent(Functions.toFunction(predicate)::apply));
}
/** Applies the function if value is present */
private <O, E extends Exception> 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 <E extends Exception> Nullable<T> ifPresent(ThrowingConsumer<? super T, E> consumer) throws E {
if (isPresent()) {
consumer.accept(get());
}
return this;
}
}