AccessorChain.java
package org.codefilarete.reflection;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import org.codefilarete.tool.Reflections;
import org.codefilarete.tool.collection.Arrays;
import org.codefilarete.tool.collection.Iterables;
import org.danekja.java.util.function.serializable.SerializableFunction;
/**
* Chain of {@link Accessor}s that behaves as a {@link Accessor}
* Behavior of null-encountered-values during {@link #get(Object)} is controlled through a {@link NullValueHandler}, by default {@link NullPointerException}
* will be thrown, see {@link #setNullValueHandler(NullValueHandler)} to change it. This class proposes some other default behavior such as
* {@link #RETURN_NULL} or {@link #INITIALIZE_VALUE}
*
* @author Guillaume Mary
*/
public class AccessorChain<C, T> extends AbstractAccessor<C, T> implements ReversibleAccessor<C, T> {
public static <IN, OUT> AccessorChain<IN, OUT> fromMethodReference(SerializableFunction<IN, OUT> getter) {
return new AccessorChain<>(Accessors.accessor(getter));
}
public static <IN, A, OUT> AccessorChain<IN, OUT> fromMethodReferences(SerializableFunction<IN, A> function1, SerializableFunction<A, OUT> function2) {
// Note that we use Accessors.accessor(..) for the second argument because it builds a ReversibleAccessor,
// to fulfill AccessorChain ReversibleAccessor contract, whereas direct AccessorByMethodReference doesn't
return new AccessorChain<>(new AccessorByMethodReference<>(function1), Accessors.accessor(function2));
}
public static <IN, A, B, OUT> AccessorChain<IN, OUT> fromMethodReferences(
SerializableFunction<IN, A> function1,
SerializableFunction<A, B> function2,
SerializableFunction<B, OUT> function3
) {
// Note that we use Accessors.accessor(..) for the third argument because it builds a ReversibleAccessor,
// to fulfill AccessorChain ReversibleAccessor contract, whereas direct AccessorByMethodReference doesn't
return new AccessorChain<>(new AccessorByMethodReference<>(function1), new AccessorByMethodReference<>(function2), Accessors.accessor(function3));
}
public static <IN, A, B, C, OUT> AccessorChain<IN, OUT> fromMethodReferences(SerializableFunction<IN, A> function1,
SerializableFunction<A, B> function2,
SerializableFunction<B, C> function3,
SerializableFunction<C, OUT> function4
) {
// Note that we use Accessors.accessor(..) for the fourth argument because it builds a ReversibleAccessor,
// to fulfill AccessorChain ReversibleAccessor contract, whereas direct AccessorByMethodReference doesn't
return new AccessorChain<>(new AccessorByMethodReference<>(function1), new AccessorByMethodReference<>(function2), new AccessorByMethodReference<>(function3), Accessors.accessor(function4));
}
/**
* Creates a chain that:
* - returns null when any getters return null
* - initializes values (instantiate bean) on path when its mutator is used
* (voluntary dissimetric behavior)
*
* @param getter1 getter of the first property
* @param getter2 getter of the second property
* @see #RETURN_NULL
* @see ValueInitializerOnNullValue#newInstance(Accessor, Class)
* @see #fromAccessorsWithNullSafe(List, BiFunction)
*/
public static <IN, A, OUT> AccessorChain<IN, OUT> fromMethodReferencesWithNullSafe(SerializableFunction<IN, A> getter1, SerializableFunction<A, OUT> getter2) {
// Note that we use Accessors.accessor(..) for the second argument because it builds a ReversibleAccessor,
// to fulfill AccessorChain ReversibleAccessor contract, whereas direct AccessorByMethodReference doesn't
return new AccessorChain<IN, OUT>(Accessors.accessor(getter1), Accessors.accessor(getter2)) {
private final AccessorChainMutator<IN, Object, OUT> mutator = (AccessorChainMutator<IN, Object, OUT>) super.toMutator()
.setNullValueHandler(INITIALIZE_VALUE);
@Override
public AccessorChainMutator toMutator() {
return mutator;
}
}.setNullValueHandler(AccessorChain.RETURN_NULL);
}
/**
* Creates a chain that initializes values (instantiate bean) on its path if accessor returns null.
*
* @param getter getter of the first property
* @param setter setter of the second property
* @see #INITIALIZE_VALUE
* @see #fromAccessorsWithNullSafe(List, BiFunction)
*/
public static <IN, A, OUT> AccessorChainMutator<IN, A, OUT> fromMethodReferencesWithNullSafe(SerializableFunction<IN, A> getter, BiConsumer<A, OUT> setter) {
// Note that we use Accessors.accessor because it builds a ReversibleAccessor (required further to eventually set value) whereas AccessorByMethodReference doesn't
AccessorChainMutator<IN, A, OUT> result = new AccessorChainMutator<>(Arrays.asList(Accessors.accessor(getter)), setter::accept);
result.setNullValueHandler(AccessorChain.INITIALIZE_VALUE);
return result;
}
/**
* Creates a chain that:
* - returns null when any accessor on the path returns null
* - initializes values (instantiate bean) on the path when its mutator is used
* (voluntary dissimetric behavior)
*
* @param accessors list of {@link Accessor} to be used by chain
* @see #RETURN_NULL
* @see ValueInitializerOnNullValue#newInstance(Accessor, Class)
* @see #fromAccessorsWithNullSafe(List, BiFunction)
*/
public static <IN, OUT> AccessorChain<IN, OUT> fromAccessorsWithNullSafe(List<? extends Accessor<?, ?>> accessors) {
return fromAccessorsWithNullSafe(accessors, null);
}
/**
* Creates a chain that:
* - returns null when any accessor on path returns null
* - initializes values (instantiate bean) on path when its mutator is used
* (voluntary dissimetric behavior)
*
* @param accessors list of {@link Accessor} to be used by chain
* @param valueTypeDeterminer must be given if a bean type is badly determined by default mechanism
* (returning Object on generic for instance, or wrong Collection concrete type), null accepted (means default mechanism)
* @see #RETURN_NULL
* @see ValueInitializerOnNullValue#newInstance(Accessor, Class)
*/
public static <IN, OUT, T> AccessorChain<IN, OUT> fromAccessorsWithNullSafe(List<? extends Accessor<?, ?>> accessors, @Nullable BiFunction<Accessor<?, T>, Class<T>, T> valueTypeDeterminer) {
return new AccessorChain<IN, OUT>(accessors) {
private final AccessorChainMutator<IN, Object, OUT> mutator = (AccessorChainMutator<IN, Object, OUT>) super.toMutator()
.setNullValueHandler(new ValueInitializerOnNullValue(valueTypeDeterminer));
@Override
public AccessorChainMutator toMutator() {
return mutator;
}
}.setNullValueHandler(AccessorChain.RETURN_NULL);
}
/**
* Will throw a {@link NullPointerException} if a link in an accessor chain returns null.
* Default behavior
*/
public static final NullValueHandler THROW_NULLPOINTEREXCEPTION = new NullPointerExceptionThrowerOnNullValue();
/** Will return null if a link in an accessor chain returns null */
public static final NullValueHandler RETURN_NULL = new NullReturnerOnNullValue();
/** Will instantiate needed value (and set it) if a link in an accessor chain returns null */
public static final NullValueHandler INITIALIZE_VALUE = new ValueInitializerOnNullValue();
private final List<Accessor<?, ?>> accessors;
private NullValueHandler nullValueHandler = THROW_NULLPOINTEREXCEPTION;
public AccessorChain() {
this(new ArrayList<>(5));
}
public AccessorChain(Accessor<?, ?>... accessors) {
this(Arrays.asList(accessors));
}
public AccessorChain(List<? extends Accessor<?, ?>> accessors) {
this.accessors = (List<Accessor<?, ?>>) accessors;
}
public List<Accessor<?, ?>> getAccessors() {
return accessors;
}
public void add(Accessor<?, ?> accessor) {
accessors.add(accessor);
}
public void add(Accessor<?, ?>... accessors) {
add(Arrays.asList(accessors));
}
public void add(Iterable<? extends Accessor<?, ?>> accessors) {
if (accessors instanceof Collection) {
this.accessors.addAll((Collection<? extends Accessor<?, ?>>) accessors);
} else {
accessors.forEach(this::add);
}
}
public AccessorChain<C, T> setNullValueHandler(NullValueHandler nullValueHandler) {
this.nullValueHandler = nullValueHandler;
return this;
}
@Override
public T doGet(C c) {
Object target = c;
Object previousTarget;
for (Accessor accessor : accessors) {
previousTarget = target;
target = accessor.get(target);
if (target == null) {
Object handlerResult = onNullValue(previousTarget, accessor);
if (handlerResult == null) {
// we must go out from the loop to avoid a NullPointerException, moreover it has no purpose to continue iteration
return null;
} else {
target = handlerResult;
}
}
}
return (T) target;
}
/**
* Method called when a null value is returned by an accessor in the chain
* @param targetBean bean on which accessor was invoked
* @param accessor accessor that returned null when invoked on targetBean
* @return the value that should replace null value, can be null too
*/
@Nullable
protected Object onNullValue(Object targetBean, Accessor accessor) {
return this.nullValueHandler.consume(targetBean, accessor);
}
/**
* Only supported when last accessor is reversible (aka implements {@link ReversibleAccessor}.
*
* @return a new chain which path is the same as this
* @throws UnsupportedOperationException if last accessor is not reversible
*/
@Override
public AccessorChainMutator<C, Object, T> toMutator() {
Accessor lastAccessor = Iterables.last(getAccessors());
if (lastAccessor instanceof ReversibleAccessor) {
Mutator<Object, T> lastMutator = ((ReversibleAccessor<Object, T>) lastAccessor).toMutator();
AccessorChainMutator<C, Object, T> result = new AccessorChainMutator<>(Iterables.cutTail(getAccessors()), lastMutator);
result.setNullValueHandler(this.nullValueHandler);
return result;
} else {
throw new UnsupportedOperationException("Last accessor cannot be reverted because it's not " + Reflections.toString(ReversibleAccessor.class)
+ ": " + lastAccessor);
}
}
@Override
public boolean equals(Object other) {
return this == other || (other instanceof AccessorChain && accessors.equals(((AccessorChain) other).accessors));
}
@Override
public int hashCode() {
return accessors.hashCode();
}
@Override
protected String getGetterDescription() {
return accessors.toString();
}
/**
* Contract for handling null objects during accessor chaining
*/
public interface NullValueHandler {
Object consume(Object srcBean, Accessor accessor);
}
/**
* Class that will throw a {@link NullPointerException} when a null value is encountered
*/
private static class NullPointerExceptionThrowerOnNullValue implements NullValueHandler {
@Override
public Object consume(Object srcBean, Accessor accessor) {
String accessorDescription = accessor.toString();
String exceptionMessage;
if (accessor instanceof AccessorByField) {
exceptionMessage = srcBean + " has null value on field " + ((AccessorByField) accessor).getGetter().getName();
} else {
exceptionMessage = "Call of " + accessorDescription + " on " + srcBean + " returned null";
}
throw new NullPointerException(exceptionMessage);
}
}
/**
* Simple class that will always return null for the whole chain when a null value is encountered
*/
private static class NullReturnerOnNullValue implements NullValueHandler {
@Override
public Object consume(Object srcBean, Accessor accessor) {
return null;
}
}
/**
* Class that will initialize value by instantiating its class and set it onto the property.
* instantiated types can be controlled through {@link #newInstance(Accessor, Class)}.
*/
public static class ValueInitializerOnNullValue implements NullValueHandler {
private final BiFunction<Accessor<?, ?>, Class<?>, ?> valueTypeDeterminer;
public ValueInitializerOnNullValue() {
this(null);
}
public <T> ValueInitializerOnNullValue(@Nullable BiFunction<Accessor<?, T>, Class<T>, T> valueTypeDeterminer) {
this.valueTypeDeterminer = valueTypeDeterminer == null
? (accessor, clazz) -> this.newInstance((Accessor) accessor, clazz)
: (BiFunction) valueTypeDeterminer;
}
@Override
public Object consume(Object srcBean, Accessor accessor) {
if (accessor instanceof ReversibleAccessor) {
Mutator mutator = ((ReversibleAccessor) accessor).toMutator();
Class inputType = Accessors.giveInputType(mutator);
// NB: will throw an exception if type is not instantiable
Object value = valueTypeDeterminer.apply(accessor, inputType);
mutator.set(srcBean, value);
return value;
} else {
throw new UnsupportedOperationException(
"accessor cannot be reverted because it's not " + Reflections.toString(ReversibleAccessor.class) + ": " + accessor);
}
}
/**
* Expected to return an instance matching <code>valueType</code> class.
* @param accessor the current accessor that returned null, given for a fine-grained adjustment of returned type
* @param valueType expected compatible type, this of accessor
* @return a concrete and instantiable type compatible with accessor input type
*/
protected <T> T newInstance(Accessor<?, T> accessor, Class<T> valueType) {
return Reflections.newInstance(valueType);
}
}
}