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

groovy.util.ProxyGenerator Maven / Gradle / Ivy

There is a newer version: 1.5.8
Show newest version
/*
 * Copyright 2003-2007 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package groovy.util;

import groovy.lang.*;
import org.codehaus.groovy.control.MultipleCompilationErrorsException;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;

/**
 * Classes to generate 'Proxy' objects which implement interfaces
 * and/or extend classes.
 *
 * @author Paul King
 * @author Guillaume Laforge
 */
public class ProxyGenerator {
    public static boolean debug = false;

    public static Object instantiateAggregateFromBaseClass(Class clazz) {
        return instantiateAggregateFromBaseClass(null, clazz);
    }

    public static Object instantiateAggregateFromBaseClass(Map map, Class clazz) {
        return instantiateAggregateFromBaseClass(map, clazz, null);
    }

    public static Object instantiateAggregateFromBaseClass(Map map, Class clazz, Object[] constructorArgs) {
        return instantiateAggregate(map, null, clazz, constructorArgs);
    }

    public static Object instantiateAggregateFromInterface(Class clazz) {
        return instantiateAggregateFromInterface(null, clazz);
    }

    public static Object instantiateAggregateFromInterface(Map map, Class clazz) {
        List interfaces = new ArrayList();
        interfaces.add(clazz);
        return instantiateAggregate(map, interfaces);
    }

    public static Object instantiateAggregate(List interfaces) {
        return instantiateAggregate(null, interfaces);
    }

    public static Object instantiateAggregate(Map closureMap, List interfaces) {
        return instantiateAggregate(closureMap, interfaces, null);
    }

    public static Object instantiateAggregate(Map closureMap, List interfaces, Class clazz) {
        return instantiateAggregate(closureMap, interfaces, clazz, null);
    }

    public static Object instantiateAggregate(Map closureMap, List interfaces, Class clazz, Object[] constructorArgs) {
        Map map = new HashMap();
        if (closureMap != null) {
            map = closureMap;
        }
        List interfacesToImplement = new ArrayList();
        if (interfaces != null) {
            interfacesToImplement = interfaces;
        }
        Class baseClass = GroovyObjectSupport.class;
        if (clazz != null) {
            baseClass = clazz;
        }
        boolean hasArgs = constructorArgs != null && constructorArgs.length > 0;
        String name = shortName(baseClass.getName()) + "_groovyProxy";
        StringBuffer buffer = new StringBuffer();

        // add class header and fields
        buffer.append("class ").append(name);
        if (clazz != null) {
            buffer.append(" extends ").append(baseClass.getName());
        }
        for (int i = 0; i < interfacesToImplement.size(); i++) {
            Class thisInterface = (Class) interfacesToImplement.get(i);
            if (i == 0) {
                buffer.append(" implements ");
            } else {
                buffer.append(", ");
            }
            buffer.append(thisInterface.getName());
        }
        buffer.append(" {\n").append("    private closureMap\n    ");

        // add constructor
        buffer.append(name).append("(map");
        if (hasArgs) {
            buffer.append(", args");
        }
        buffer.append(") {\n");
        buffer.append("        super(");
        if (hasArgs) {
            buffer.append("*args");
        }
        buffer.append(")\n");
        buffer.append("        this.closureMap = map\n");
        buffer.append("    }\n");

        // add overwriting methods
        List selectedMethods = new ArrayList();
        List publicAndProtectedMethods = DefaultGroovyMethods.toList(baseClass.getMethods());
        publicAndProtectedMethods.addAll(getInheritedMethods(baseClass));
        for (int i = 0; i < publicAndProtectedMethods.size(); i++) {
            Method method = (Method) publicAndProtectedMethods.get(i);
            if (map.containsKey(method.getName())) {
                selectedMethods.add(method.getName());
                addOverridingMapCall(buffer, method);
            }
        }

        // add interface methods
        List interfaceMethods = new ArrayList();
        for (int i = 0; i < interfacesToImplement.size(); i++) {
            Class thisInterface = (Class) interfacesToImplement.get(i);
            interfaceMethods.addAll(DefaultGroovyMethods.toList(thisInterface.getMethods()));
            interfaceMethods.addAll(getInheritedMethods(thisInterface));
        }
        for (int i = 0; i < interfaceMethods.size(); i++) {
            Method method = (Method) interfaceMethods.get(i);
            if (!containsEquivalentMethod(publicAndProtectedMethods, method)) {
                selectedMethods.add(method.getName());
                addMapOrDummyCall(map, buffer, method);
            }
        }

        // add leftover methods from the map
        for (Iterator iterator = map.keySet().iterator(); iterator.hasNext();) {
            String methodName = (String) iterator.next();
            if (selectedMethods.contains(methodName)) continue;
            addNewMapCall(buffer, methodName);
        }

        // end class

        buffer.append("}\n").append("new ").append(name);
        buffer.append("(map");
        if (hasArgs) {
            buffer.append(", constructorArgs");
        }
        buffer.append(")");

        Binding binding = new Binding();
        binding.setVariable("map", map);
        binding.setVariable("constructorArgs", constructorArgs);
        ClassLoader cl = baseClass.getClassLoader();
        if (clazz == null && interfacesToImplement.size() > 0) {
            Class c = (Class) interfacesToImplement.get(0);
            cl = c.getClassLoader();
        }
        GroovyShell shell = new GroovyShell(cl, binding);
        if (debug)
            System.out.println("proxy source:\n------------------\n" + buffer.toString() + "\n------------------");
        try {
            return shell.evaluate(buffer.toString());
        } catch (MultipleCompilationErrorsException err) {
            throw new GroovyRuntimeException("Error creating proxy: " + err.getMessage());
        }
    }

    public static Object instantiateDelegate(Object delegate) {
        return instantiateDelegate(null, delegate);
    }

    public static Object instantiateDelegate(List interfaces, Object delegate) {
        return instantiateDelegate(null, interfaces, delegate);
    }

    public static Object instantiateDelegate(Map closureMap, List interfaces, Object delegate) {
        return instantiateDelegateWithBaseClass(closureMap, interfaces, delegate, null);
    }

    public static Object instantiateDelegateWithBaseClass(Map closureMap, List interfaces, Object delegate) {
        return instantiateDelegateWithBaseClass(closureMap, interfaces, delegate, delegate.getClass());
    }

    public static Object instantiateDelegateWithBaseClass(Map closureMap, List interfaces, Object delegate, Class baseClass) {
        Map map = new HashMap();
        if (closureMap != null) {
            map = closureMap;
        }
        List selectedMethods = new ArrayList();
        List interfacesToImplement = new ArrayList();
        if (interfaces != null) {
            interfacesToImplement = interfaces;
        }
        String name = shortName(delegate.getClass().getName()) + "_delegateProxy";
        StringBuffer buffer = new StringBuffer();

        // add class header and fields
        buffer.append("import org.codehaus.groovy.runtime.InvokerHelper\nclass ").append(name);
        if (baseClass != null) {
            buffer.append(" extends ").append(baseClass.getName());
        }
        
        for (int i = 0; i < interfacesToImplement.size(); i++) {
            Class thisInterface = (Class) interfacesToImplement.get(i);
            if (i == 0) {
                buffer.append(" implements ");
            } else {
                buffer.append(", ");
            }
            buffer.append(thisInterface.getName());
        }
        buffer.append(" {\n").append("    private delegate\n").append("    private closureMap\n    ");

        // add constructor
        buffer.append(name).append("(map, delegate) {\n");
        buffer.append("        this.closureMap = map\n");
        buffer.append("        this.delegate = delegate\n");
        buffer.append("    }\n");

        List objectMethods = DefaultGroovyMethods.toList(Object.class.getMethods());
        objectMethods.addAll(getInheritedMethods(Object.class));

        List groovyObjectMethods = DefaultGroovyMethods.toList(GroovyObject.class.getMethods());
        groovyObjectMethods.addAll(getInheritedMethods(GroovyObject.class));

        // add interface methods
        List interfaceMethods = new ArrayList();
        for (int i = 0; i < interfacesToImplement.size(); i++) {
            Class thisInterface = (Class) interfacesToImplement.get(i);
            interfaceMethods.addAll(DefaultGroovyMethods.toList(thisInterface.getMethods()));
            interfaceMethods.addAll(getInheritedMethods(thisInterface));
        }
        for (int i = 0; i < interfaceMethods.size(); i++) {
            Method method = (Method) interfaceMethods.get(i);
            if (!containsEquivalentMethod(objectMethods, method) &&
                    !containsEquivalentMethod(groovyObjectMethods, method)) {
                selectedMethods.add(method.getName());
                addWrappedCall(buffer, method, map);
            }
        }
        List additionalMethods = new ArrayList();
        additionalMethods.addAll(DefaultGroovyMethods.toList(delegate.getClass().getMethods()));
        additionalMethods.addAll(getInheritedMethods(delegate.getClass()));
        for (int i = 0; i < additionalMethods.size(); i++) {
            Method method = (Method) additionalMethods.get(i);
            if (!containsEquivalentMethod(interfaceMethods, method) &&
                    !containsEquivalentMethod(objectMethods, method) &&
                    !containsEquivalentMethod(groovyObjectMethods, method)) {
                selectedMethods.add(method.getName());
                addWrappedCall(buffer, method, map);
            }
        }

        // add leftover methods from the map
        for (Iterator iterator = map.keySet().iterator(); iterator.hasNext();) {
            String methodName = (String) iterator.next();
            if (selectedMethods.contains(methodName)) continue;
            addNewMapCall(buffer, methodName);
        }

        // end class

        buffer.append("}\n").append("new ").append(name);
        buffer.append("(map, delegate)");

        Binding binding = new Binding();
        binding.setVariable("map", map);
        binding.setVariable("delegate", delegate);
        ClassLoader cl = delegate.getClass().getClassLoader();
        GroovyShell shell = new GroovyShell(cl, binding);
        if (debug)
            System.out.println("proxy source:\n------------------\n" + buffer.toString() + "\n------------------");
        try {
            return shell.evaluate(buffer.toString());
        } catch (MultipleCompilationErrorsException err) {
            throw new GroovyRuntimeException("Error creating proxy: " + err.getMessage());
        }
    }

