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

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

package com.dooapp.gaedo.finders.dynamic;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;
import java.util.logging.Logger;

import com.dooapp.gaedo.exceptions.finder.dynamic.UnableToBuildDueToMissingQueryExpressionException;
import com.dooapp.gaedo.exceptions.finder.dynamic.UnableToBuildDueToMissingFieldException;
import com.dooapp.gaedo.exceptions.finder.dynamic.UnableToBuildDueToMissingModeException;
import com.dooapp.gaedo.exceptions.finder.dynamic.UnableToBuildDueToMissingSortingExpressionException;
import com.dooapp.gaedo.finders.FieldInformer;
import com.dooapp.gaedo.finders.FinderCrudService;
import com.dooapp.gaedo.finders.Informer;
import com.dooapp.gaedo.finders.SortingExpression;
import com.dooapp.gaedo.finders.id.IdBasedService;
import com.dooapp.gaedo.properties.PropertyProvider;
import com.dooapp.gaedo.properties.PropertyProviderUtils;
import com.dooapp.gaedo.utils.BasicInvocationHandler;
import com.dooapp.gaedo.utils.CallMethodOnObjectResolver;
import com.dooapp.gaedo.utils.MethodResolver;
import com.dooapp.gaedo.utils.Utils;
import com.dooapp.gaedo.utils.VirtualMethodCreationException;

/**
 * A dynamic finder handler is the class that will transform methods declared in provided interface into calls to back end.
 *
 * @author ndx
 *
 * @param 
 */
public class DynamicFinderHandler extends BasicInvocationHandler implements InvocationHandler {
	/**
	 * text used to combine one or more element of sort
	 */
	private static final String SORT_BY_COMBINATOR = "Then";

	/**
	 * Prefix used for toggling mode from filter to sort
	 */
	private static final String SORT_BY_PREFIX = "SortBy";

	/**
	 * Enum used when creating a method resolver to select in which mode we currently are.
	 * This mode will determine how consumed text of method signature is transformed into executable MethodResolver code
	 * @author ndx
	 *
	 */
	private enum ParametersConstructionMode {
		FILTER,
		SORT;

		public String consumeText(DynamicFinderHandler handler, String methodText, String consumableText, Map fieldNames,DynamicFinderMethodResolver created) {
			switch(this) {
			case FILTER:
				return handler.consumeFilter(methodText, consumableText, fieldNames, created);
			case SORT:
				return handler.consumeSort(methodText, consumableText, fieldNames, created);
			}
			throw new UnsupportedOperationException("are you kiddin or what ? You used an undeclared mode named "+name()+"\nplease fill a bug report at gaedo-definition");
		}
	}
	private static final Logger logger  = Logger.getLogger(DynamicFinderHandler.class.getName());

	/**
	 * Backend which will receive forwarded calls
	 */
	protected final FinderCrudService> backEnd;

	/**
	 * Used property provider
	 */
	protected final PropertyProvider propertyProvider;

	/**
	 * Define the handler by giving it the class to implement and the back end used to implement it.
	 * Due to issue #18, we eager create method resolvers.
	 * @param toImplement interface to implement
	 * @param backEnd back end that will receive calls
	 */
	public DynamicFinderHandler(Class toImplement,
			FinderCrudService backEnd, PropertyProvider provider) {
		super(toImplement);
		this.backEnd = backEnd;
		this.propertyProvider = provider;
		createAllMethodResolvers(toImplement);
	}

	/**
	 * Consume text to populate the sorting expression
	 * @category dynamic_method_builder
	 */
	public String consumeSort(String methodString, String consumableText, Map fieldNames,DynamicFinderMethodResolver created) {
		boolean found = false;
		for(Map.Entry fieldEntry: fieldNames.entrySet()) {
			if(consumableText.startsWith(fieldEntry.getKey())) {
				consumableText = consumableText.substring(fieldEntry.getKey().length());
				FieldInformer informer = backEnd.getInformer().get(fieldEntry.getValue());
				found = false;
				for(SortingExpression.Direction direction : SortingExpression.Direction.values()) {
					if(consumableText.startsWith(direction.getText())) {
						consumableText = consumableText.substring(direction.getText().length());
						created.addSortingExpression(informer, direction);
						found = true;
					}
				}
				if(!found) {
					throw new UnableToBuildDueToMissingSortingExpressionException(consumableText, methodString, fieldEntry.getKey(),SortingExpression.Direction.values());
				}
				// Sorting can only be combined with And text
				if(consumableText.startsWith(SORT_BY_COMBINATOR)) {
					consumableText = consumableText.substring(SORT_BY_COMBINATOR.length());
				}
				found = true;
			}
		}
		return consumableText;
	}

