[PROXY-22] Provide an ASM-based ProxyFactory implementation
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/proxy/branches/version-2.0-work@1520886 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/asm4/pom.xml b/asm4/pom.xml
new file mode 100644
index 0000000..f0dff48
--- /dev/null
+++ b/asm4/pom.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You 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.
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>commons-proxy2-parent</artifactId>
+ <groupId>org.apache.commons</groupId>
+ <version>2.0-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>commons-proxy2-asm4</artifactId>
+ <name>Commons Proxy ASM4 Proxies Module</name>
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>commons-proxy2</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.xbean</groupId>
+ <artifactId>xbean-asm4-shaded</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>commons-proxy2</artifactId>
+ <version>${project.version}</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/asm4/src/main/java/org/apache/commons/proxy2/asm4/ASM4ProxyFactory.java b/asm4/src/main/java/org/apache/commons/proxy2/asm4/ASM4ProxyFactory.java
new file mode 100644
index 0000000..1fcd8dc
--- /dev/null
+++ b/asm4/src/main/java/org/apache/commons/proxy2/asm4/ASM4ProxyFactory.java
@@ -0,0 +1,1084 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.commons.proxy2.asm4;
+
+import org.apache.commons.proxy2.Interceptor;
+import org.apache.commons.proxy2.Invocation;
+import org.apache.commons.proxy2.Invoker;
+import org.apache.commons.proxy2.ObjectProvider;
+import org.apache.commons.proxy2.ProxyUtils;
+import org.apache.commons.proxy2.exception.ProxyFactoryException;
+import org.apache.commons.proxy2.impl.AbstractProxyClassGenerator;
+import org.apache.commons.proxy2.impl.AbstractSubclassingProxyFactory;
+import org.apache.xbean.asm4.ClassWriter;
+import org.apache.xbean.asm4.Label;
+import org.apache.xbean.asm4.MethodVisitor;
+import org.apache.xbean.asm4.Opcodes;
+import org.apache.xbean.asm4.Type;
+
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Proxy;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.ProtectionDomain;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class ASM4ProxyFactory extends AbstractSubclassingProxyFactory
+{
+ @Override
+ public <T> T createDelegatorProxy(final ClassLoader classLoader, final ObjectProvider<?> delegateProvider, final Class<?>... proxyClasses)
+ {
+ return ProxyGenerator.newProxyInstance(classLoader, new DelegatorInvocationHandler(delegateProvider), proxyClasses);
+ }
+
+ @Override
+ public <T> T createInterceptorProxy(final ClassLoader classLoader, final Object target, final Interceptor interceptor, final Class<?>... proxyClasses)
+ {
+ return ProxyGenerator.newProxyInstance(classLoader, new InterceptorInvocationHandler(target, interceptor), proxyClasses);
+ }
+
+ @Override
+ public <T> T createInvokerProxy(final ClassLoader classLoader, final Invoker invoker, final Class<?>... proxyClasses)
+ {
+ return ProxyGenerator.newProxyInstance(classLoader, new InvokerInvocationHandler(invoker), proxyClasses);
+ }
+
+ private static class ProxyGenerator extends AbstractProxyClassGenerator implements Opcodes
+ {
+ private static final String HANDLER_NAME = "__handler";
+ private static final ReentrantLock LOCK = new ReentrantLock();
+
+ public static <T> T newProxyInstance(final ClassLoader classLoader, final InvocationHandler handler, final Class<?>... classes) throws ProxyFactoryException
+ {
+ try
+ {
+ final Class<?> superclass = getSuperclass(classes);
+ if (superclass == Object.class)
+ {
+ @SuppressWarnings("unchecked")
+ final T result = (T) Proxy.newProxyInstance(classLoader, classes, handler);
+ return result;
+ }
+ final Class<?> proxyClass = createProxy(superclass, classLoader, getImplementationMethods(classes), toInterfaces(classes));
+ return constructProxy(proxyClass, handler);
+ }
+ catch (final Exception e)
+ {
+ throw new ProxyFactoryException(e);
+ }
+ }
+
+ public static <T> T constructProxy(final Class<?> clazz, final java.lang.reflect.InvocationHandler handler) throws IllegalStateException
+ {
+ final Object instance = Unsafe.allocateInstance(clazz);
+ Unsafe.setValue(getDeclaredField(clazz, HANDLER_NAME), instance, handler);
+ @SuppressWarnings("unchecked")
+ final T result = (T) instance;
+ return result;
+ }
+
+ private static Field getDeclaredField(final Class<?> clazz, final String fieldName)
+ {
+ try
+ {
+ return clazz.getDeclaredField(fieldName);
+ }
+ catch (NoSuchFieldException e)
+ {
+ final String message = String.format("Proxy class does not contain expected field \"%s\": %s", fieldName, clazz.getName());
+ throw new IllegalStateException(message, e);
+ }
+ }
+
+ public static Class<?> createProxy(final Class<?> classToProxy, final ClassLoader loader, final String proxyName, final Method[] methods, final Class<?>... interfaces)
+ {
+ final String classFileName = proxyName.replace('.', '/');
+
+ try
+ {
+ return loader.loadClass(proxyName);
+ }
+ catch (Exception e)
+ {
+ // no-op
+ }
+
+ LOCK.lock();
+ try
+ {
+ try
+ { // Try it again, another thread may have beaten this one...
+ return loader.loadClass(proxyName);
+ }
+ catch (Exception e)
+ {
+ // no-op
+ }
+
+ final byte[] proxyBytes = generateProxy(classToProxy, classFileName, methods, interfaces);
+ return Unsafe.defineClass(loader, classToProxy, proxyName, proxyBytes);
+ }
+ catch (final Exception e)
+ {
+ throw new ProxyFactoryException(e);
+ }
+ finally {
+ LOCK.unlock();
+ }
+ }
+
+ public static Class<?> createProxy(final Class<?> classToProxy, final ClassLoader cl, final Method[] methods, final Class<?>... interfaces)
+ {
+ return createProxy(classToProxy, cl, proxyName(classToProxy.getName()), methods, interfaces);
+ }
+
+ private static String proxyName(final String name)
+ {
+ final String proxy = name + "$$CommonsProxy";
+ if (!proxy.startsWith("java"))
+ {
+ return proxy;
+ }
+ return "org.apache.commons.proxy2.generated." + proxy;
+ }
+
+ public static byte[] generateProxy(final Class<?> classToProxy, final String proxyName, final Method[] methods, final Class<?>... interfaces) throws ProxyFactoryException
+ {
+ final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
+
+ final String proxyClassFileName = proxyName.replace('.', '/');
+ final String classFileName = classToProxy.getName().replace('.', '/');
+
+ // push class signature
+ final String[] interfaceNames = new String[interfaces.length];
+ for (int i = 0; i < interfaces.length; i++)
+ {
+ final Class<?> anInterface = interfaces[i];
+ interfaceNames[i] = anInterface.getName().replace('.', '/');
+ }
+
+ cw.visit(V1_5, ACC_PUBLIC + ACC_SUPER, proxyClassFileName, null, classFileName, interfaceNames);
+ cw.visitSource(classFileName + ".java", null);
+
+ // push InvocationHandler fields
+ cw.visitField(ACC_FINAL + ACC_PRIVATE, HANDLER_NAME, "Ljava/lang/reflect/InvocationHandler;", null, null).visitEnd();
+
+ final Map<String, List<Method>> methodMap = new HashMap<String, List<Method>>();
+
+ findMethods(classToProxy, methodMap);
+
+ for (final Class<?> anInterface : interfaces)
+ {
+ findMethods(anInterface, methodMap);
+ }
+
+ for (final Method method : methods)
+ {
+ processMethod(cw, method, proxyClassFileName, HANDLER_NAME);
+ }
+
+ return cw.toByteArray();
+ }
+
+ private static void findMethods(Class<?> clazz, final Map<String, List<Method>> methodMap)
+ {
+ while (clazz != null) {
+ for (final Method method : clazz.getDeclaredMethods())
+ {
+ final int modifiers = method.getModifiers();
+
+ if (Modifier.isFinal(modifiers) || Modifier.isStatic(modifiers))
+ {
+ continue;
+ }
+
+ List<Method> methods = methodMap.get(method.getName());
+ if (methods == null)
+ {
+ methods = new ArrayList<Method>();
+ methods.add(method);
+ methodMap.put(method.getName(), methods);
+ }
+ else if (!isOverridden(methods, method))
+ {
+ methods.add(method);
+ }
+ }
+
+ clazz = clazz.getSuperclass();
+ }
+ }
+
+ private static boolean isOverridden(final List<Method> methods, final Method method)
+ {
+ for (final Method m : methods)
+ {
+ if (Arrays.equals(m.getParameterTypes(), method.getParameterTypes()))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static void processMethod(final ClassWriter cw, final Method method, final String proxyName, final String handlerName) throws ProxyFactoryException {
+ if ("<init>".equals(method.getName()))
+ {
+ return;
+ }
+
+ final Class<?> returnType = method.getReturnType();
+ final Class<?>[] parameterTypes = method.getParameterTypes();
+ final Class<?>[] exceptionTypes = method.getExceptionTypes();
+ final int modifiers = method.getModifiers();
+
+ // push the method definition
+ int modifier = 0;
+ if (Modifier.isPublic(modifiers))
+ {
+ modifier = ACC_PUBLIC;
+ }
+ else if (Modifier.isProtected(modifiers))
+ {
+ modifier = ACC_PROTECTED;
+ }
+
+ final MethodVisitor mv = cw.visitMethod(modifier, method.getName(), getMethodSignatureAsString(returnType, parameterTypes), null, null);
+ mv.visitCode();
+
+ // push try/catch block, to catch declared exceptions, and to catch java.lang.Throwable
+ final Label l0 = new Label();
+ final Label l1 = new Label();
+ final Label l2 = new Label();
+
+ if (exceptionTypes.length > 0)
+ {
+ mv.visitTryCatchBlock(l0, l1, l2, "java/lang/reflect/InvocationTargetException");
+ }
+
+ // push try code
+ mv.visitLabel(l0);
+ final String classNameToOverride = method.getDeclaringClass().getName().replace('.', '/');
+ mv.visitLdcInsn(Type.getType("L" + classNameToOverride + ";"));
+
+ // the following code generates the bytecode for this line of Java:
+ // Method method = <proxy>.class.getMethod("add", new Class[] { <array of function argument classes> });
+
+ // get the method name to invoke, and push to stack
+ mv.visitLdcInsn(method.getName());
+
+ // create the Class[]
+ createArrayDefinition(mv, parameterTypes.length, Class.class);
+
+ int length = 1;
+
+ // push parameters into array
+ for (int i = 0; i < parameterTypes.length; i++)
+ {
+ // keep copy of array on stack
+ mv.visitInsn(DUP);
+
+ final Class<?> parameterType = parameterTypes[i];
+
+ // push number onto stack
+ pushIntOntoStack(mv, i);
+
+ if (parameterType.isPrimitive())
+ {
+ final String wrapperType = getWrapperType(parameterType);
+ mv.visitFieldInsn(GETSTATIC, wrapperType, "TYPE", "Ljava/lang/Class;");
+ } else {
+ mv.visitLdcInsn(Type.getType(getAsmTypeAsString(parameterType, true)));
+ }
+
+ mv.visitInsn(AASTORE);
+
+ if (Long.TYPE.equals(parameterType) || Double.TYPE.equals(parameterType))
+ {
+ length += 2;
+ } else {
+ length++;
+ }
+ }
+
+ // invoke getMethod() with the method name and the array of types
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getDeclaredMethod", "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;");
+
+ // store the returned method for later
+ mv.visitVarInsn(ASTORE, length);
+
+ // the following code generates bytecode equivalent to:
+ // return ((<returntype>) invocationHandler.invoke(this, method, new Object[] { <function arguments }))[.<primitive>Value()];
+
+ final Label l4 = new Label();
+ mv.visitLabel(l4);
+ mv.visitVarInsn(ALOAD, 0);
+
+ // get the invocationHandler field from this class
+ mv.visitFieldInsn(GETFIELD, proxyName, handlerName, "Ljava/lang/reflect/InvocationHandler;");
+
+ // we want to pass "this" in as the first parameter
+ mv.visitVarInsn(ALOAD, 0);
+
+ // and the method we fetched earlier
+ mv.visitVarInsn(ALOAD, length);
+
+ // need to construct the array of objects passed in
+
+ // create the Object[]
+ createArrayDefinition(mv, parameterTypes.length, Object.class);
+
+ int index = 1;
+ // push parameters into array
+ for (int i = 0; i < parameterTypes.length; i++)
+ {
+ // keep copy of array on stack
+ mv.visitInsn(DUP);
+
+ final Class<?> parameterType = parameterTypes[i];
+
+ // push number onto stack
+ pushIntOntoStack(mv, i);
+
+ if (parameterType.isPrimitive())
+ {
+ final String wrapperType = getWrapperType(parameterType);
+ mv.visitVarInsn(getVarInsn(parameterType), index);
+
+ mv.visitMethodInsn(INVOKESTATIC, wrapperType, "valueOf", "(" + getPrimitiveLetter(parameterType) + ")L" + wrapperType + ";");
+ mv.visitInsn(AASTORE);
+
+ if (Long.TYPE.equals(parameterType) || Double.TYPE.equals(parameterType))
+ {
+ index += 2;
+ }
+ else
+ {
+ index++;
+ }
+ }
+ else
+ {
+ mv.visitVarInsn(ALOAD, index);
+ mv.visitInsn(AASTORE);
+ index++;
+ }
+ }
+
+ // invoke the invocationHandler
+ mv.visitMethodInsn(INVOKEINTERFACE, "java/lang/reflect/InvocationHandler", "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;");
+
+ // cast the result
+ mv.visitTypeInsn(CHECKCAST, getCastType(returnType));
+
+ if (returnType.isPrimitive() && (!Void.TYPE.equals(returnType)))
+ {
+ // get the primitive value
+ mv.visitMethodInsn(INVOKEVIRTUAL, getWrapperType(returnType), getPrimitiveMethod(returnType), "()" + getPrimitiveLetter(returnType));
+ }
+
+ // push return
+ mv.visitLabel(l1);
+ if (!Void.TYPE.equals(returnType))
+ {
+ mv.visitInsn(getReturnInsn(returnType));
+ }
+ else
+ {
+ mv.visitInsn(POP);
+ mv.visitInsn(RETURN);
+ }
+
+ // catch InvocationTargetException
+ if (exceptionTypes.length > 0)
+ {
+ mv.visitLabel(l2);
+ mv.visitVarInsn(ASTORE, length);
+
+ final Label l5 = new Label();
+ mv.visitLabel(l5);
+
+ for (int i = 0; i < exceptionTypes.length; i++)
+ {
+ final Class<?> exceptionType = exceptionTypes[i];
+
+ mv.visitLdcInsn(Type.getType("L" + exceptionType.getName().replace('.', '/') + ";"));
+ mv.visitVarInsn(ALOAD, length);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause", "()Ljava/lang/Throwable;");
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;");
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "equals", "(Ljava/lang/Object;)Z");
+
+ final Label l6 = new Label();
+ mv.visitJumpInsn(IFEQ, l6);
+
+ final Label l7 = new Label();
+ mv.visitLabel(l7);
+
+ mv.visitVarInsn(ALOAD, length);
+ mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause", "()Ljava/lang/Throwable;");
+ mv.visitTypeInsn(CHECKCAST, exceptionType.getName().replace('.', '/'));
+ mv.visitInsn(ATHROW);
+ mv.visitLabel(l6);
+
+ if (i == (exceptionTypes.length - 1))
+ {
+ mv.visitTypeInsn(NEW, "java/lang/reflect/UndeclaredThrowableException");
+ mv.visitInsn(DUP);
+ mv.visitVarInsn(ALOAD, length);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/reflect/UndeclaredThrowableException", "<init>", "(Ljava/lang/Throwable;)V");
+ mv.visitInsn(ATHROW);
+ }
+ }
+ }
+
+ // finish this method
+ mv.visitMaxs(0, 0);
+ mv.visitEnd();
+ }
+
+ private static int getReturnInsn(final Class<?> type)
+ {
+ if (type.isPrimitive())
+ {
+ if (Integer.TYPE.equals(type))
+ {
+ return IRETURN;
+ }
+ if (Boolean.TYPE.equals(type))
+ {
+ return IRETURN;
+ }
+ if (Character.TYPE.equals(type))
+ {
+ return IRETURN;
+ }
+ if (Byte.TYPE.equals(type))
+ {
+ return IRETURN;
+ }
+ if (Short.TYPE.equals(type))
+ {
+ return IRETURN;
+ }
+ if (Float.TYPE.equals(type))
+ {
+ return FRETURN;
+ }
+ if (Long.TYPE.equals(type))
+ {
+ return LRETURN;
+ }
+ if (Double.TYPE.equals(type))
+ {
+ return DRETURN;
+ }
+ }
+
+ return ARETURN;
+ }
+
+ private static int getVarInsn(final Class<?> type)
+ {
+ if (type.isPrimitive())
+ {
+ if (Integer.TYPE.equals(type))
+ {
+ return ILOAD;
+ }
+ if (Boolean.TYPE.equals(type))
+ {
+ return ILOAD;
+ }
+ if (Character.TYPE.equals(type))
+ {
+ return ILOAD;
+ }
+ if (Byte.TYPE.equals(type))
+ {
+ return ILOAD;
+ }
+ if (Short.TYPE.equals(type))
+ {
+ return ILOAD;
+ }
+ if (Float.TYPE.equals(type))
+ {
+ return FLOAD;
+ }
+ if (Long.TYPE.equals(type))
+ {
+ return LLOAD;
+ }
+ if (Double.TYPE.equals(type))
+ {
+ return DLOAD;
+ }
+ }
+
+ throw new IllegalStateException("Type: " + type.getCanonicalName() + " is not a primitive type");
+ }
+
+ private static String getPrimitiveMethod(final Class<?> type)
+ {
+ if (Integer.TYPE.equals(type))
+ {
+ return "intValue";
+ }
+ if (Boolean.TYPE.equals(type))
+ {
+ return "booleanValue";
+ }
+ if (Character.TYPE.equals(type))
+ {
+ return "charValue";
+ }
+ if (Byte.TYPE.equals(type))
+ {
+ return "byteValue";
+ }
+ if (Short.TYPE.equals(type))
+ {
+ return "shortValue";
+ }
+ if (Float.TYPE.equals(type))
+ {
+ return "floatValue";
+ }
+ if (Long.TYPE.equals(type))
+ {
+ return "longValue";
+ }
+ if (Double.TYPE.equals(type))
+ {
+ return "doubleValue";
+ }
+
+ throw new IllegalStateException("Type: " + type.getCanonicalName() + " is not a primitive type");
+ }
+
+ static String getCastType(final Class<?> returnType)
+ {
+ if (returnType.isPrimitive())
+ {
+ return getWrapperType(returnType);
+ }
+ return getAsmTypeAsString(returnType, false);
+ }
+
+ private static String getWrapperType(final Class<?> type)
+ {
+ if (Integer.TYPE.equals(type))
+ {
+ return Integer.class.getCanonicalName().replace('.', '/');
+ }
+ if (Boolean.TYPE.equals(type))
+ {
+ return Boolean.class.getCanonicalName().replace('.', '/');
+ }
+ if (Character.TYPE.equals(type))
+ {
+ return Character.class.getCanonicalName().replace('.', '/');
+ }
+ if (Byte.TYPE.equals(type))
+ {
+ return Byte.class.getCanonicalName().replace('.', '/');
+ }
+ if (Short.TYPE.equals(type))
+ {
+ return Short.class.getCanonicalName().replace('.', '/');
+ }
+ if (Float.TYPE.equals(type))
+ {
+ return Float.class.getCanonicalName().replace('.', '/');
+ }
+ if (Long.TYPE.equals(type))
+ {
+ return Long.class.getCanonicalName().replace('.', '/');
+ }
+ if (Double.TYPE.equals(type))
+ {
+ return Double.class.getCanonicalName().replace('.', '/');
+ }
+ if (Void.TYPE.equals(type))
+ {
+ return Void.class.getCanonicalName().replace('.', '/');
+ }
+
+ throw new IllegalStateException("Type: " + type.getCanonicalName() + " is not a primitive type");
+ }
+
+ private static void pushIntOntoStack(final MethodVisitor mv, final int i)
+ {
+ if (i == 0)
+ {
+ mv.visitInsn(ICONST_0);
+ }
+ else if (i == 1)
+ {
+ mv.visitInsn(ICONST_1);
+ }
+ else if (i == 2)
+ {
+ mv.visitInsn(ICONST_2);
+ }
+ else if (i == 3)
+ {
+ mv.visitInsn(ICONST_3);
+ }
+ else if (i == 4)
+ {
+ mv.visitInsn(ICONST_4);
+ }
+ else if (i == 5)
+ {
+ mv.visitInsn(ICONST_5);
+ }
+ else if (i > 5 && i <= 255)
+ {
+ mv.visitIntInsn(BIPUSH, i);
+ }
+ else
+ {
+ mv.visitIntInsn(SIPUSH, i);
+ }
+ }
+
+ private static void createArrayDefinition(final MethodVisitor mv, final int size, final Class<?> type) throws ProxyFactoryException
+ {
+ // create a new array of java.lang.class (2)
+ if (size < 0)
+ {
+ throw new ProxyFactoryException("Array size cannot be less than zero");
+ }
+
+ pushIntOntoStack(mv, size);
+ mv.visitTypeInsn(ANEWARRAY, type.getCanonicalName().replace('.', '/'));
+ }
+
+ static String getMethodSignatureAsString(final Class<?> returnType, final Class<?>[] parameterTypes)
+ {
+ final StringBuilder builder = new StringBuilder("(");
+ for (final Class<?> parameterType : parameterTypes) {
+ builder.append(getAsmTypeAsString(parameterType, true));
+ }
+
+ builder.append(")");
+ builder.append(getAsmTypeAsString(returnType, true));
+
+ return builder.toString();
+ }
+
+ /**
+ * Returns the single letter that matches the given primitive in bytecode instructions
+ */
+ private static String getPrimitiveLetter(final Class<?> type)
+ {
+ if (Integer.TYPE.equals(type))
+ {
+ return "I";
+ }
+ if (Void.TYPE.equals(type))
+ {
+ return "V";
+ }
+ if (Boolean.TYPE.equals(type))
+ {
+ return "Z";
+ }
+ if (Character.TYPE.equals(type))
+ {
+ return "C";
+ }
+ if (Byte.TYPE.equals(type))
+ {
+ return "B";
+ }
+ if (Short.TYPE.equals(type))
+ {
+ return "S";
+ }
+ if (Float.TYPE.equals(type))
+ {
+ return "F";
+ }
+ if (Long.TYPE.equals(type))
+ {
+ return "J";
+ }
+ if (Double.TYPE.equals(type))
+ {
+ return "D";
+ }
+
+ throw new IllegalStateException("Type: " + type.getCanonicalName() + " is not a primitive type");
+ }
+
+ public static String getAsmTypeAsString(final Class<?> parameterType, final boolean wrap)
+ {
+ if (parameterType.isArray())
+ {
+ if (parameterType.getComponentType().isPrimitive())
+ {
+ final Class<?> componentType = parameterType.getComponentType();
+ return "[" + getPrimitiveLetter(componentType);
+ }
+ return "[" + getAsmTypeAsString(parameterType.getComponentType(), true);
+ }
+ if (parameterType.isPrimitive()) {
+ return getPrimitiveLetter(parameterType);
+ }
+ String className = parameterType.getCanonicalName();
+
+ if (parameterType.isMemberClass()) {
+ final int lastDot = className.lastIndexOf(".");
+ className = className.substring(0, lastDot) + "$" + className.substring(lastDot + 1);
+ }
+
+ if (wrap)
+ {
+ return "L" + className.replace('.', '/') + ";";
+ }
+ return className.replace('.', '/');
+ }
+
+ @Override // not used ATM
+ public Class<?> generateProxyClass(final ClassLoader classLoader, final Class<?>... proxyClasses)
+ {
+ return createProxy(getSuperclass(proxyClasses), classLoader, getImplementationMethods(proxyClasses), toInterfaces(proxyClasses));
+ }
+
+ private static class Unsafe
+ {
+ // sun.misc.Unsafe
+ private static final Object unsafe;
+ private static final Method defineClass;
+ private static final Method allocateInstance;
+ private static final Method putObject;
+ private static final Method objectFieldOffset;
+
+ static
+ {
+ final Class<?> unsafeClass;
+ try {
+ unsafeClass = AccessController.doPrivileged(new PrivilegedAction<Class<?>>()
+ {
+ @Override
+ public Class<?> run()
+ {
+ try
+ {
+ return Thread.currentThread().getContextClassLoader().loadClass("sun.misc.Unsafe");
+ }
+ catch (Exception e)
+ {
+ try
+ {
+ return ClassLoader.getSystemClassLoader().loadClass("sun.misc.Unsafe");
+ }
+ catch (ClassNotFoundException e1)
+ {
+ throw new IllegalStateException("Cannot get sun.misc.Unsafe", e);
+ }
+ }
+ }
+ });
+ }
+ catch (Exception e)
+ {
+ throw new IllegalStateException("Cannot get sun.misc.Unsafe class", e);
+ }
+
+ unsafe = AccessController.doPrivileged(new PrivilegedAction<Object>()
+ {
+ @Override
+ public Object run()
+ {
+ try
+ {
+ final Field field = unsafeClass.getDeclaredField("theUnsafe");
+ field.setAccessible(true);
+ return field.get(null);
+ }
+ catch (Exception e)
+ {
+ throw new IllegalStateException("Cannot get sun.misc.Unsafe", e);
+ }
+ }
+ });
+ allocateInstance = AccessController.doPrivileged(new PrivilegedAction<Method>()
+ {
+ @Override
+ public Method run()
+ {
+ try
+ {
+ final Method mtd = unsafeClass.getDeclaredMethod("allocateInstance", Class.class);
+ mtd.setAccessible(true);
+ return mtd;
+ }
+ catch (Exception e)
+ {
+ throw new IllegalStateException("Cannot get sun.misc.Unsafe.allocateInstance", e);
+ }
+ }
+ });
+ objectFieldOffset = AccessController.doPrivileged(new PrivilegedAction<Method>()
+ {
+ @Override
+ public Method run()
+ {
+ try
+ {
+ final Method mtd = unsafeClass.getDeclaredMethod("objectFieldOffset", Field.class);
+ mtd.setAccessible(true);
+ return mtd;
+ }
+ catch (Exception e)
+ {
+ throw new IllegalStateException("Cannot get sun.misc.Unsafe.objectFieldOffset", e);
+ }
+ }
+ });
+ putObject = AccessController.doPrivileged(new PrivilegedAction<Method>()
+ {
+ @Override
+ public Method run()
+ {
+ try
+ {
+ final Method mtd = unsafeClass.getDeclaredMethod("putObject", Object.class, long.class, Object.class);
+ mtd.setAccessible(true);
+ return mtd;
+ }
+ catch (Exception e)
+ {
+ throw new IllegalStateException("Cannot get sun.misc.Unsafe.putObject", e);
+ }
+ }
+ });
+ defineClass = AccessController.doPrivileged(new PrivilegedAction<Method>()
+ {
+ @Override
+ public Method run()
+ {
+ try
+ {
+ final Method mtd = unsafeClass.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class, ClassLoader.class, ProtectionDomain.class);
+ mtd.setAccessible(true);
+ return mtd;
+ }
+ catch (Exception e)
+ {
+ throw new IllegalStateException("Cannot get sun.misc.Unsafe.defineClass", e);
+ }
+ }
+ });
+ }
+
+ private static Object allocateInstance(final Class<?> clazz)
+ {
+ try
+ {
+ return allocateInstance.invoke(unsafe, clazz);
+ }
+ catch (IllegalAccessException e)
+ {
+ throw new IllegalStateException("Failed to allocateInstance of Proxy class " + clazz.getName(), e);
+ }
+ catch (InvocationTargetException e)
+ {
+ final Throwable throwable = e.getTargetException() != null ? e.getTargetException() : e;
+ throw new IllegalStateException("Failed to allocateInstance of Proxy class " + clazz.getName(), throwable);
+ }
+ }
+
+ private static void setValue(final Field field, final Object object, final Object value)
+ {
+ final long offset;
+ try
+ {
+ offset = (Long) objectFieldOffset.invoke(unsafe, field);
+ }
+ catch (Exception e)
+ {
+ throw new IllegalStateException("Failed getting offset for: field=" + field.getName() + " class=" + field.getDeclaringClass().getName(), e);
+ }
+
+ try
+ {
+ putObject.invoke(unsafe, object, offset, value);
+ }
+ catch (Exception e)
+ {
+ throw new IllegalStateException("Failed putting field=" + field.getName() + " class=" + field.getDeclaringClass().getName(), e);
+ }
+ }
+
+ private static Class<?> defineClass(final ClassLoader loader, final Class<?> clsToProxy, final String proxyName, final byte[] proxyBytes) throws IllegalAccessException, InvocationTargetException
+ {
+ return (Class<?>) defineClass.invoke(unsafe, proxyName, proxyBytes, 0, proxyBytes.length, loader, clsToProxy.getProtectionDomain());
+ }
+ }
+ }
+
+ //////////////// these classes should be protected in ProxyFactory
+ @SuppressWarnings("serial")
+ private static class DelegatorInvocationHandler extends AbstractInvocationHandler
+ {
+ private final ObjectProvider<?> delegateProvider;
+
+ protected DelegatorInvocationHandler(ObjectProvider<?> delegateProvider)
+ {
+ this.delegateProvider = delegateProvider;
+ }
+
+ public Object invokeImpl(Object proxy, Method method, Object[] args) throws Throwable
+ {
+ try
+ {
+ return method.invoke(delegateProvider.getObject(), args);
+ }
+ catch (InvocationTargetException e)
+ {
+ throw e.getTargetException();
+ }
+ }
+ }
+
+ @SuppressWarnings("serial")
+ private static class InterceptorInvocationHandler extends AbstractInvocationHandler
+ {
+ private final Object target;
+ private final Interceptor methodInterceptor;
+
+ public InterceptorInvocationHandler(Object target, Interceptor methodInterceptor)
+ {
+ this.target = target;
+ this.methodInterceptor = methodInterceptor;
+ }
+
+ public Object invokeImpl(Object proxy, Method method, Object[] args) throws Throwable
+ {
+ final ReflectionInvocation invocation = new ReflectionInvocation(target, proxy, method, args);
+ return methodInterceptor.intercept(invocation);
+ }
+ }
+
+ @SuppressWarnings("serial")
+ private abstract static class AbstractInvocationHandler implements InvocationHandler, Serializable
+ {
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
+ {
+ if (isHashCode(method))
+ {
+ return System.identityHashCode(proxy);
+ }
+ if (isEqualsMethod(method))
+ {
+ return proxy == args[0];
+ }
+ return invokeImpl(proxy, method, args);
+ }
+
+ protected abstract Object invokeImpl(Object proxy, Method method, Object[] args) throws Throwable;
+ }
+
+ @SuppressWarnings("serial")
+ private static class InvokerInvocationHandler extends AbstractInvocationHandler
+ {
+ private final Invoker invoker;
+
+ public InvokerInvocationHandler(Invoker invoker)
+ {
+ this.invoker = invoker;
+ }
+
+ public Object invokeImpl(Object proxy, Method method, Object[] args) throws Throwable
+ {
+ return invoker.invoke(proxy, method, args);
+ }
+ }
+
+ protected static boolean isHashCode(Method method)
+ {
+ return "hashCode".equals(method.getName()) &&
+ Integer.TYPE.equals(method.getReturnType()) &&
+ method.getParameterTypes().length == 0;
+ }
+
+ protected static boolean isEqualsMethod(Method method)
+ {
+ return "equals".equals(method.getName()) &&
+ Boolean.TYPE.equals(method.getReturnType()) &&
+ method.getParameterTypes().length == 1 &&
+ Object.class.equals(method.getParameterTypes()[0]);
+ }
+
+ @SuppressWarnings("serial")
+ private static class ReflectionInvocation implements Invocation, Serializable
+ {
+ private final Method method;
+ private final Object[] arguments;
+ private final Object proxy;
+ private final Object target;
+
+ public ReflectionInvocation(final Object target, final Object proxy, final Method method, final Object[] arguments)
+ {
+ this.method = method;
+ this.arguments = (arguments == null ? ProxyUtils.EMPTY_ARGUMENTS : arguments);
+ this.proxy = proxy;
+ this.target = target;
+ }
+
+ public Object[] getArguments()
+ {
+ return arguments;
+ }
+
+ public Method getMethod()
+ {
+ return method;
+ }
+
+ public Object getProxy()
+ {
+ return proxy;
+ }
+
+ public Object proceed() throws Throwable
+ {
+ try
+ {
+ return method.invoke(target, arguments);
+ }
+ catch (InvocationTargetException e)
+ {
+ throw e.getTargetException();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/asm4/src/main/resources/META-INF/services/org.apache.commons.proxy2.ProxyFactory b/asm4/src/main/resources/META-INF/services/org.apache.commons.proxy2.ProxyFactory
new file mode 100644
index 0000000..529137c
--- /dev/null
+++ b/asm4/src/main/resources/META-INF/services/org.apache.commons.proxy2.ProxyFactory
@@ -0,0 +1,17 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+#
+org.apache.commons.proxy2.asm4.ASM4ProxyFactory
diff --git a/asm4/src/test/java/org/apache/commons/proxy2/asm4/TestAsm4ProxyFactory.java b/asm4/src/test/java/org/apache/commons/proxy2/asm4/TestAsm4ProxyFactory.java
new file mode 100644
index 0000000..7808e84
--- /dev/null
+++ b/asm4/src/test/java/org/apache/commons/proxy2/asm4/TestAsm4ProxyFactory.java
@@ -0,0 +1,10 @@
+package org.apache.commons.proxy2.asm4;
+
+import org.apache.commons.proxy2.AbstractSubclassingProxyFactoryTestCase;
+
+public class TestAsm4ProxyFactory extends AbstractSubclassingProxyFactoryTestCase
+{
+//**********************************************************************************************************************
+// Constructors
+//**********************************************************************************************************************
+}
diff --git a/pom.xml b/pom.xml
index 6863779..36d3e81 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,6 +23,7 @@
<modules>
<module>core</module>
<module>jdk</module>
+ <module>asm4</module>
<module>javassist</module>
<module>cglib</module>
<module>stub</module>
@@ -107,6 +108,12 @@
<role>advisor</role>
</roles>
</contributor>
+ <contributor>
+ <name>Mark Struberg</name>
+ </contributor>
+ <contributor>
+ <name>Romain Manni-Bucau</name>
+ </contributor>
</contributors>
<scm>
@@ -210,6 +217,11 @@
<version>3.0</version>
</dependency>
<dependency>
+ <groupId>org.apache.xbean</groupId>
+ <artifactId>xbean-asm4-shaded</artifactId>
+ <version>3.14</version>
+ </dependency>
+ <dependency>
<groupId>jmock</groupId>
<artifactId>jmock</artifactId>
<version>1.0.1</version>