/*
* Copyright 2015 McDowell
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package uk.kludje;
import java.util.Objects;
import java.util.function.Supplier;
/**
* <p>A functional interface with null-safe checks.</p>
* <p>For use with getter chains where one or more elements in the chain
* can be null.</p>
*
* <p>Example usage:</p>
* <pre>D d = Nullifier.eval(a, A::getB, B::getC, C::getD);</pre>
*
* <p>The above code is equivalent to:</p>
* <pre>
* D d = null;
* if (a != null) {
* B b = a.getB();
* if (b != null) {
* C c = b.getC();
* if (c != null) {
* d = c.getD();
* }
* }
* }
* </pre>
*
* <p>To test if <code>d</code> is null, use:</p>
* <pre>boolean dIsNull = Nullifier.isNull(a, A::getB, B::getC, C::getD);</pre>
*
* <p>Implement {@link #$apply(Object)}; invoke {@link #apply(Object)}.</p>
*
* @param <T> the input
* @param <R> the result
*/
@FunctionalInterface
public interface Nullifier<T, R> {
/**
* Implement this method with a lambda expression/method reference.
* <p>
* Consumers should invoke {@link #apply(Object)} and NOT call this method directly.
*
* @param t the argument; not null if invoked by default {@link #apply(Object)}
* @return the result
* @throws Exception on error
*/
R $apply(T t) throws Exception;
/**
* Creates a null-safe chain of calls spanning possibly null call sites.
* <p>
* The functions may not be null, but the inputs and outputs may be.
* <p>
* A number of overloaded methods are provided with varying argument counts.
*
* @param f0 the initial function; MUST NOT be null
* @param f1 a subsequent function; MUST NOT be null
* @param <A> the initial type
* @param <B> an intermediary type
* @param <Z> the resultant type
* @return a function checked, given A, returns Z, or null if any element in the chain is null
*/
static <A, B, Z> Nullifier<A, Z> span(Nullifier<? super A, ? extends B> f0,
Nullifier<? super B, ? extends Z> f1) {
Objects.requireNonNull(f0, "0");
Objects.requireNonNull(f1, "1");
return a -> eval(a, f0, f1);
}
static <A, B, C, Z> Nullifier<A, Z> span(Nullifier<? super A, ? extends B> f0,
Nullifier<? super B, ? extends C> f1,
Nullifier<? super C, ? extends Z> f2) {
Objects.requireNonNull(f0, "0");
Objects.requireNonNull(f1, "1");
Objects.requireNonNull(f2, "2");
return a -> eval(a, f0, f1, f2);
}
static <A, B, C, D, Z> Nullifier<A, Z> span(Nullifier<? super A, ? extends B> f0,
Nullifier<? super B, ? extends C> f1,
Nullifier<? super C, ? extends D> f2,
Nullifier<? super D, ? extends Z> f3) {
Objects.requireNonNull(f0, "0");
Objects.requireNonNull(f1, "1");
Objects.requireNonNull(f2, "2");
Objects.requireNonNull(f3, "3");
return a -> eval(a, f0, f1, f2, f3);
}
static <A, B, C, D, E, Z> Nullifier<A, Z> span(Nullifier<? super A, ? extends B> f0,
Nullifier<? super B, ? extends C> f1,
Nullifier<? super C, ? extends D> f2,
Nullifier<? super D, ? extends E> f3,
Nullifier<? super E, ? extends Z> f4) {
Objects.requireNonNull(f0, "0");
Objects.requireNonNull(f1, "1");
Objects.requireNonNull(f2, "2");
Objects.requireNonNull(f3, "3");
Objects.requireNonNull(f4, "4");
return a -> eval(a, f0, f1, f2, f3, f4);
}
static <A, Z> Z eval(A a,
Nullifier<? super A, ? extends Z> f0) {
return f0.apply(a);
}
/**
* Convenience method for evaluating a chain of {@link Nullifier} calls.
* <p>
* A number of overloaded methods are provided with varying argument counts.
*
* @param a the root object in the object graph (may be null)
* @param f0 is passed "a"; MUST NOT be null
* @param f1 is passed the result of "f0"; MUST NOT be null
* @param <A> the root type
* @param <B> an intermediary type
* @param <Z> the result type
* @return the result of the function chain or null
* @see #eval(Object, Nullifier)
* @see #eval(Object, Nullifier, Nullifier)
* @see #eval(Object, Nullifier, Nullifier, Nullifier)
* @see #eval(Object, Nullifier, Nullifier, Nullifier, Nullifier, Nullifier)
*/
static <A, B, Z> Z eval(A a,
Nullifier<? super A, ? extends B> f0,
Nullifier<? super B, ? extends Z> f1) {
B b = f0.apply(a);
return f1.apply(b);
}
static <A, B, C, Z> Z eval(A a,
Nullifier<? super A, ? extends B> f0,
Nullifier<? super B, ? extends C> f1,
Nullifier<? super C, ? extends Z> f2) {
B b = f0.apply(a);
C c = f1.apply(b);
return f2.apply(c);
}
static <A, B, C, D, Z> Z eval(A a,
Nullifier<? super A, ? extends B> f0,
Nullifier<? super B, ? extends C> f1,
Nullifier<? super C, ? extends D> f2,
Nullifier<? super D, ? extends Z> f3) {
B b = f0.apply(a);
C c = f1.apply(b);
D d = f2.apply(c);
return f3.apply(d);
}
static <A, B, C, D, E, Z> Z eval(A a,
Nullifier<? super A, ? extends B> f0,
Nullifier<? super B, ? extends C> f1,
Nullifier<? super C, ? extends D> f2,
Nullifier<? super D, ? extends E> f3,
Nullifier<? super E, ? extends Z> f4) {
B b = f0.apply(a);
C c = f1.apply(b);
D d = f2.apply(c);
E e = f3.apply(d);
return f4.apply(e);
}
static <A, Z> boolean isNull(A a,
Nullifier<? super A, ? extends Z> f0) {
return f0.apply(a) == null;
}
/**
* <p>Convenience method for evaluating a chain of {@link Nullifier} calls to see if any link is null.</p>
* <p>A number of overloaded methods are provided with varying argument counts.</p>
* <p>Equivalent to:</p>
* <pre>boolean isNull = (Nullifier.eval(a, f0, f1) == null);</pre>
*
* @param a the root object in the object graph (may be null)
* @param f0 is passed "a"; MUST NOT be null
* @param f1 is passed the result of "f0"; MUST NOT be null
* @param <A> the root type
* @param <B> an intermediary type
* @param <Z> the result type
* @return true if the result is null; false otherwise
* @see #isNull(Object, Nullifier)
* @see #isNull(Object, Nullifier, Nullifier)
* @see #isNull(Object, Nullifier, Nullifier, Nullifier)
* @see #isNull(Object, Nullifier, Nullifier, Nullifier, Nullifier, Nullifier)
*/
static <A, B, Z> boolean isNull(A a,
Nullifier<? super A, ? extends B> f0,
Nullifier<? super B, ? extends Z> f1) {
return eval(a, f0, f1) == null;
}
static <A, B, C, Z> boolean isNull(A a,
Nullifier<? super A, ? extends B> f0,
Nullifier<? super B, ? extends C> f1,
Nullifier<? super C, ? extends Z> f2) {
return eval(a, f0, f1, f2) == null;
}
static <A, B, C, D, Z> boolean isNull(A a,
Nullifier<? super A, ? extends B> f0,
Nullifier<? super B, ? extends C> f1,
Nullifier<? super C, ? extends D> f2,
Nullifier<? super D, ? extends Z> f3) {
return eval(a, f0, f1, f2, f3) == null;
}
static <A, B, C, D, E, Z> boolean isNull(A a,
Nullifier<? super A, ? extends B> f0,
Nullifier<? super B, ? extends C> f1,
Nullifier<? super C, ? extends D> f2,
Nullifier<? super D, ? extends E> f3,
Nullifier<? super E, ? extends Z> f4) {
return eval(a, f0, f1, f2, f3, f4) == null;
}
/**
* If the argument is null, returns null; else invokes {@link #$apply(Object)}.
* <p>
* This method rethrows any exception thrown by {@link #$apply(Object)} as an unchecked exception.
*
* @param t the input which may be null
* @return the result which may be null
* @see Exceptions#throwChecked(Throwable)
*/
default R apply(T t) {
try {
return (t == null) ? null : $apply(t);
} catch (Exception e) {
throw Exceptions.throwChecked(e);
}
}
/**
* Chains two instances together.
*
* @param after the nullifier to invoke after this; MUST NOT be null
* @param <V> the new result type
* @return a new nullifier
*/
default <V> Nullifier<T, V> andThenSpan(Nullifier<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
/**
* As {@link #apply(Object)} except checked another value can be returned if the result
* would otherwise be null.
*
* @param t the input which may be null
* @param defaultValue a default value
* @return the result which will not be null unless defaultValue is null
*/
default R applyOr(T t, R defaultValue) {
try {
if (t == null) {
return defaultValue;
}
R result = $apply(t);
return (result == null) ? defaultValue : $apply(t);
} catch (Exception e) {
throw Exceptions.throwChecked(e);
}
}
/**
* As {@link #apply(Object)} except checked another value can be returned if the result
* would otherwise be null.
*
* @param t the input which may be null
* @param factory result producer called for null cases
* @return the result which will not be null unless the factory also return null
*/
default R applyOrGet(T t, Supplier<R> factory) {
try {
if (t == null) {
return factory.get();
}
R result = $apply(t);
return (result == null) ? factory.get() : $apply(t);
} catch (Exception e) {
throw Exceptions.throwChecked(e);
}
}
}