    private static void addWrappedCall(StringBuffer buffer, Method method, Map map) {
        if (map.containsKey(method.getName())) {
            addOverridingMapCall(buffer, method);
        } else {
            Class[] parameterTypes = addMethodPrefix(buffer, method);
            addWrappedMethodBody(buffer, method, parameterTypes);
            addMethodSuffix(buffer);
        }
    }

    private static boolean containsEquivalentMethod(List publicAndProtectedMethods, Method candidate) {
        for (int i = 0; i < publicAndProtectedMethods.size(); i++) {
            Method method = (Method) publicAndProtectedMethods.get(i);
            if (candidate.getName().equals(method.getName()) &&
                    candidate.getReturnType().equals(method.getReturnType()) &&
                    hasMatchingParameterTypes(candidate, method)) {
                return true;
            }
        }
        return false;
    }

    private static boolean hasMatchingParameterTypes(Method method, Method candidate) {
        Class[] candidateParamTypes = candidate.getParameterTypes();
        Class[] methodParamTypes = method.getParameterTypes();
        if (candidateParamTypes.length != methodParamTypes.length) return false;
        for (int i = 0; i < methodParamTypes.length; i++) {
            if (!candidateParamTypes[i].equals(methodParamTypes[i])) return false;
        }
        return true;
    }

    private static List getInheritedMethods(Class baseClass) {
        List protectedMethodList = new ArrayList();
        Class currentClass = baseClass;
        while (currentClass != null) {
            Method[] protectedMethods = currentClass.getDeclaredMethods();
            for (int i = 0; i < protectedMethods.length; i++) {
                Method method = protectedMethods[i];
                if (Modifier.isProtected(method.getModifiers()))
                    protectedMethodList.add(method);
            }
            currentClass = currentClass.getSuperclass();
        }
        return protectedMethodList;
    }

    private static void addNewMapCall(StringBuffer buffer, String methodName) {
        buffer.append("    def ").append(methodName).append("(Object[] args) {\n")
                .append("        this.@closureMap['").append(methodName).append("'] (*args)\n    }\n");
    }

    private static void addOverridingMapCall(StringBuffer buffer, Method method) {
        Class[] parameterTypes = addMethodPrefix(buffer, method);
        addMethodBody(buffer, method, parameterTypes);
        addMethodSuffix(buffer);
    }

    private static void addMapOrDummyCall(Map map, StringBuffer buffer, Method method) {
        Class[] parameterTypes = addMethodPrefix(buffer, method);
        if (map.containsKey(method.getName())) {
            addMethodBody(buffer, method, parameterTypes);
        }
        addMethodSuffix(buffer);
    }

    private static Class[] addMethodPrefix(StringBuffer buffer, Method method) {
        buffer.append("    ").append(getSimpleName(method.getReturnType()))
                .append(" ").append(method.getName()).append("(");
        Class[] parameterTypes = method.getParameterTypes();
        for (int parameterTypeIndex = 0; parameterTypeIndex < parameterTypes.length; parameterTypeIndex++) {
            Class parameter = parameterTypes[parameterTypeIndex];
            if (parameterTypeIndex != 0) {
                buffer.append(", ");
            }
            buffer.append(getSimpleName(parameter)).append(" ")
                    .append("p").append(parameterTypeIndex);
        }
        buffer.append(") { ");
        return parameterTypes;
    }

    private static void addMethodBody(StringBuffer buffer, Method method, Class[] parameterTypes) {
        buffer.append("this.@closureMap['").append(method.getName()).append("'] (");
        for (int j = 0; j < parameterTypes.length; j++) {
            if (j != 0) {
                buffer.append(", ");
            }
            buffer.append("p").append(j);
        }
        buffer.append(")");
    }

    private static void addWrappedMethodBody(StringBuffer buffer, Method method, Class[] parameterTypes) {
        buffer.append("\n        Object[] args = [");
        for (int j = 0; j < parameterTypes.length; j++) {
            if (j != 0) {
                buffer.append(", ");
            }
            buffer.append("p").append(j);
        }
        buffer.append("]\n        ");
        buffer.append("InvokerHelper.invokeMethod(delegate, '").append(method.getName()).append("', args)\n");
    }

    private static void addMethodSuffix(StringBuffer buffer) {
        buffer.append("    }\n");
    }

    /**
     * TODO once we switch to Java 1.5 bt default, use Class#getSimpleName() directly
     *
     * @param c the class of which we want the readable simple name
     * @return the readable simple name
     */
    private static String getSimpleName(Class c) {
        if (c.isArray()) {
            int dimension = 0;
            Class componentClass = c;
            while (componentClass.isArray()) {
                componentClass = componentClass.getComponentType();
                dimension++;
            }
            return componentClass.getName().replaceAll("\\$", "\\.") +
                    DefaultGroovyMethods.multiply("[]", new Integer(dimension));
        } else {
            return c.getName().replaceAll("\\$", "\\.");
        }
    }

    public static String shortName(String name) {
        int index = name.lastIndexOf('.');
        if (index == -1) return name;
        return name.substring(index + 1, name.length());
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy