Accessors.java
package org.codefilarete.reflection;
import javax.annotation.Nullable;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.List;
import org.danekja.java.util.function.serializable.SerializableBiConsumer;
import org.danekja.java.util.function.serializable.SerializableFunction;
import org.codefilarete.tool.Reflections;
import org.codefilarete.tool.Reflections.MemberNotFoundException;
import org.codefilarete.tool.Strings;
import org.codefilarete.tool.collection.Iterables;
import org.codefilarete.tool.reflect.MethodDispatcher;
import static org.codefilarete.tool.Reflections.propertyName;
/**
* @author Guillaume Mary
*/
public final class Accessors {
/**
* Helper to get method input type. Set as static to benefit from its cache.
*/
private static final MethodReferenceCapturer methodCapturer = new MethodReferenceCapturer();
@Nullable
public static <C, T> AccessorByMethod<C, T> accessorByMethod(Field field) {
Method getter = findGetter(field.getDeclaringClass(), field.getName(), field.getType());
return getter == null ? null : new AccessorByMethod<>(getter);
}
/**
* Shortcut to create a {@link AccessorByMethod} from a class and a property name.
* Java bean naming convention will be applied to find out property getter name : prefixed with "get" or "is".
* Returns null is getter method is not found.
*
* @param clazz any class
* @param propertyName a property name owned by the class or one of its parent
* @param <C> owning class type
* @param <T> getter return type, which is property type too
* @return null if getter method is not found
*/
@Nullable
public static <C, T> AccessorByMethod<C, T> accessorByMethod(Class clazz, String propertyName) {
Field field = Reflections.findField(clazz, propertyName);
return accessorByMethod(field);
}
@Nullable
public static <C, T> AccessorByMethod<C, T> accessorByMethod(Class clazz, String propertyName, Class<T> inputType) {
Method getter = findGetter(clazz, propertyName, inputType);
return getter == null ? null : new AccessorByMethod<>(getter);
}
@Nullable
public static <C, T> AccessorByMethod<C, T> accessorByMethod(Class clazz, String propertyName, Class<T> inputType, Mutator<C, T> mutator) {
Method getter = findGetter(clazz, propertyName, inputType);
if (getter == null) {
return null;
} else {
Reflections.ensureAccessible(getter);
return new AccessorByMethod<>(getter, new Object[getter.getParameterTypes().length], mutator);
}
}
@Nullable
private static <T> Method findGetter(Class clazz, String propertyName, Class<T> inputType) {
String capitalizedProperty = Strings.capitalize(propertyName);
String methodPrefix;
if (boolean.class.isAssignableFrom(inputType) || Boolean.class.isAssignableFrom(inputType)) {
methodPrefix = "is";
} else {
methodPrefix = "get";
}
return Reflections.findMethod(clazz, methodPrefix + capitalizedProperty);
}
public static <C, T> AccessorByMethodReference<C, T> accessorByMethodReference(SerializableFunction<C, T> getter) {
return new AccessorByMethodReference<>(getter);
}
public static <C, T> ReversibleAccessor<C, T> accessorByMethodReference(SerializableFunction<C, T> getter, SerializableBiConsumer<C, T> setter) {
return PropertyAccessor.fromMethodReference(getter, setter);
}
public static <C, T> AccessorByField<C, T> accessorByField(Field field) {
return new AccessorByField<>(field);
}
public static <C, T> AccessorByField<C, T> accessorByField(Class<C> clazz, String propertyName) {
Field propertyField = Reflections.getField(clazz, propertyName);
return accessorByField(propertyField);
}
public static <C, T> MutatorByMethod<C, T> mutatorByMethod(Field field) {
// we do our best : no argument is given because we couldn't determine it
return new MutatorByMethod<>(Reflections.getMethod(field.getDeclaringClass(), "set" + Strings.capitalize(field.getName()), field.getType()));
}
/**
* Shortcut to create a {@link MutatorByMethod} from a class and a property name.
* Java bean naming convention will be applied to find out property setter name : prefixed with "set".
* Returns null is setter method is not found.
*
* @param clazz any class
* @param propertyName a property name owned by the class or one of its parent
* @param <C> owning class type
* @param <T> setter input type, which is property type too
* @return null if setter method is not found
*/
@Nullable
public static <C, T> MutatorByMethod<C, T> mutatorByMethod(Class<C> clazz, String propertyName) {
Field field = Reflections.getField(clazz, propertyName);
try {
return mutatorByMethod(field);
} catch (MemberNotFoundException e) {
return null;
}
}
/**
* Shortcut to create a {@link MutatorByMethod} from a class, a property name, and its type.
* Java bean naming convention will be applied to find out property setter name : prefixed with "set".
* Returns null is setter method is not found.
*
* @param clazz any class
* @param propertyName a property name owned by the class or one of its parent
* @param inputType property type
* @param <C> owning class type
* @param <T> setter input type, which is property type too
* @return null if setter method is not found
*/
@Nullable
public static <C, T> MutatorByMethod<C, T> mutatorByMethod(Class<C> clazz, String propertyName, Class<T> inputType) {
String capitalizedProperty = Strings.capitalize(propertyName);
Method setter = Reflections.findMethod(clazz, "set" + capitalizedProperty, inputType);
return setter == null ? null : new MutatorByMethod<>(setter);
}
@Nullable
public static <C, T> MutatorByMethod<C, T> mutatorByMethod(Class<C> clazz, String propertyName, Class<T> inputType, Accessor<C, T> accessor) {
String capitalizedProperty = Strings.capitalize(propertyName);
Method setter = Reflections.findMethod(clazz, "set" + capitalizedProperty, inputType);
if (setter == null) {
return null;
} else {
Reflections.ensureAccessible(setter);
return new MutatorByMethod<>(setter, accessor);
}
}
public static <C, T> MutatorByMethodReference<C, T> mutatorByMethodReference(SerializableBiConsumer<C, T> setter) {
return new MutatorByMethodReference<>(setter);
}
public static <C, T> MutatorByField<C, T> mutatorByField(Field field) {
return new MutatorByField<>(field);
}
public static <C, T> MutatorByField<C, T> mutatorByField(Class clazz, String propertyName) {
Field propertyField = Reflections.getField(clazz, propertyName);
return mutatorByField(propertyField);
}
public static <C, T> MutatorByField<C, T> mutatorByField(Class clazz, String propertyName, Accessor<C, T> accessor) {
Field propertyField = Reflections.getField(clazz, propertyName);
Reflections.ensureAccessible(propertyField);
return new MutatorByField<>(propertyField, accessor);
}
public static Field wrappedField(AccessorByMethod accessorByMethod) {
Method getter = accessorByMethod.getGetter();
return Reflections.wrappedField(getter);
}
public static <C, T> PropertyAccessor<C, T> propertyAccessor(Field field) {
return new PropertyAccessor<>(new AccessorByField<>(field), new MutatorByField<>(field));
}
public static <C, T> PropertyAccessor<C, T> propertyAccessor(Class<C> clazz, String propertyName) {
AccessorByMember<C, T, ?> propertyGetter = accessor(clazz, propertyName);
Mutator<C, T> propertySetter = mutator(clazz, propertyName, propertyGetter.getPropertyType());
return new PropertyAccessor<>(propertyGetter, propertySetter);
}
/**
* Creates an {@link Accessor} for the given property of the given class. Does it with conventional getter or a direct access to the field.
*
* @param clazz the class owning the property
* @param propertyName the name of the property
* @param <C> the type of the class owning the property
* @param <T> the type of the field (returned by the getter)
* @return a new {@link Accessor}
*/
public static <C, T, M extends Member> AccessorByMember<C, T, M> accessor(Class<C> clazz, String propertyName) {
AccessorByMember<C, T, ?> propertyGetter = accessorByMethod(clazz, propertyName);
if (propertyGetter == null) {
// NB: we use getField instead of findField because the latest returns null if field wasn't found
// so AccessorByField will throw a NPE later
propertyGetter = new AccessorByField<>(Reflections.getField(clazz, propertyName));
}
return (AccessorByMember<C, T, M>) propertyGetter;
}
/**
* Creates an {@link Accessor} for the given property of the given class. Does it with conventional getter or a direct access to the field.
*
* @param clazz the class owning the property
* @param propertyName the name of the property
* @param <C> the type of the class owning the property
* @param <T> the type of the field (returned by the getter)
* @return a new {@link Accessor}
*/
public static <C, T, M extends Member> AccessorByMember<C, T, M> accessor(Class<C> clazz, String propertyName, Class<T> propertyType) {
AccessorByMember<C, T, ?> propertyGetter = accessorByMethod(clazz, propertyName, propertyType);
if (propertyGetter == null) {
// NB: we use getField instead of findField because the latest returns null if field wasn't found
// so AccessorByField will throw a NPE later
Field foundField = Reflections.getField(clazz, propertyName);
if (!Reflections.isAssignableFrom(propertyType, foundField.getType())) {
throw new MemberNotFoundException(
Reflections.toString(clazz) + "." + propertyName,
"Member type doesn't match expected one for field " + Reflections.toString(foundField)
+ ": expected " + Reflections.toString(propertyType) + " but is " + Reflections.toString(foundField.getType()));
}
propertyGetter = new AccessorByField<>(foundField);
}
return (AccessorByMember<C, T, M>) propertyGetter;
}
/**
* Creates an {@link Mutator} for the given property of the given class. Does it with conventional setter or a direct access to the field.
*
* @param clazz the class owning the property
* @param propertyName the name of the property
* @param <C> the type of the class owning the property
* @param <T> the type of the field (first parameter of the setter)
* @return a new {@link Mutator}
*/
public static <C, T, M extends Member> MutatorByMember<C, T, M> mutator(Class<C> clazz, String propertyName) {
Field field = Reflections.getField(clazz, propertyName);
try {
return (MutatorByMember<C, T, M>) mutatorByMethod(field);
} catch (MemberNotFoundException e) {
return (MutatorByMember<C, T, M>) new MutatorByField<>(field);
}
}
/**
* Creates an {@link Mutator} for the given property of the given class. Does it with conventional setter or a direct access to the field.
*
* @param clazz the class owning the property
* @param propertyName the name of the property
* @param <C> the type of the class owning the property
* @param <T> the type of the field (first parameter of the setter)
* @return a new {@link Mutator}
*/
public static <C, T, M extends Member> MutatorByMember<C, T, M> mutator(Class<C> clazz, String propertyName, Class<T> propertyType) {
MutatorByMember<C, T, ?> propertySetter = mutatorByMethod(clazz, propertyName, propertyType);
if (propertySetter == null) {
// NB: we use getField instead of findField because the latest returns null if field wasn't found
// so AccessorByField will throw a NPE later
Field foundField = Reflections.getField(clazz, propertyName);
if (!Reflections.isAssignableFrom(propertyType, foundField.getType())) {
throw new MemberNotFoundException(
Reflections.toString(clazz) + "." + propertyName,
"Member type doesn't match expected one for field " + Reflections.toString(foundField)
+ ": expected " + Reflections.toString(propertyType) + " but is " + Reflections.toString(foundField.getType()));
}
propertySetter = new MutatorByField<>(foundField);
}
return (MutatorByMember<C, T, M>) propertySetter;
}
public static <C, E> PropertyAccessor<C, E> accessor(SerializableFunction<C, E> getter) {
AccessorByMethodReference<C, E> methodReference = accessorByMethodReference(getter);
return new PropertyAccessor<>(
methodReference,
mutator(methodReference.getDeclaringClass(), propertyName(methodReference.getMethodName()), methodReference.getPropertyType())
);
}
public static <C, E> PropertyAccessor<C, E> mutator(SerializableBiConsumer<C, E> setter) {
MutatorByMethodReference<C, E> methodReference = mutatorByMethodReference(setter);
return new PropertyAccessor<>(
accessor(methodReference.getDeclaringClass(), propertyName(methodReference.getMethodName()), methodReference.getPropertyType()),
methodReference
);
}
/**
* Gives an adequate {@link PropertyAccessor} according to the given {@link Member}
* @param member a member to be transformed as a {@link PropertyAccessor}
* @param <C> the declaring class of the {@link Member}
* @param <T> the type of the {@link Member}
* @return a new {@link PropertyAccessor} with accessor and mutator alloqing to access to the member
*/
public static <C, T> PropertyAccessor<C, T> accessor(Member member) {
if (member instanceof Field) {
return new PropertyAccessor<>(new AccessorByField<>((Field) member));
} else if (member instanceof Method) {
// Determining if the method is an accessor or a mutator to give the good arguments to the final PropertyAccessor constructor
Method method = (Method) member;
AbstractReflector<Object> reflector = Reflections.onJavaBeanPropertyWrapperName(method,
AccessorByMethod::new,
MutatorByMethod::new,
AccessorByMethod::new);
if (reflector instanceof ReversibleAccessor) {
return new PropertyAccessor<>((ReversibleAccessor<C, T>) reflector);
} else if (reflector instanceof ReversibleMutator) {
return new PropertyAccessor<>((ReversibleMutator<C, T>) reflector);
} else {
// unreachable because preceding ifs check all conditions
throw new IllegalArgumentException("Member cannot be determined as a getter or a setter : " + member);
}
} else {
throw new IllegalArgumentException("Member cannot be used as an accessor : " + member);
}
}
/**
* Gives input type of a mutator. Implementation is based on well-known mutator classes and is not expected to be generic
*
* @param mutator any {@link Mutator}
* @return input type of a mutator : input value of a setter and type of a field
*/
public static Class giveInputType(Mutator mutator) {
if (mutator instanceof MutatorByMember) {
Member member = ((MutatorByMember) mutator).getSetter();
if (member instanceof Method) {
return ((Method) member).getParameterTypes()[0];
} else if (member instanceof Field) {
return ((Field) member).getType();
} else {
// for future new MutatorByMember that are neither a Field nor a Method ... should not happen
throw new UnsupportedOperationException("Mutator type is not implemented : " + mutator);
}
} else if (mutator instanceof MutatorByMethodReference) {
return methodCapturer.findMethod(((MutatorByMethodReference) mutator).getMethodReference()).getParameterTypes()[0];
} else if (mutator instanceof PropertyAccessor) {
return giveInputType(((PropertyAccessor) mutator).getMutator());
} else if (mutator instanceof AccessorChainMutator) {
return giveInputType(((AccessorChainMutator) mutator).getMutator());
} else {
// for future new MutatorByMember that are neither a Field nor a Method ... should not happen
throw new UnsupportedOperationException("Mutator type is not implemented : " + mutator);
}
}
/**
* Gives input type of a mutator. Implementation is based on well-known mutator classes and is not expected to be generic
*
* @param accessor any {@link Accessor}
* @return input type of a mutator : input value of a setter and type of a field
*/
public static Class giveReturnType(Accessor accessor) {
if (accessor instanceof AccessorByMember) {
Member member = ((AccessorByMember) accessor).getGetter();
if (member instanceof Method) {
return ((Method) member).getReturnType();
} else if (member instanceof Field) {
return ((Field) member).getType();
} else {
// for future new MutatorByMember that are neither a Field nor a Method ... should not happen
throw new UnsupportedOperationException("Mutator type is not implemented : " + accessor);
}
} else if (accessor instanceof AccessorByMethodReference) {
return methodCapturer.findMethod(((AccessorByMethodReference) accessor).getMethodReference()).getReturnType();
} else if (accessor instanceof PropertyAccessor) {
return giveReturnType(((PropertyAccessor) accessor).getAccessor());
} else if (accessor instanceof AccessorChain) {
return giveReturnType(Iterables.last((List<Accessor>) ((AccessorChain) accessor).getAccessors()));
} else {
// for future new MutatorByMember that are neither a Field nor a Method ... should not happen
throw new UnsupportedOperationException("Accessor type is not implemented : " + accessor);
}
}
private Accessors() {
// utility class
}
}