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

com.dooapp.gaedo.finders.dynamic.DynamicFinderMethodResolver Maven / Gradle / Ivy

/**
 * 
 */
package com.dooapp.gaedo.finders.dynamic;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import com.dooapp.gaedo.exceptions.finder.dynamic.MethodBindingException;
import com.dooapp.gaedo.exceptions.finder.dynamic.UnableToBuildQueryExpressionException;
import com.dooapp.gaedo.finders.FieldInformer;
import com.dooapp.gaedo.finders.FinderCrudService;
import com.dooapp.gaedo.finders.QueryExpression;
import com.dooapp.gaedo.finders.QueryExpressionContainer;
import com.dooapp.gaedo.finders.QueryExpressionContainerVisitor;
import com.dooapp.gaedo.finders.SortingBuilder;
import com.dooapp.gaedo.finders.SortingExpression;
import com.dooapp.gaedo.finders.sort.SortingExpressionImpl;
import com.dooapp.gaedo.patterns.Visitable;
import com.dooapp.gaedo.utils.MethodResolver;
import com.dooapp.gaedo.utils.Utils;

/**
 * Resolves a method call to a an executable set of method calls on back end servcie
 * @author ndx
 *
 */
public class DynamicFinderMethodResolver implements QueryExpressionContainer, MethodResolver {
	/**
	 * Combination mode for method
	 */
	private Combinator combinator = Combinator.And;
	/**
	 * We use a collection of entries to associate the {@link FieldInformer} in a unique way to the {@link Method} used to get associated expression
	 */
	private Collection> queryExpressions = new LinkedList>();
	/**
	 * Mode defining how the produced QueryStatement will be executed
	 */
	private Mode mode;
	
	/**
	 * Back-end on which all invocations are done
	 */
	private FinderCrudService backEnd;
	
	/**
	 * Execution id for this method resolution, built from some method text
	 */
	private final String executionId;
	
	/**
	 * Method return type, used to provide some type coercion
	 */
	private Class returnType;
	
	/**
	 * used sorting expression
	 */
	private SortingExpression sortingExpression = new SortingExpressionImpl();

	public DynamicFinderMethodResolver(
			FinderCrudService backEnd,
			Method method) {
		this(backEnd, method.toGenericString(), method.getReturnType());
	}

	public DynamicFinderMethodResolver(
			FinderCrudService backEnd,
			String executionId,
			Class returnType) {
		this.backEnd = backEnd;
		this.executionId = executionId;
		this.returnType = returnType;
	}

	/**
	 * Adds one expression to the list of checked query expressions
	 * @param informer
	 * @param method
	 */
	public void addQueryExpression(FieldInformer informer, Method method) {
		queryExpressions.add(new com.dooapp.gaedo.utils.Entry(informer, method));
	}

	/**
	 * Adds one expression to the list of checked query expressions
	 * @param informer
	 * @param method
	 */
	public void addSortingExpression(FieldInformer informer, SortingExpression.Direction direction) {
		sortingExpression.add(informer, direction);
	}

	/**
	 * Build the QueryExpression, then gently ask {@link Mode#execute(FinderCrudService, QueryExpression, SortingBuilder, Object[])} to perform query.
	 * Finally, for findAll, we provide the additionnal feature to put obejcts in the correct Iterable sub-interface associated type
	 * @param args
	 * @return
	 */
	public Object call(Object[] args) {
		Collection resolvedExpressions = new LinkedList();
		int offset = mode.getOffset();
		for(Map.Entry entry : queryExpressions) {
			int consumableLength = entry.getValue().getParameterTypes().length;
			Object[] methodArgs = new Object[consumableLength];
			System.arraycopy(args, offset, methodArgs, 0, consumableLength);
			offset += consumableLength;
			try {
				resolvedExpressions.add((QueryExpression) entry.getValue().invoke(entry.getKey(), methodArgs));
			} catch (Exception e) {
				throw new UnableToBuildQueryExpressionException(entry.getKey(), entry.getValue(), methodArgs, e);
			}
		}
		QueryExpression queryExpression = combinator.create(resolvedExpressions);
		// Creates an array of parameters going from 0 to offset
		Object[] modeArgs = new Object[mode.getOffset()];
		System.arraycopy(args, 0, modeArgs, 0, mode.getOffset());
		Object returned = mode.execute(backEnd, queryExpression, sortingExpression, modeArgs, executionId);
		// Find all tricky trick
		if(mode==Mode.FIND_ALL || mode==Mode.FIND_RANGE) {
			// If method return type is not iterable but a sub-interface, put my merlin's cape !
			if(!Iterable.class.equals(returnType)) {
				Collection toReturn = Utils.generateCollection(returnType, null);
				Iterable iterable = (Iterable) returned;
				for(Object data : iterable) {
					toReturn.add(data);
				}
				returned = toReturn;
			}
		}
		return returned;
	}

