package com.bergerkiller.bukkit.common.reflection; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import net.sf.cglib.asm.Type; import net.sf.cglib.core.Signature; import net.sf.cglib.proxy.Callback; import net.sf.cglib.proxy.CallbackFilter; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import net.sf.cglib.proxy.NoOp; import com.bergerkiller.bukkit.common.Common; import com.bergerkiller.bukkit.common.reflection.gen.CallbackMethod; import com.bergerkiller.bukkit.common.reflection.gen.CallbackSignature; import com.bergerkiller.bukkit.common.reflection.gen.ProxyCallbackSignature; import com.bergerkiller.bukkit.common.reflection.gen.SuperCallbackSignature; import com.bergerkiller.bukkit.common.utils.LogicUtil; /** * A simple implementation builder that allows the creation of extensions of classes. * This is done by specifying interfaces and implementations for these interfaces.<br><br> * * To construct new extensions of classes, do the following:<br> * - Make new interface(s) containing the methods to override<br> * - Implement these interface(s) in your own implementation class(es)<br> * - Construct a new Class Builder using the superclass and implementation<br><br> * * To call super methods in the superclass, add interface methods starting with 'super_'. * These methods are never called in the implemented version, you can leave them empty there. */ public class ClassBuilder { private static final String SUPER_PREFIX = "super_"; private final Class<?> superClass; private final Enhancer enhancer = new Enhancer(); private final Map<Class<?>, Object> callbackInstancesBuffer = new HashMap<Class<?>, Object>(); private final Map<Signature, CallbackSignature> callbackSignatures = new HashMap<Signature, CallbackSignature>(); private final List<Class<?>> callbackClasses; @SuppressWarnings("rawtypes") public ClassBuilder(Class<?> superclass, Class... callbackClasses) { this(superclass, Arrays.asList((Class<?>[]) callbackClasses)); } public ClassBuilder(Class<?> superclass, Collection<Class<?>> callbackClasses) { if (LogicUtil.nullOrEmpty(callbackClasses)) { throw new IllegalArgumentException("At least one Callback Class is needed to use a Class Builder"); } this.callbackClasses = Collections.unmodifiableList(new ArrayList<Class<?>>(callbackClasses)); try { this.superClass = superclass; // Obtain all available interfaces and callbacks from the callback classes Collection<Class<?>> interfaceClasses = new ArrayList<Class<?>>(callbackClasses.size()); for (Class<?> callbackClass : callbackClasses) { for (Class<?> interfaceClass : callbackClass.getInterfaces()) { interfaceClasses.add(interfaceClass); // Set the initial Callbacks - this is important during the CallbackFilter accept phase for (Method method : interfaceClass.getDeclaredMethods()) { addCallback(method, null); } } } // Initialize the base Class enhancer.setSuperclass(superclass); enhancer.setInterfaces(interfaceClasses.toArray(new Class<?>[0])); enhancer.setClassLoader(getClass().getClassLoader()); enhancer.setCallbackTypes(new Class<?>[] {NoOp.class, CallbackMethodInterceptor.class}); enhancer.setCallbackFilter(new CallbackFilter() { @Override public int accept(Method method) { return callbackSignatures.containsKey(getSig(method)) ? 1 : 0; } }); // Generate callback instances CallbackSignature callback; for (Class<?> interfaceClass : interfaceClasses) { for (Method method : interfaceClass.getDeclaredMethods()) { // Find the best suitable way of redirecting this Method callback = null; final String name = method.getName(); if (name.startsWith(SUPER_PREFIX)) { // Automatically redirect to the super method final String methodName = Common.SERVER.getMethodName(superclass, name.substring(SUPER_PREFIX.length()), method.getParameterTypes()); final Signature superSig = getSig(method, methodName); try { // Try to get the method in the superclass - prior if (!SafeMethod.contains(superClass, methodName, method.getParameterTypes())) { throw new RuntimeException("Could not find super method: " + superSig); } callback = new SuperCallbackSignature(superSig); } catch (IllegalArgumentException ex) { throw new RuntimeException("Could not access super method: " + superSig, ex); } } else { // Find this method in one of the callback classes for (Class<?> callBackClass : callbackClasses) { if (interfaceClass.isAssignableFrom(callBackClass)) { callback = new ProxyCallbackSignature(callBackClass.getDeclaredMethod(method.getName(), method.getParameterTypes())); break; } } } // Put the callback instance if available if (callback == null) { throw new RuntimeException("Interface method is not implemented: " + method.getName()); } addCallback(method, callback); } } } catch (Throwable t) { throw new RuntimeException("Could not initialize Entity Class Builder for '" + superclass.getSimpleName() + "':", t); } } /** * Creates a new instance using the constructor that matches the argumentTypes, using the arguments. * The callbacks are applied to the newly created instance. * * @param argumentTypes of the superclass constructor to use * @param arguments to pass along the superclass constructor * @param callbacks to use for the newly created instance * @return a new instance created by this ClassBuilder */ public synchronized Object create(Class<?>[] argumentTypes, Object[] arguments, Collection<Object> callbacks) { try { // Prepare the instances buffer: add all callback instances for (Object callbackInstance : callbacks) { callbackInstancesBuffer.put(callbackInstance.getClass(), callbackInstance); } // Create a method interceptor, assign it and then create a new instance final CallbackMethodInterceptor interceptor = new CallbackMethodInterceptor(); enhancer.setCallbacks(new Callback[] {NoOp.INSTANCE, interceptor}); final Object instance = enhancer.create(argumentTypes, arguments); // Prepare for clearing the callback instances buffer interceptor.createAllCallbacks(instance); return instance; } finally { callbackInstancesBuffer.clear(); } } /** * Gets an unmodifiable List of Callback Classes used by this Class Builder * * @return Unmodifiable List of Callback Classes */ public List<Class<?>> getCallbackClasses() { return this.callbackClasses; } /** * Gets the superclass that new Instances created by this builder extend * * @return superclass */ public Class<?> getSuperClass() { return this.superClass; } private void addCallback(Method superMethod, CallbackSignature callback) { Signature sig = getSig(superMethod); this.callbackSignatures.put(sig, callback); String sigName = sig.getName(); if (sigName.startsWith(SUPER_PREFIX)) { return; } String fixedName = Common.SERVER.getMethodName(this.superClass, sigName, superMethod.getParameterTypes()); if (!fixedName.equals(sigName)) { Signature fixedSig = getSig(superMethod, fixedName); this.callbackSignatures.put(fixedSig, callback); } } private static Signature getSig(Method method) { return getSig(method, method.getName()); } private static Signature getSig(Method method, String name) { return new Signature(name, Type.getReturnType(method), Type.getArgumentTypes(method)); } private final class CallbackMethodInterceptor implements MethodInterceptor { private final Map<Signature, Object> callbackMethods; public CallbackMethodInterceptor() { this.callbackMethods = new HashMap<Signature, Object>(callbackSignatures); } /** * Converts all (any remaining) Callback Signatures to Callback Methods. * After this method is called, the callback instances buffer can be cleared and re-used. * * @param instance that was created */ public void createAllCallbacks(Object instance) { for (Map.Entry<Signature, Object> callbackEntry : this.callbackMethods.entrySet()) { Object callback = callbackEntry.getValue(); if (callback instanceof CallbackSignature) { callbackEntry.setValue(createCallback(callback, instance)); } } } @Override public Object intercept(Object instance, Method method, Object[] args, MethodProxy proxy) throws Throwable { Object callback = this.callbackMethods.get(proxy.getSignature()); if (callback instanceof CallbackSignature) { // Initialize this callback method (occurred in the Class constructor) callback = createCallback(callback, instance); this.callbackMethods.put(proxy.getSignature(), callback); } if (callback instanceof CallbackMethod) { // Execute the method return ((CallbackMethod) callback).invoke(instance, args); } else { // This should never occur because we have a filter, but it's here for safety purposes return proxy.invokeSuper(instance, args); } } private CallbackMethod createCallback(Object callbackSignature, Object instance) { return ((CallbackSignature) callbackSignature).createCallback(instance, callbackInstancesBuffer); } } }