[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>