package plasticdemo.services; import java.lang.reflect.Method; import org.apache.tapestry5.plastic.ClassInstantiator; import org.apache.tapestry5.plastic.InstructionBuilder; import org.apache.tapestry5.plastic.InstructionBuilderCallback; import org.apache.tapestry5.plastic.MethodDescription; import org.apache.tapestry5.plastic.PlasticClass; import org.apache.tapestry5.plastic.PlasticClassTransformer; import org.apache.tapestry5.plastic.PlasticField; import org.apache.tapestry5.plastic.PlasticManager; import org.apache.tapestry5.plastic.PlasticMethod; import org.apache.tapestry5.plastic.PlasticUtils; /** * {@inheritDocs} */ public class MethodShadowBuilderImpl implements MethodShadowBuilder { private PlasticManager pm; public MethodShadowBuilderImpl(PlasticManager pm) { this.pm = pm; } /** * {@inheritDoc} */ public <T> T build(final Object source, final String methodName, final Class<T> interfaceType, final Object... args) { final String sourceClass = PlasticUtils.toTypeName(source.getClass()); final String interfaceClass = PlasticUtils.toTypeName(interfaceType); // Ensure it is an interface if (!interfaceType.isInterface()) { throw new RuntimeException(interfaceType.getName() + " is not an interface"); } // Get argument types final Class<?>[] parameterTypes = new Class<?>[args.length]; for (int i = 0; i < parameterTypes.length; ++i) { parameterTypes[i] = args[i].getClass(); } // Check and get matching method final Method matchingMethod = getCompatibleMethod(source.getClass(), methodName, parameterTypes); if (matchingMethod == null) { throw new RuntimeException("Could not find method " + methodName + " with arguments " + parameterTypes); } // Create a new class which implements the interface final ClassInstantiator<T> instantiator = pm.createProxy(interfaceType, new PlasticClassTransformer() { public void transform(PlasticClass plasticClass) { final PlasticField field = plasticClass.introduceField( sourceClass, "_$source"); field.inject(source); final PlasticField[] argumentFields = new PlasticField[parameterTypes.length]; for (int i = 0; i < argumentFields.length; ++i) { argumentFields[i] = plasticClass.introduceField( args[i].getClass(), "_$field" + i); argumentFields[i].inject(args[i]); } PlasticMethod delegateMethod = plasticClass .introduceMethod(new MethodDescription( interfaceClass, "_delegate")); delegateMethod .changeImplementation(new InstructionBuilderCallback() { public void doBuild(InstructionBuilder builder) { builder.loadThis().getField(field); for (int i = 0; i < argumentFields.length; ++i) { builder.loadThis() .getField(argumentFields[i]) .castOrUnbox( PlasticUtils .toTypeName(matchingMethod .getParameterTypes()[i])); } builder.invokeVirtual( sourceClass, interfaceClass, methodName, PlasticUtils .toTypeNames(matchingMethod .getParameterTypes())); builder.returnResult(); } }); for (Method method : interfaceType.getMethods()) { plasticClass.introduceMethod(method).delegateTo( delegateMethod); } } }); return instantiator.newInstance(); } /** * Get a compatible constructor to the supplied parameter types. Most of this * method is copied from {@linkplain http * ://christerblog.wordpress.com/2010/02 * /27/java-reflection-matching-formal-parameter * -list-to-actual-parameter-list/ This Blog} * * @param clazz * the class which we want to construct * @param parameterTypes * the types required of the constructor * * @return a compatible constructor or null if none exists */ public static Method getCompatibleMethod(Class<?> clazz, String methodName, Class<?>[] parameterTypes) { Method[] methods = clazz.getDeclaredMethods(); for (int i = 0; i < methods.length; i++) { if (!methods[i].getName().equals(methodName)) { continue; } if (methods[i].getParameterTypes().length == (parameterTypes != null ? parameterTypes.length : 0)) { Class<?>[] methodTypes = methods[i].getParameterTypes(); boolean isCompatible = true; for (int j = 0; j < (parameterTypes != null ? parameterTypes.length : 0); j++) { if (!methodTypes[j].isAssignableFrom(parameterTypes[j])) { if (parameterTypes[j].isPrimitive()) { Class<?> wrapperType = PlasticUtils .toWrapperType(parameterTypes[j]); if (methodTypes[j].isAssignableFrom(wrapperType)) { isCompatible = false; } } if (!isCompatible && methodTypes[j].isPrimitive()) { Class<?> wrapperType = PlasticUtils .toWrapperType(methodTypes[j]); if (!wrapperType.isAssignableFrom(parameterTypes[j])) { isCompatible = false; } } } } if (isCompatible) { return methods[i]; } } } return null; } }