All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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 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 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 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 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 predicate) {
		return Nullable.nullable(ifPresent(Functions.toFunction(predicate)::apply));
	}
	
	/** Applies the function if value is present */
	private  O ifPresent(ThrowingFunction mapper) throws E {
		return isPresent() ? mapper.apply(get()) : null;
	}
	
	/** Applies the consumer if value is present */
	private  Nullable ifPresent(ThrowingConsumer consumer) throws E {
		if (isPresent()) {
			consumer.accept(get());
		}
		return this;
	}
}