	/**
	 * Check that this MethodResolver uses the same parameters stack than the given Method object
	 * @param method input method
	 */
	public void checkParametersClasses(Method method) {
		Collection errors = new LinkedList();
		Type[] consumableParameters = method.getGenericParameterTypes();
		List consumableList = new LinkedList(Arrays.asList(consumableParameters));
		// Creates an array of parameters going from 0 to offset
		Type[] modeArgs = new Type[mode.getOffset()];
		System.arraycopy(consumableParameters, 0, modeArgs, 0, mode.getOffset());
		mode.checkParametersClasses(method, modeArgs, this);
		int paramIndex = mode.getOffset();
		for(Map.Entry entry : queryExpressions) {
			Class[] parameters = entry.getValue().getParameterTypes();
			for(Class p : parameters) {
				if(consumableList.size()==0) {
					errors.add("there are not enough parameters to match method call "+entry.getKey().toString()+" "+entry.getValue().toGenericString());
					continue;
				}
				Type toCompare = consumableList.remove(0);
				if(toCompare instanceof Class) {
					Class toCompareClass = (Class) toCompare;
					if(toCompareClass.isPrimitive()) {
						// Do some magick to retrieve associated class
						toCompareClass = Utils.objectify(toCompareClass);
					}
					if(!p.isAssignableFrom(toCompareClass)) {
						errors.add("parameter "+p.getName()+" of method "+entry.getValue().toGenericString()+"cannot use value of type "+toCompareClass.getName());
					}
				} else {
					throw new UnsupportedOperationException(toCompare.getClass().getName()+" not supported in that case. please fill a gaedo-definition bug report");
				}
			}
			paramIndex++;
		}
		if(errors.size()>0) {
			throw new MethodBindingException(method, this, errors);
		}
	}

	public Mode getMode() {
		return mode;
	}

	public void setCombinator(Combinator c) {
		combinator = c;
	}

	public void setMode(Mode mode) {
		this.mode = mode;
	}
	
	@Override
	public String toString() {
		StringBuilder sOut = new StringBuilder();
		sOut.append("combination : ").append(combinator).append("\n");
		sOut.append("expression :\n");
		for(Map.Entry entry : queryExpressions) {
			sOut.append("\t").append(entry.getKey().toString()).append("\tinvoking\t").append(entry.getValue().toGenericString()).append("\n");
		}
		return sOut.toString();
	}

	/**
	 * Check that both parameters and return types are compliant with what we expect
	 * @param method
	 */
	public void checkMethod(Method method) {
		checkParametersClasses(method);
		checkReturnType(method);
	}

	/**
	 * Check that implemented method return type is compliant with internal code.
	 * Notice we can perform some type escalation (from Iterable to Collection, as an example)
	 * @param method
	 */
	private void checkReturnType(Method method) {
		Class returnType = method.getReturnType();
		switch(mode) {
		case COUNT:
			if(!(Integer.class.isAssignableFrom(returnType) || Integer.TYPE.isAssignableFrom(returnType) ||
					Long.class.isAssignableFrom(returnType) || Long.TYPE.isAssignableFrom(returnType))) {
				throw new BadReturnTypeException(method, returnType, Integer.class, int.class, long.class, Long.class);
			}
			break;
		case FIND_ALL:
			if(!Iterable.class.isAssignableFrom(returnType)) {
				throw new BadReturnTypeException(method, returnType, Iterable.class);
			}
			break;
		case FIND_ONE:
			if(!returnType.isAssignableFrom(backEnd.getContainedClass())) {
				throw new BadReturnTypeException(method, returnType, backEnd.getContainedClass());
			}
		}
	}

	@Override
	public void accept(QueryExpressionContainerVisitor visitor) {
		visitor.startVisit(this);
		// TODO find a way to visit non-constructed query expressions !
		sortingExpression.accept(visitor);
		visitor.endVisit(this);
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy