/*
* Copyright 2003-2013 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 org.codehaus.groovy.runtime;
import groovy.lang.Closure;
import groovy.lang.GeneratedGroovyProxy;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyObject;
import groovy.lang.GroovyRuntimeException;
import groovy.transform.Trait;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.classgen.asm.BytecodeHelper;
import org.codehaus.groovy.control.CompilationUnit;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.ErrorCollector;
import org.codehaus.groovy.control.Phases;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.tools.GroovyClass;
import org.codehaus.groovy.transform.trait.Traits;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
/**
* A proxy generator responsible for mapping a map of closures to a class implementing a list of interfaces. For
* example, the following code:
* <pre>
* abstract class Foo {
* abstract void bar();
* abstract void baz();
* }
* def dyn = [bar: { println 'hello' }, baz: { println 'world'}] as Foo
* </pre>
* will generate a proxy class which extends class <i>Foo</i> and delegates method calls to the provided closures.
*
* The generated proxy implements the {@link GroovyObject} interface.
*
* Additionaly, this proxy generator supports delegation to another object. In that case, if a method is defined
* both in the closure map and the delegate, the version from the map is preferred. This allows overriding methods
* from delegates with ease.
*
* Internally, the proxy generator makes use of ASM to generate bytecode, for improved performance as compared
* to the legacy proxy generation mechanism which made use of string templates.
*
* @author Cedric Champeau
*
* @since 2.0.0
*/
public class ProxyGeneratorAdapter extends ClassVisitor implements Opcodes {
private static final Map<String, Boolean> EMPTY_DELEGATECLOSURE_MAP = Collections.emptyMap();
private static final Set<String> EMPTY_STRING_SET = Collections.emptySet();
private static final String CLOSURES_MAP_FIELD = "$closures$delegate$map";
private static final String DELEGATE_OBJECT_FIELD = "$delegate";
private static List<Method> OBJECT_METHODS = getInheritedMethods(Object.class, new ArrayList<Method>());
private static List<Method> GROOVYOBJECT_METHODS = getInheritedMethods(GroovyObject.class, new ArrayList<Method>());
private static final AtomicLong pxyCounter = new AtomicLong();
private static final Set<String> GROOVYOBJECT_METHOD_NAMESS;
private static final Object[] EMPTY_ARGS = new Object[0];
static {
List<String> names = new ArrayList<String>();
for (Method method : GroovyObject.class.getMethods()) {
names.add(method.getName());
}
GROOVYOBJECT_METHOD_NAMESS = new HashSet<String>(names);
}
private final Class superClass;
private final Class delegateClass;
private final InnerLoader loader;
private final String proxyName;
private final LinkedHashSet<Class> classList;
private final Map<String, Boolean> delegatedClosures;
// if emptyBody == true, then we generate an empty body instead throwing error on unimplemented methods
private final boolean emptyBody;
private final boolean hasWildcard;
private final boolean generateDelegateField;
private final Set<String> objectDelegateMethods;
private final Set<Object> visitedMethods;
// cached class
private final Class cachedClass;
private final Constructor cachedNoArgConstructor;
/**
* Construct a proxy generator. This generator is used when we need to create a proxy object for a class or an
* interface given a map of closures.
*
* @param closureMap the delegates implementations
* @param superClass corresponding to the superclass class visitor
* @param interfaces extra interfaces the proxy should implement
* @param proxyLoader the class loader which should be used to load the generated proxy
* @param delegateClass if not null, generate a delegate field with the corresponding class
* @param emptyBody if set to true, the unimplemented abstract methods will receive an empty body instead of
* throwing an {@link UnsupportedOperationException}.
*/
public ProxyGeneratorAdapter(
final Map<Object, Object> closureMap,
final Class superClass,
final Class[] interfaces,
final ClassLoader proxyLoader,
final boolean emptyBody,
final Class delegateClass) {
super(Opcodes.ASM4, new ClassWriter(0));
this.loader = proxyLoader!=null?createInnerLoader(proxyLoader):findClassLoader(superClass);
this.visitedMethods = new LinkedHashSet<Object>();
this.delegatedClosures = closureMap.isEmpty()? EMPTY_DELEGATECLOSURE_MAP :new HashMap<String, Boolean>();
boolean wildcard = false;
for (Map.Entry<Object, Object> entry : closureMap.entrySet()) {
String name = entry.getKey().toString();
if ("*".equals(name)) {
wildcard = true;
}
this.delegatedClosures.put(name, Boolean.FALSE);
}
this.hasWildcard = wildcard;
Class fixedSuperClass = adjustSuperClass(superClass, interfaces);
// if we have to delegate to another object, generate the appropriate delegate field
// and collect the name of the methods for which delegation is active
this.generateDelegateField = delegateClass!=null;
this.objectDelegateMethods = generateDelegateField?createDelegateMethodList(fixedSuperClass, delegateClass, interfaces):EMPTY_STRING_SET;
this.delegateClass = delegateClass;
// a proxy is supposed to be a concrete class, so it cannot extend an interface.
// If the provided superclass is an interface, then we replace the superclass with Object
// and add this interface to the list of implemented interfaces
this.superClass = fixedSuperClass;
// create the base list of classes which have possible methods to be overloaded
this.classList = new LinkedHashSet<Class>();
this.classList.add(superClass);
if (generateDelegateField) {
classList.add(delegateClass);
Collections.addAll(this.classList, delegateClass.getInterfaces());
}
if (interfaces!=null) {
Collections.addAll(this.classList, interfaces);
}
this.proxyName = proxyName();
this.emptyBody = emptyBody;
// generate bytecode
ClassWriter writer = (ClassWriter) cv;
this.visit(Opcodes.V1_5, ACC_PUBLIC, proxyName, null, null, null);
byte[] b = writer.toByteArray();
// CheckClassAdapter.verify(new ClassReader(b), true, new PrintWriter(System.err));
cachedClass = loader.defineClass(proxyName.replace('/','.'), b);
// cache no-arg constructor
Class[] args = generateDelegateField?new Class[] { Map.class, delegateClass }:new Class[] { Map.class };
Constructor constructor;
try {
constructor = cachedClass.getConstructor(args);
} catch (NoSuchMethodException e) {
constructor = null;
}
cachedNoArgConstructor = constructor;
}
private Class adjustSuperClass(Class superClass, final Class[] interfaces) {
boolean isSuperClassAnInterface = superClass.isInterface();
if (!isSuperClassAnInterface) {
return superClass;
}
Class result = Object.class;
Set<ClassNode> traits = new LinkedHashSet<ClassNode>();
// check if it's a trait
collectTraits(superClass, traits);
if (interfaces!=null) {
for (Class anInterface : interfaces) {
collectTraits(anInterface, traits);
}
}
if (!traits.isEmpty()) {
String name = superClass.getName() + "$TraitAdapter";
ClassNode cn = new ClassNode(name, ACC_PUBLIC | ACC_ABSTRACT, ClassHelper.OBJECT_TYPE, traits.toArray(new ClassNode[traits.size()]), null);
CompilationUnit cu = new CompilationUnit(loader);
CompilerConfiguration config = new CompilerConfiguration();
SourceUnit su = new SourceUnit(name+"wrapper", "", config, loader, new ErrorCollector(config));
cu.addSource(su);
cu.compile(Phases.CONVERSION);
su.getAST().addClass(cn);
cu.compile(Phases.CLASS_GENERATION);
@SuppressWarnings("unchecked")
List<GroovyClass> classes = (List<GroovyClass>) cu.getClasses();
for (GroovyClass groovyClass : classes) {
if (groovyClass.getName().equals(name)) {
return loader.defineClass(name,groovyClass.getBytes());
}
}
}
return result;
}
private void collectTraits(final Class clazz, final Set<ClassNode> traits) {
Annotation annotation = clazz.getAnnotation(Trait.class);
if (annotation!=null) {
ClassNode trait = ClassHelper.make(clazz);
traits.add(trait);
LinkedHashSet<ClassNode> selfTypes = new LinkedHashSet<ClassNode>();
Traits.collectSelfTypes(trait, selfTypes, true, true);
for (ClassNode selfType : selfTypes) {
if (Traits.isTrait(selfType)) {
traits.add(selfType);
}
}
}
}
private static InnerLoader createInnerLoader(final ClassLoader parent) {
return AccessController.doPrivileged(new PrivilegedAction<InnerLoader>() {
public InnerLoader run() {
return new InnerLoader(parent);
}
});
}
private InnerLoader findClassLoader(Class clazz) {
ClassLoader cl = clazz.getClassLoader();
if (cl==null) cl = this.getClass().getClassLoader();
return createInnerLoader(cl);
}
private static Set<String> createDelegateMethodList(Class superClass, Class delegateClass, Class[] interfaces) {
Set<String> selectedMethods = new HashSet<String>();
List<Method> interfaceMethods = new ArrayList<Method>();
List<Method> superClassMethods = new ArrayList<Method>();
Collections.addAll(superClassMethods, superClass.getDeclaredMethods());
if (interfaces!=null) {
for (Class thisInterface : interfaces) {
getInheritedMethods(thisInterface, interfaceMethods);
}
for (Method method : interfaceMethods) {
if (!(containsEquivalentMethod(superClassMethods, method))) {
selectedMethods.add(method.getName()+Type.getMethodDescriptor(method));
}
}
}
List<Method> additionalMethods = getInheritedMethods(delegateClass, new ArrayList<Method>());
for (Method method : additionalMethods) {
if (method.getName().indexOf('$') != -1)
continue;
if (!containsEquivalentMethod(interfaceMethods, method) &&
!containsEquivalentMethod(OBJECT_METHODS, method) &&
!containsEquivalentMethod(GROOVYOBJECT_METHODS, method)) {
selectedMethods.add(method.getName()+Type.getMethodDescriptor(method));
}
}
return selectedMethods;
}
private static List<Method> getInheritedMethods(Class baseClass, List<Method> methods) {
Collections.addAll(methods, baseClass.getMethods());
Class currentClass = baseClass;
while (currentClass != null) {
Method[] protectedMethods = currentClass.getDeclaredMethods();
for (Method method : protectedMethods) {
if (method.getName().indexOf('$') != -1)
continue;
if (Modifier.isProtected(method.getModifiers()) && !containsEquivalentMethod(methods, method))
methods.add(method);
}
currentClass = currentClass.getSuperclass();
}
return methods;
}
private static boolean containsEquivalentMethod(Collection<Method> publicAndProtectedMethods, Method candidate) {
for (Method method : publicAndProtectedMethods) {
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;
}
@Override
public void visit(final int version, final int access, final String name, final String signature, final String superName, final String[] interfaces) {
Set<String> interfacesSet = new LinkedHashSet<String>();
if (interfaces != null) Collections.addAll(interfacesSet, interfaces);
for (Class extraInterface : classList) {
if (extraInterface.isInterface()) interfacesSet.add(BytecodeHelper.getClassInternalName(extraInterface));
}
final boolean addGroovyObjectSupport = !GroovyObject.class.isAssignableFrom(superClass);
if (addGroovyObjectSupport) interfacesSet.add("groovy/lang/GroovyObject");
if (generateDelegateField) {
classList.add(GeneratedGroovyProxy.class);
interfacesSet.add("groovy/lang/GeneratedGroovyProxy");
}
super.visit(V1_5, ACC_PUBLIC, proxyName, signature, BytecodeHelper.getClassInternalName(superClass), interfacesSet.toArray(new String[interfacesSet.size()]));
visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
addDelegateFields();
if (addGroovyObjectSupport) {
createGroovyObjectSupport();
}
for (Class clazz : classList) {
visitClass(clazz);
}
}
/**
* Visit every class/interface this proxy should implement, and generate the appropriate
* bytecode for delegation if available.
* @param clazz an class for which to generate bytecode
*/
private void visitClass(final Class clazz) {
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
Class<?>[] exceptionTypes = method.getExceptionTypes();
String[] exceptions = new String[exceptionTypes.length];
for (int i = 0; i < exceptions.length; i++) {
exceptions[i] = BytecodeHelper.getClassInternalName(exceptionTypes[i]);
}
// for each method defined in the class, generate the appropriate delegation bytecode
visitMethod(method.getModifiers(),
method.getName(),
BytecodeHelper.getMethodDescriptor(method.getReturnType(), method.getParameterTypes()),
null,
exceptions);
}
Constructor[] constructors = clazz.getDeclaredConstructors();
for (Constructor method : constructors) {
Class<?>[] exceptionTypes = method.getExceptionTypes();
String[] exceptions = new String[exceptionTypes.length];
for (int i = 0; i < exceptions.length; i++) {
exceptions[i] = BytecodeHelper.getClassInternalName(exceptionTypes[i]);
}
// for each method defined in the class, generate the appropriate delegation bytecode
visitMethod(method.getModifiers(),
"<init>",
BytecodeHelper.getMethodDescriptor(Void.TYPE, method.getParameterTypes()),
null,
exceptions);
}
for (Class intf : clazz.getInterfaces()) {
visitClass(intf);
}
Class superclass = clazz.getSuperclass();
if (superclass!=null) visitClass(superclass);
// Ultimately, methods can be available in the closure map which are not defined by the superclass
// nor the interfaces
for (Map.Entry<String, Boolean> entry : delegatedClosures.entrySet()) {
Boolean visited = entry.getValue();
if (!visited) {
String name = entry.getKey();
if (!"*".equals(name)) {
// generate a new method
visitMethod(ACC_PUBLIC, name, "([Ljava/lang/Object;)Ljava/lang/Object;", null, null);
}
}
}
}
/**
* When an object doesn't implement the GroovyObject interface, we generate bytecode for the
* {@link GroovyObject} interface methods. Otherwise, the superclass is expected to implement them.
*/
private void createGroovyObjectSupport() {
visitField(ACC_PRIVATE + ACC_TRANSIENT, "metaClass", "Lgroovy/lang/MetaClass;", null, null);
// getMetaClass
MethodVisitor mv;
{
mv = super.visitMethod(ACC_PUBLIC, "getMetaClass", "()Lgroovy/lang/MetaClass;", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, proxyName, "metaClass", "Lgroovy/lang/MetaClass;");
Label l1 = new Label();
mv.visitJumpInsn(IFNONNULL, l1);
Label l2 = new Label();
mv.visitLabel(l2);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false);
mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/InvokerHelper", "getMetaClass", "(Ljava/lang/Class;)Lgroovy/lang/MetaClass;", false);
mv.visitFieldInsn(PUTFIELD, proxyName, "metaClass", "Lgroovy/lang/MetaClass;");
mv.visitLabel(l1);
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, proxyName, "metaClass", "Lgroovy/lang/MetaClass;");
mv.visitInsn(ARETURN);
mv.visitMaxs(2, 1);
mv.visitEnd();
}
// getProperty
{
mv = super.visitMethod(ACC_PUBLIC, "getProperty", "(Ljava/lang/String;)Ljava/lang/Object;", null, null);
mv.visitCode();
mv.visitIntInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKEINTERFACE, "groovy/lang/GroovyObject", "getMetaClass", "()Lgroovy/lang/MetaClass;", true);
mv.visitIntInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEINTERFACE, "groovy/lang/MetaClass", "getProperty", "(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;", true);
mv.visitInsn(ARETURN);
mv.visitMaxs(3, 2);
mv.visitEnd();
}
// setProperty
{
mv = super.visitMethod(ACC_PUBLIC, "setProperty", "(Ljava/lang/String;Ljava/lang/Object;)V", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKEVIRTUAL, proxyName, "getMetaClass", "()Lgroovy/lang/MetaClass;", false);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitVarInsn(ALOAD, 2);
mv.visitMethodInsn(INVOKEINTERFACE, "groovy/lang/MetaClass", "setProperty", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/Object;)V", true);
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitInsn(RETURN);
Label l2 = new Label();
mv.visitLabel(l2);
mv.visitMaxs(4, 3);
mv.visitEnd();
}
// invokeMethod
{
mv = super.visitMethod(ACC_PUBLIC, "invokeMethod", "(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;", null, null);
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKEVIRTUAL, proxyName, "getMetaClass", "()Lgroovy/lang/MetaClass;", false);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitVarInsn(ALOAD, 2);
mv.visitMethodInsn(INVOKEINTERFACE, "groovy/lang/MetaClass", "invokeMethod", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;", true);
mv.visitInsn(ARETURN);
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitMaxs(4, 3);
mv.visitEnd();
}
// setMetaClass
{
mv = super.visitMethod(ACC_PUBLIC, "setMetaClass", "(Lgroovy/lang/MetaClass;)V", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitFieldInsn(PUTFIELD, proxyName, "metaClass", "Lgroovy/lang/MetaClass;");
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitInsn(RETURN);
Label l2 = new Label();
mv.visitLabel(l2);
mv.visitMaxs(2, 2);
mv.visitEnd();
}
}
/**
* Creates delegate fields for every closure defined in the map.
*/
private void addDelegateFields() {
visitField(ACC_PRIVATE + ACC_FINAL, CLOSURES_MAP_FIELD, "Ljava/util/Map;", null, null);
if (generateDelegateField) {
visitField(ACC_PRIVATE+ACC_FINAL, DELEGATE_OBJECT_FIELD, BytecodeHelper.getTypeDescription(delegateClass), null, null);
}
}
private String proxyName() {
String name = delegateClass!=null?delegateClass.getName():superClass.getName();
int index = name.lastIndexOf('.');
if (index == -1) return name + pxyCounter.incrementAndGet() + "_groovyProxy";
return name.substring(index + 1, name.length()) + pxyCounter.incrementAndGet() + "_groovyProxy";
}
private static boolean isImplemented(Class clazz, String name, String desc) {
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
if (method.getName().equals(name)) {
if (desc.equals(Type.getMethodDescriptor(method))) {
return !Modifier.isAbstract(method.getModifiers());
}
}
}
Class parent = clazz.getSuperclass();
if (parent !=null) {
return isImplemented(parent, name, desc);
}
return false;
}
@Override
public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) {
Object key = Arrays.asList(name, desc);
if (visitedMethods.contains(key)) return null;
if (Modifier.isPrivate(access) || Modifier.isNative(access) || ((access&ACC_SYNTHETIC)!=0)) {
// do not generate bytecode for private methods
return null;
}
int accessFlags = access;
visitedMethods.add(key);
if ((objectDelegateMethods.contains(name+desc) || delegatedClosures.containsKey(name) || (!"<init>".equals(name) && hasWildcard)) && !Modifier.isStatic(access) && !Modifier.isFinal(access)) {
if (!GROOVYOBJECT_METHOD_NAMESS.contains(name)) {
if (Modifier.isAbstract(access)) {
// prevents the proxy from being abstract
accessFlags -= ACC_ABSTRACT;
}
if (delegatedClosures.containsKey(name) || (!"<init>".equals(name) && hasWildcard)) {
delegatedClosures.put(name, Boolean.TRUE);
return makeDelegateToClosureCall(name, desc, signature, exceptions, accessFlags);
}
if (generateDelegateField && objectDelegateMethods.contains(name+desc)) {
return makeDelegateCall(name, desc, signature, exceptions, accessFlags);
}
delegatedClosures.put(name, Boolean.TRUE);
return makeDelegateToClosureCall(name, desc, signature, exceptions, accessFlags);
}
} else if ("getProxyTarget".equals(name) && "()Ljava/lang/Object;".equals(desc)) {
return createGetProxyTargetMethod(access, name, desc, signature, exceptions);
} else if ("<init>".equals(name) && (Modifier.isPublic(access) || Modifier.isProtected(access))) {
return createConstructor(access, name, desc, signature, exceptions);
} else if (Modifier.isAbstract(access) && !GROOVYOBJECT_METHOD_NAMESS.contains(name)) {
if (isImplemented(superClass, name, desc)) {
return null;
}
accessFlags -= ACC_ABSTRACT;
MethodVisitor mv = super.visitMethod(accessFlags, name, desc, signature, exceptions);
mv.visitCode();
Type[] args = Type.getArgumentTypes(desc);
if (emptyBody) {
Type returnType = Type.getReturnType(desc);
if (returnType==Type.VOID_TYPE) {
mv.visitInsn(RETURN);
} else {
int loadIns = getLoadInsn(returnType);
switch (loadIns) {
case ILOAD: mv.visitInsn(ICONST_0);
break;
case LLOAD: mv.visitInsn(LCONST_0);
break;
case FLOAD: mv.visitInsn(FCONST_0);
break;
case DLOAD: mv.visitInsn(DCONST_0);
break;
default:
mv.visitInsn(ACONST_NULL);
}
mv.visitInsn(getReturnInsn(returnType));
mv.visitMaxs(2, registerLen(args)+1);
}
} else {
// for compatibility with the legacy proxy generator, we should throw an UnsupportedOperationException
// instead of an AbtractMethodException
mv.visitTypeInsn(NEW, "java/lang/UnsupportedOperationException");
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/UnsupportedOperationException", "<init>", "()V", false);
mv.visitInsn(ATHROW);
mv.visitMaxs(2, registerLen(args)+1);
}
mv.visitEnd();
}
return null;
}
private MethodVisitor createGetProxyTargetMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) {
MethodVisitor mv = super.visitMethod(ACC_PUBLIC | ACC_FINAL, name, desc, signature, exceptions);
mv.visitCode();
mv.visitIntInsn(ALOAD,0);
mv.visitFieldInsn(GETFIELD, proxyName, DELEGATE_OBJECT_FIELD, BytecodeHelper.getTypeDescription(delegateClass));
mv.visitInsn(ARETURN);
mv.visitMaxs(1,1);
mv.visitEnd();
return null;
}
private int registerLen(Type[] args) {
int i = 0;
for (Type arg : args) {
i += registerLen(arg);
}
return i;
}
private int registerLen(final Type arg) {
return arg== Type.DOUBLE_TYPE||arg==Type.LONG_TYPE?2:1;
}
private MethodVisitor createConstructor(final int access, final String name, final String desc, final String signature, final String[] exceptions) {
Type[] args = Type.getArgumentTypes(desc);
StringBuilder newDesc = new StringBuilder("(");
for (Type arg : args) {
newDesc.append(arg.getDescriptor());
}
newDesc.append("Ljava/util/Map;"); // the closure map
if (generateDelegateField) {
newDesc.append(BytecodeHelper.getTypeDescription(delegateClass));
}
newDesc.append(")V");
MethodVisitor mv = super.visitMethod(access, name, newDesc.toString(), signature, exceptions);
mv.visitCode();
initializeDelegateClosure(mv, args);
if (generateDelegateField) {
initializeDelegateObject(mv, args);
}
mv.visitVarInsn(ALOAD, 0);
int idx = 1;
for (Type arg : args) {
if (isPrimitive(arg)) {
mv.visitIntInsn(getLoadInsn(arg), idx);
} else {
mv.visitVarInsn(ALOAD, idx); // load argument i
}
idx += registerLen(arg);
}
mv.visitMethodInsn(INVOKESPECIAL, BytecodeHelper.getClassInternalName(superClass), "<init>", desc, false);
mv.visitInsn(RETURN);
int max = idx + 1 + (generateDelegateField?1:0);
mv.visitMaxs(max, max);
mv.visitEnd();
return null;
}
private void initializeDelegateClosure(final MethodVisitor mv, Type[] args) {
int idx = 1 + getTypeArgsRegisterLength(args);
mv.visitIntInsn(ALOAD, 0); // this
mv.visitIntInsn(ALOAD, idx); // constructor arg n is the closure map
mv.visitFieldInsn(PUTFIELD, proxyName, CLOSURES_MAP_FIELD, "Ljava/util/Map;");
}
private void initializeDelegateObject(final MethodVisitor mv, Type[] args) {
int idx = 2 + getTypeArgsRegisterLength(args);
mv.visitIntInsn(ALOAD, 0); // this
mv.visitIntInsn(ALOAD, idx); // constructor arg n is the closure map
mv.visitFieldInsn(PUTFIELD, proxyName, DELEGATE_OBJECT_FIELD, BytecodeHelper.getTypeDescription(delegateClass));
}
private int getTypeArgsRegisterLength(Type[] args) {
int length = 0;
for (Type type : args) { length += registerLen(type); }
return length;
}
/**
* Generate a call to the delegate object.
*/
protected MethodVisitor makeDelegateCall(final String name, final String desc, final String signature, final String[] exceptions, final int accessFlags) {
MethodVisitor mv = super.visitMethod(accessFlags, name, desc, signature, exceptions);
mv.visitVarInsn(ALOAD, 0); // load this
mv.visitFieldInsn(GETFIELD, proxyName, DELEGATE_OBJECT_FIELD, BytecodeHelper.getTypeDescription(delegateClass)); // load delegate
// using InvokerHelper to allow potential intercepted calls
int size;
mv.visitLdcInsn(name); // method name
Type[] args = Type.getArgumentTypes(desc);
BytecodeHelper.pushConstant(mv, args.length);
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
size = 6;
int idx = 1;
for (int i = 0; i < args.length; i++) {
Type arg = args[i];
mv.visitInsn(DUP);
BytecodeHelper.pushConstant(mv, i);
// primitive types must be boxed
if (isPrimitive(arg)) {
mv.visitIntInsn(getLoadInsn(arg), idx);
String wrappedType = getWrappedClassDescriptor(arg);
mv.visitMethodInsn(INVOKESTATIC, wrappedType, "valueOf", "(" + arg.getDescriptor() + ")L" + wrappedType + ";", false);
} else {
mv.visitVarInsn(ALOAD, idx); // load argument i
}
size = Math.max(size, 5+registerLen(arg));
idx += registerLen(arg);
mv.visitInsn(AASTORE); // store value into array
}
mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/InvokerHelper", "invokeMethod", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object;", false);
unwrapResult(mv, desc);
mv.visitMaxs(size, registerLen(args) + 1);
return mv;
}
protected MethodVisitor makeDelegateToClosureCall(final String name, final String desc, final String signature, final String[] exceptions, final int accessFlags) {
MethodVisitor mv = super.visitMethod(accessFlags, name, desc, signature, exceptions);
// TraceMethodVisitor tmv = new TraceMethodVisitor(mv);
// mv = tmv;
mv.visitCode();
int stackSize = 0;
// method body should be:
// this.$delegate$closure$methodName.call(new Object[] { method arguments })
Type[] args = Type.getArgumentTypes(desc);
int arrayStore = args.length+1;
BytecodeHelper.pushConstant(mv, args.length);
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); // stack size = 1
stackSize = 1;
int idx = 1;
for (int i = 0; i < args.length; i++) {
Type arg = args[i];
mv.visitInsn(DUP); // stack size = 2
BytecodeHelper.pushConstant(mv, i); // array index, stack size = 3
stackSize = 3;
// primitive types must be boxed
if (isPrimitive(arg)) {
mv.visitIntInsn(getLoadInsn(arg), idx);
String wrappedType = getWrappedClassDescriptor(arg);
mv.visitMethodInsn(INVOKESTATIC, wrappedType, "valueOf", "(" + arg.getDescriptor() + ")L" + wrappedType + ";", false);
} else {
mv.visitVarInsn(ALOAD, idx); // load argument i
}
idx += registerLen(arg);
stackSize = Math.max(4, 3+registerLen(arg));
mv.visitInsn(AASTORE); // store value into array
}
mv.visitVarInsn(ASTORE, arrayStore); // store array
int arrayIndex = arrayStore;
mv.visitVarInsn(ALOAD, 0); // load this
mv.visitFieldInsn(GETFIELD, proxyName, CLOSURES_MAP_FIELD, "Ljava/util/Map;"); // load closure map
mv.visitLdcInsn(name); // load method name
mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "get", "(Ljava/lang/Object;)Ljava/lang/Object;", true);
arrayStore++;
mv.visitVarInsn(ASTORE, arrayStore);
// if null, test if wildcard exists
Label notNull = new Label();
mv.visitIntInsn(ALOAD, arrayStore);
mv.visitJumpInsn(IFNONNULL, notNull);
mv.visitVarInsn(ALOAD, 0); // load this
mv.visitFieldInsn(GETFIELD, proxyName, CLOSURES_MAP_FIELD, "Ljava/util/Map;"); // load closure map
mv.visitLdcInsn("*"); // load wildcard
mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "get", "(Ljava/lang/Object;)Ljava/lang/Object;", true);
mv.visitVarInsn(ASTORE, arrayStore);
mv.visitLabel(notNull);
mv.visitVarInsn(ALOAD, arrayStore);
mv.visitMethodInsn(INVOKESTATIC, BytecodeHelper.getClassInternalName(this.getClass()), "ensureClosure", "(Ljava/lang/Object;)Lgroovy/lang/Closure;", false);
mv.visitVarInsn(ALOAD, arrayIndex); // load argument array
stackSize++;
mv.visitMethodInsn(INVOKEVIRTUAL, "groovy/lang/Closure", "call", "([Ljava/lang/Object;)Ljava/lang/Object;", false); // call closure
unwrapResult(mv, desc);
mv.visitMaxs(stackSize, arrayStore+1);
mv.visitEnd();
// System.out.println("tmv.getText() = " + tmv.getText());
return null;
}
private void unwrapResult(final MethodVisitor mv, final String desc) {
Type returnType = Type.getReturnType(desc);
if (returnType==Type.VOID_TYPE) {
mv.visitInsn(POP);
mv.visitInsn(RETURN);
} else {
if (isPrimitive(returnType)) {
BytecodeHelper.unbox(mv, ClassHelper.make(returnType.getClassName()));
} else {
mv.visitTypeInsn(CHECKCAST, returnType.getInternalName());
}
mv.visitInsn(getReturnInsn(returnType));
}
}
@SuppressWarnings("unchecked")
public GroovyObject proxy(Map<Object,Object> map, Object... constructorArgs) {
if (constructorArgs==null && cachedNoArgConstructor!=null) {
// if there isn't any argument, we can make invocation faster using the cached constructor
try {
return (GroovyObject) cachedNoArgConstructor.newInstance(map);
} catch (InstantiationException e) {
throw new GroovyRuntimeException(e);
} catch (IllegalAccessException e) {
throw new GroovyRuntimeException(e);
} catch (InvocationTargetException e) {
throw new GroovyRuntimeException(e);
}
}
if (constructorArgs==null) constructorArgs= EMPTY_ARGS;
Object[] values = new Object[constructorArgs.length + 1];
System.arraycopy(constructorArgs, 0, values, 0, constructorArgs.length);
values[values.length-1] = map;
return DefaultGroovyMethods.<GroovyObject>newInstance(cachedClass, values);
}
@SuppressWarnings("unchecked")
public GroovyObject delegatingProxy(Object delegate,Map<Object,Object> map, Object... constructorArgs) {
if (constructorArgs==null && cachedNoArgConstructor!=null) {
// if there isn't any argument, we can make invocation faster using the cached constructor
try {
return (GroovyObject) cachedNoArgConstructor.newInstance(map, delegate);
} catch (InstantiationException e) {
throw new GroovyRuntimeException(e);
} catch (IllegalAccessException e) {
throw new GroovyRuntimeException(e);
} catch (InvocationTargetException e) {
throw new GroovyRuntimeException(e);
}
}
if (constructorArgs==null) constructorArgs= EMPTY_ARGS;
Object[] values = new Object[constructorArgs.length + 2];
System.arraycopy(constructorArgs, 0, values, 0, constructorArgs.length);
values[values.length-2] = map;
values[values.length-1] = delegate;
return DefaultGroovyMethods.<GroovyObject>newInstance(cachedClass, values);
}
/**
* Ensures that the provided object is wrapped into a closure if it's not
* a closure.
* Do not trust IDEs, this method is used in bytecode.
*/
@SuppressWarnings("unchecked")
public static Closure ensureClosure(Object o) {
if (o==null) throw new UnsupportedOperationException();
if (o instanceof Closure) return (Closure) o;
return new ReturnValueWrappingClosure(o);
}
private static int getLoadInsn(final Type type) {
if (type == Type.BOOLEAN_TYPE) return ILOAD;
if (type == Type.BYTE_TYPE) return ILOAD;
if (type == Type.CHAR_TYPE) return ILOAD;
if (type == Type.DOUBLE_TYPE) return DLOAD;
if (type == Type.FLOAT_TYPE) return FLOAD;
if (type == Type.INT_TYPE) return ILOAD;
if (type == Type.LONG_TYPE) return LLOAD;
if (type == Type.SHORT_TYPE) return ILOAD;
return ALOAD;
}
private static int getReturnInsn(final Type type) {
if (type == Type.BOOLEAN_TYPE) return IRETURN;
if (type == Type.BYTE_TYPE) return IRETURN;
if (type == Type.CHAR_TYPE) return IRETURN;
if (type == Type.DOUBLE_TYPE) return DRETURN;
if (type == Type.FLOAT_TYPE) return FRETURN;
if (type == Type.INT_TYPE) return IRETURN;
if (type == Type.LONG_TYPE) return LRETURN;
if (type == Type.SHORT_TYPE) return IRETURN;
return ARETURN;
}
private boolean isPrimitive(final Type arg) {
return arg == Type.BOOLEAN_TYPE
|| arg == Type.BYTE_TYPE
|| arg == Type.CHAR_TYPE
|| arg == Type.DOUBLE_TYPE
|| arg == Type.FLOAT_TYPE
|| arg == Type.INT_TYPE
|| arg == Type.LONG_TYPE
|| arg == Type.SHORT_TYPE;
}
private String getWrappedClassDescriptor(Type type) {
if (type == Type.BOOLEAN_TYPE) return "java/lang/Boolean";
if (type == Type.BYTE_TYPE) return "java/lang/Byte";
if (type == Type.CHAR_TYPE) return "java/lang/Character";
if (type == Type.DOUBLE_TYPE) return "java/lang/Double";
if (type == Type.FLOAT_TYPE) return "java/lang/Float";
if (type == Type.INT_TYPE) return "java/lang/Integer";
if (type == Type.LONG_TYPE) return "java/lang/Long";
if (type == Type.SHORT_TYPE) return "java/lang/Short";
throw new IllegalArgumentException("Unexpected type class [" + type + "]");
}
private static class InnerLoader extends GroovyClassLoader {
protected InnerLoader(final ClassLoader parent) {
super(parent);
}
public Class defineClass(String name, byte[] data) {
return super.defineClass(name, data, 0, data.length);
}
}
private static class ReturnValueWrappingClosure<V> extends Closure<V>{
private final V value;
public ReturnValueWrappingClosure(V returnValue) {
super(null);
value = returnValue;
}
@Override
public V call(final Object... args) {
return value;
}
}
}