	/**
	 * Consume text to populate the query expression
	 * @category dynamic_method_builder
	 */
	public String consumeFilter(String methodString, String consumableText, Map fieldNames,DynamicFinderMethodResolver created) {
		boolean found = false;
		for(Map.Entry fieldEntry: fieldNames.entrySet()) {
			if(consumableText.startsWith(fieldEntry.getKey())) {
				consumableText = consumableText.substring(fieldEntry.getKey().length());
				FieldInformer informer = backEnd.getInformer().get(fieldEntry.getValue());
				Map queries = Utils.getUppercasedMap(informer.getClass().getMethods());
				found = false;
				for(Map.Entry methodEntry : queries.entrySet()) {
					if(consumableText.startsWith(methodEntry.getKey())) {
						consumableText = consumableText.substring(methodEntry.getKey().length());
						created.addQueryExpression(informer, methodEntry.getValue());
						found = true;
					}
				}
				// TODO insert here code for call chaining
				if(!found) {
					throw new UnableToBuildDueToMissingQueryExpressionException(consumableText, methodString, fieldEntry.getKey(),
									Utils.getUppercasedMap(Utils.removeGaedoInternalMethodsFrom(informer.getClass().getMethods())).keySet());
				}
				for(Combinator c: Combinator.values()) {
					if(consumableText.startsWith(c.getText())) {
						consumableText = consumableText.substring(c.getText().length());
						created.setCombinator(c);
					}
				}
				found = true;
			}
		}
		return consumableText;
	}

	/**
	 * Create the resolver for the method name by consuming its string declaration
	 * @param method method to map
	 * @return a method resolver for the given method name
	 * @category dynamic_method_builder
	 */
	public MethodResolver createResolver(Method method) throws VirtualMethodCreationException {
		Class declaringClass = method.getDeclaringClass();
		if(declaringClass.isAssignableFrom(DynamicFinder.class)) {
			return new CallMethodOnObjectResolver(backEnd, method);
		} else if(declaringClass.isAssignableFrom(DynamicFinderHandler.class)) {
			return new CallMethodOnObjectResolver(this, method);
		} else if(declaringClass.isAssignableFrom(IdBasedService.class)) {
			return new CallMethodOnObjectResolver(backEnd, method);
		} else {
			return createResolverForDynamicMethod(method);
		}
	}

	/**
	 * When using dynamic languages, we can't access to high level type information like in Java, but rather to a name and method parameters (which are actual ones).
	 * As a consequence, a weaker type handling is to use
	 * @param method
	 * @return
	 */
	private DynamicFinderMethodResolver createResolverForDynamicMethod(String methodName, Object[] methodArgs) {
		boolean found = false;
		String consumableText = methodName;

		ParametersConstructionMode mode = ParametersConstructionMode.FILTER;
		DynamicFinderMethodResolver created = new DynamicFinderMethodResolver(backEnd, methodName, Object.class);
		for(Mode m : Mode.values()) {
			if(consumableText.startsWith(m.getPrefix())) {
				created.setMode(m);
				found = true;
			}
		}
		if(!found) {
			throw new UnableToBuildDueToMissingModeException(methodName);
		}
		consumableText = consumableText.substring(created.getMode().getPrefix().length());
		Map fieldNames = Utils.getUppercasedMap(PropertyProviderUtils.getAllProperties(propertyProvider, backEnd.getContainedClass()));
		while(consumableText.length()>0) {
			String initialText = consumableText;
			consumableText = mode.consumeText(this, methodName, consumableText, fieldNames, created);
			if(consumableText.startsWith(SORT_BY_PREFIX)) {
				if(mode==ParametersConstructionMode.FILTER) {
					mode = ParametersConstructionMode.SORT;
					consumableText = consumableText.substring(SORT_BY_PREFIX.length());
				}
			}
			if(initialText.equals(consumableText)) {
				throw new UnableToBuildDueToMissingFieldException(consumableText, methodName, fieldNames.keySet());
			}
		}
		return created;
	}

	/**
	 * Create resolver for method object
	 * @param method
	 * @return
	 */
	private DynamicFinderMethodResolver createResolverForDynamicMethod(
			Method method) {
		boolean found = false;
		String consumableText = method.getName();
		ParametersConstructionMode mode = ParametersConstructionMode.FILTER;
		DynamicFinderMethodResolver created = new DynamicFinderMethodResolver(backEnd, method);
		for(Mode m : Mode.values()) {
			if(consumableText.startsWith(m.getPrefix())) {
				created.setMode(m);
				found = true;
			}
		}
		if(!found) {
			throw new UnableToBuildDueToMissingModeException(method);
		}
		consumableText = consumableText.substring(created.getMode().getPrefix().length());
		Map fieldNames = Utils.getUppercasedMap(PropertyProviderUtils.getAllProperties(propertyProvider, backEnd.getContainedClass()));
		while(consumableText.length()>0) {
			String initialText = consumableText;
			consumableText = mode.consumeText(this, method.toGenericString(), consumableText, fieldNames, created);
			if(consumableText.startsWith(SORT_BY_PREFIX)) {
				if(mode==ParametersConstructionMode.FILTER) {
					mode = ParametersConstructionMode.SORT;
					consumableText = consumableText.substring(SORT_BY_PREFIX.length());
				}
			}
			if(initialText.equals(consumableText)) {
				throw new UnableToBuildDueToMissingFieldException(consumableText, method, fieldNames.keySet());
			}
		}
		// Now created has been terminated, check method corresponds to what we expect
		created.checkMethod(method);
		return created;
	}

	public FinderCrudService> getBackEnd() {
		return backEnd;
	}

	public Class getToImplement() {
		return toImplement;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy