AccessorByMethod.java

package org.codefilarete.reflection;

import javax.annotation.Nonnegative;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.function.Supplier;

import org.codefilarete.tool.Reflections;
import org.codefilarete.tool.Reflections.MemberNotFoundException;
import org.codefilarete.tool.function.ThreadSafeLazyInitializer;

import static org.codefilarete.tool.Reflections.propertyName;

/**
 * {@link Accessor} that wraps a {@link Method} to provide its value.
 * Has {@link ReversibleAccessor} behavior through a lazily initialized internal {@link Mutator}.
 *
 * @author Guillaume Mary
 */
public class AccessorByMethod<C, T> extends AbstractAccessor<C, T>
		implements AccessorByMember<C, T, Method>, ReversibleAccessor<C, T>, ValueAccessPointByMethod<C> {
	
	private final Method getter;
	
	private final Object[] methodParameters;
	
	/** For {@link ReversibleAccessor implementation} */
	private final Supplier<Mutator<C, T>> mutator;
	
	/**
	 * Create an instance based on the given method coordinates.
	 *
	 * @param declaringClass the class that owns the method
	 * @param getterName the method name to be found in given class
	 * @param argTypes optional argument types of the method
	 */
    public AccessorByMethod(Class<C> declaringClass, String getterName, Class ... argTypes) {
        this(Reflections.getMethod(declaringClass, getterName, argTypes));
    }
	
	/**
	 * Create an instance based on the given {@link Method}. The method is expected to take no argument (getter)
	 * @param getter a property accessor
	 */
	public AccessorByMethod(Method getter) {
		this(getter, new Object[getter.getParameterTypes().length]);
	}
	
	/**
	 * Create an instance based on the given {@link Method}. The method is expected to take some arguments, their values are also given now.
	 *
	 * @param getter a property accessor
	 * @param arguments the values to pass to the method when invoking {@link #get(Object)}
	 */
	public AccessorByMethod(Method getter, Object ... arguments) {
		Reflections.ensureAccessible(getter);
		this.getter = getter;
		this.methodParameters = arguments;
		this.mutator = new ThreadSafeLazyInitializer<Mutator<C, T>>() {
			@Override
			protected Mutator<C, T> createInstance() {
				return findCompatibleMutator();
			}
		};
	}
	
	AccessorByMethod(Method getter, Object[] methodParameters, Mutator<C, T> mutator) {
		this.getter = getter;
		this.methodParameters = methodParameters;
		this.mutator = () -> mutator;
	}
	
	/**
	 * Constructor for a getter-equivalent method
	 * 
	 * @param declaringClass type that declares the method
	 * @param getterName name of the accessor
	 */
	public AccessorByMethod(Class<C> declaringClass, String getterName) {
		this(Reflections.getMethod(declaringClass, getterName));
	}
	
	/**
	 * Constructor for a getter that already has an argument value
	 * 
	 * @param declaringClass type that declares the method
	 * @param methodName a one-arg method
	 * @param inputType argument type of the method
	 * @param input the argument value
	 * @param <I> argument type
	 */
	public <I> AccessorByMethod(Class<C> declaringClass, String methodName, Class<I> inputType, I input) {
		this(Reflections.getMethod(declaringClass, methodName, inputType), input);
	}
	
	@Override
	public Method getGetter() {
		return getter;
	}
	
	@Override
	public Method getMethod() {
		return getGetter();
	}
	
	@Override
	public Class<T> getPropertyType() {
		return (Class<T>) getMethod().getReturnType();
	}
	
	@Override
	public T get(C c) {
		return get(c, methodParameters);
	}
	
	/**
	 * Sets parameters
	 * 
	 * @param values expecting to have at least 1 element
	 * @return this
	 */
	public AccessorByMethod<C, T> setParameters(Object ... values) {
		for (int i = 0; i < values.length; i++) {
			setParameter(i, values[i]);
		}
		return this;
	}
	
	/**
	 * Sets parameters at index
	 *
	 * @param index the parameter index to be set
	 * @param value value of the parameter
	 * @return this
	 */
	public AccessorByMethod<C, T> setParameter(int index, Object value) {
		this.methodParameters[index] = value;
		return this;
	}
	
	/**
	 * @param index expected to be positive and in parameters size bound
	 * @return value of the parameter at index
	 */
	public Object getParameter(@Nonnegative int index) {
		return methodParameters[index];
	}
	
	/**
	 * Applies this getter on the given bean, with params.
	 * Parameters already set with {@link #setParameter(int, Object)} or {@link #setParameters(Object...)} won't be used.
	 * 
	 * @param c an Object
	 * @param params arguments
	 * @return result of the called method
	 */
	public T get(C c, Object ... params) {
		try {
			return doGet(c, params);
		} catch (ReflectiveOperationException | RuntimeException t) {
			handleException(t, c, params);
			// shouldn't happen
			return null;
		}
	}
	
	@Override
	// NB: set final to force override doGet(C, Object ...) and so to avoid mistake
	protected final T doGet(C c) throws IllegalAccessException, InvocationTargetException {
		return doGet(c, new Object[0]);
	}
	
	protected T doGet(C c, Object ... args) throws IllegalAccessException, InvocationTargetException {
		return (T) getGetter().invoke(c, args);
	}
	
	@Override
	protected String getGetterDescription() {
		return Reflections.toString(getGetter());
	}
	
	/**
	 * @return a mutator based on the equivalent setter method if exists, else based on field direct access
	 * @throws NonReversibleAccessor if neither setter nor field could be found
	 */
	@Override
	public Mutator<C, T> toMutator() {
		// Note : this will ask for findCompatibleMutator() if not initialized
		return this.mutator.get();
	}
	
	private MutatorByMember<C, T, ? extends Member> findCompatibleMutator() {
		Class<?> declaringClass = getter.getDeclaringClass();
		String propertyName = propertyName(getter);
		MutatorByMethod<C, T> mutatorByMethod = Accessors.mutatorByMethod((Class<C>) declaringClass, propertyName, (Class<T>) getter.getReturnType(), this);
		if (mutatorByMethod == null) {
			try {
				return Accessors.mutatorByField(declaringClass, propertyName, this);
			} catch (MemberNotFoundException e) {
				throw new NonReversibleAccessor("Can't find a mutator for " + Reflections.toString(getter), e);
			}
		} else {
			return mutatorByMethod;
		}
	}
	
	@Override
	public boolean equals(Object other) {
		// We base our implementation on the getter String because a setAccessible() call on the member changes its internal state
		// and I don't think it sould be taken into account for comparison
		// We could base it on getGetterDescription() but it requires more computation
		return this == other || 
				(other instanceof AccessorByMethod
						&& getGetter().toString().equals(((AccessorByMethod) other).getGetter().toString())
						&& Arrays.equals(methodParameters, ((AccessorByMethod) other).methodParameters));
	}
	
	@Override
	public int hashCode() {
		return 31 * getGetter().hashCode() + Arrays.hashCode(methodParameters);
	}
}