package org.uva.student.calinwouter.qlqls.qls; import org.uva.student.calinwouter.qlqls.qls.exceptions.CouldNotFindMatchingQLSComponentException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.Arrays; import java.util.LinkedList; import java.util.List; /** * This class is used for dynamically creating new functions or widgets from the provided component/widget paths. */ public class QLSReflection { private final List<String> searchPaths; private Class<?> fetchClassOrNull(String classPath) { try { return Class.forName(classPath); } catch(ClassNotFoundException e) { return null; } } private Class findClassInAllowedPaths(final String className) throws ClassNotFoundException { for (String path : searchPaths) { final String classPath = path + className; final Class<?> classOrNull = fetchClassOrNull(classPath); if (classOrNull != null) { return classOrNull; } } throw new ClassNotFoundException(); } /** * Get the constructor's last parameter element's class, used for casting a var args object array to the type * of the var args' elements. */ private Class<? extends Object[]> getLastConstructorParameterTypeElement(Constructor c) { assert c.isVarArgs(); final Class[] parameterTypes = c.getParameterTypes(); return (Class<? extends Object[]>) parameterTypes[parameterTypes.length - 1]; } /** * Convert the object-array to the array of the type of the var args. */ private Object[] castConstructorVarArgsArgumentsUp(List<Object> varArgsArguments, Constructor constructor) { final Object[] varArgsArgumentArray = varArgsArguments.toArray(); final Integer varArgsArgumentArrayLength = varArgsArgumentArray.length; final Class<? extends Object[]> lastConstructorParameterTypeElement = getLastConstructorParameterTypeElement(constructor); return Arrays.copyOf(varArgsArgumentArray, varArgsArgumentArrayLength, lastConstructorParameterTypeElement); } /** * Convert varargs constructor's parameters to normal call parameters (because reflection converts varargs to * an array). */ private Object[] newInstanceParametersUsingVarArgs(List<Object> args, Constructor constructor) { List<Object> argsCopy = new LinkedList<Object>(args); final int origLength = constructor.getParameterTypes().length; final List<Object> varArgsArguments = new LinkedList<Object>(); final List<Object> results = new LinkedList<Object>(); while (argsCopy.size() >= origLength) { Object lastArgument = argsCopy.remove(argsCopy.size() - 1); varArgsArguments.add(0, lastArgument); } results.addAll(argsCopy); final Object[] castedConstructorVarArgs = castConstructorVarArgsArgumentsUp(varArgsArguments, constructor); results.add(castedConstructorVarArgs); return results.toArray(); } private Object createNewInstance(Constructor constructor, List<Object> args) throws IllegalAccessException, InvocationTargetException, InstantiationException { if (constructor.isVarArgs()) { final Object[] newInstanceParametersUsingVarArgs = newInstanceParametersUsingVarArgs(args, constructor); return constructor.newInstance(newInstanceParametersUsingVarArgs); } return constructor.newInstance(args.toArray()); } private Object createNewInstanceOrReturnNull(Constructor constructor, List<Object> args) throws IllegalAccessException, InvocationTargetException, InstantiationException { try { return createNewInstance(constructor, args); } catch (IllegalArgumentException e) { return null; } catch (ArrayStoreException e) { return null; } } /** * This method creates an instance of the class to be found by the specified name, with * this QLSAdapter as constructor parameter. */ protected Object newComponentInstance(String componentName, List<Object> args) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, ClassNotFoundException, CouldNotFindMatchingQLSComponentException { final Class<?> clazz = findClassInAllowedPaths(componentName); for (Constructor c : clazz.getConstructors()) { final Object newInstance = createNewInstanceOrReturnNull(c, args); if (newInstance != null) { return newInstance; } } throw new CouldNotFindMatchingQLSComponentException(componentName); } protected QLSReflection(List<String> searchPaths) { this.searchPaths = searchPaths; } }