org.apache.deltaspike.proxy.util.AsmProxyClassGenerator.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.deltaspike.proxy.util.AsmProxyClassGenerator.java

Source

/*
 * 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.deltaspike.proxy.util;

import org.apache.deltaspike.proxy.invocation.DelegateManualInvocationHandler;
import org.apache.deltaspike.proxy.invocation.InterceptManualInvocationHandler;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.UndeclaredThrowableException;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Arrays;

import javax.enterprise.inject.Typed;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.GeneratorAdapter;
import org.objectweb.asm.commons.Method;

@Typed
public abstract class AsmProxyClassGenerator {
    private static final String FIELDNAME_DELEGATE_INVOCATION_HANDLER = "delegateInvocationHandler";

    private static final Type TYPE_CLASS = Type.getType(Class.class);
    private static final Type TYPE_OBJECT = Type.getType(Object.class);

    private AsmProxyClassGenerator() {
        // prevent instantiation
    }

    public static <T> Class<T> generateProxyClass(ClassLoader classLoader, Class<T> targetClass,
            Class<? extends InvocationHandler> invocationHandlerClass, String suffix,
            String superAccessorMethodSuffix, Class<?>[] additionalInterfaces,
            java.lang.reflect.Method[] delegateMethods, java.lang.reflect.Method[] interceptMethods) {
        String proxyName = targetClass.getCanonicalName() + suffix;
        String classFileName = proxyName.replace('.', '/');

        byte[] proxyBytes = generateProxyClassBytes(targetClass, invocationHandlerClass, classFileName,
                superAccessorMethodSuffix, additionalInterfaces, delegateMethods, interceptMethods);

        Class<T> proxyClass = (Class<T>) loadClass(classLoader, proxyName, proxyBytes,
                targetClass.getProtectionDomain());

        return proxyClass;
    }

    private static byte[] generateProxyClassBytes(Class<?> targetClass,
            Class<? extends InvocationHandler> invocationHandlerClass, String proxyName,
            String superAccessorMethodSuffix, Class<?>[] additionalInterfaces,
            java.lang.reflect.Method[] delegateMethods, java.lang.reflect.Method[] interceptMethods) {
        Class<?> superClass = targetClass;
        String[] interfaces = new String[] {};

        if (targetClass.isInterface()) {
            superClass = Object.class;
            interfaces = new String[] { Type.getInternalName(targetClass) };
        }

        // add DeltaSpikeProxy as interface
        interfaces = Arrays.copyOf(interfaces, interfaces.length + 1);
        interfaces[interfaces.length - 1] = Type.getInternalName(DeltaSpikeProxy.class);

        if (additionalInterfaces != null && additionalInterfaces.length > 0) {
            interfaces = Arrays.copyOf(interfaces, interfaces.length + additionalInterfaces.length);
            for (int i = 0; i < additionalInterfaces.length; i++) {
                interfaces[(interfaces.length - 1) + i] = Type.getInternalName(additionalInterfaces[i]);
            }
        }

        Type superType = Type.getType(superClass);
        Type proxyType = Type.getObjectType(proxyName);
        Type invocationHandlerType = Type.getType(invocationHandlerClass);

        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, proxyType.getInternalName(), null,
                superType.getInternalName(), interfaces);

        // copy annotations
        for (Annotation annotation : targetClass.getDeclaredAnnotations()) {
            cw.visitAnnotation(Type.getDescriptor(annotation.annotationType()), true).visitEnd();
        }

        defineInvocationHandlerField(cw, invocationHandlerType);
        defineDefaultConstructor(cw, proxyType, superType);
        defineDelegateInvocationHandlerConstructor(cw, proxyType, superType, invocationHandlerType);
        defineDeltaSpikeProxyMethods(cw, proxyType, invocationHandlerType);

        for (java.lang.reflect.Method method : delegateMethods) {
            defineMethod(cw, method, DelegateManualInvocationHandler.class);
        }

        for (java.lang.reflect.Method method : interceptMethods) {
            defineSuperAccessorMethod(cw, method, superType, superAccessorMethodSuffix);
            defineMethod(cw, method, InterceptManualInvocationHandler.class);
        }

        return cw.toByteArray();
    }

    private static void defineInvocationHandlerField(ClassWriter cw, Type invocationHandlerType) {
        // generates
        // private MyInvocationHandler delegateInvocationHandler;
        cw.visitField(Opcodes.ACC_PRIVATE, FIELDNAME_DELEGATE_INVOCATION_HANDLER,
                invocationHandlerType.getDescriptor(), null, null).visitEnd();
    }

    private static void defineDefaultConstructor(ClassWriter cw, Type proxyType, Type superType) {
        GeneratorAdapter mg = new GeneratorAdapter(Opcodes.ACC_PUBLIC,
                new Method("<init>", Type.VOID_TYPE, new Type[] {}), null, null, cw);

        mg.visitCode();

        // invoke super constructor
        mg.loadThis();
        mg.invokeConstructor(superType, Method.getMethod("void <init> ()"));
        mg.returnValue();
        mg.endMethod();

        mg.visitEnd();
    }

    private static void defineDelegateInvocationHandlerConstructor(ClassWriter cw, Type proxyType, Type superType,
            Type invocationHandlerType) {
        GeneratorAdapter mg = new GeneratorAdapter(Opcodes.ACC_PUBLIC,
                new Method("<init>", Type.VOID_TYPE, new Type[] { invocationHandlerType }), null, null, cw);

        mg.visitCode();

        // invoke super constructor
        mg.loadThis();
        mg.invokeConstructor(superType, Method.getMethod("void <init> ()"));

        // set invocation handler
        mg.loadThis();
        mg.loadArg(0);
        mg.putField(proxyType, FIELDNAME_DELEGATE_INVOCATION_HANDLER, invocationHandlerType);

        mg.returnValue();
        mg.endMethod();

        mg.visitEnd();
    }

    private static void defineDeltaSpikeProxyMethods(ClassWriter cw, Type proxyType, Type invocationHandlerType) {
        try {
            // implement #setDelegateInvocationHandler
            Method asmMethod = Method.getMethod(DeltaSpikeProxy.class
                    .getDeclaredMethod("setDelegateInvocationHandler", InvocationHandler.class));
            GeneratorAdapter mg = new GeneratorAdapter(Opcodes.ACC_PUBLIC, asmMethod, null, null, cw);

            mg.visitCode();

            mg.loadThis();
            mg.loadArg(0);
            mg.checkCast(invocationHandlerType);
            mg.putField(proxyType, FIELDNAME_DELEGATE_INVOCATION_HANDLER, invocationHandlerType);
            mg.returnValue();

            mg.visitMaxs(2, 1);
            mg.visitEnd();

            // implement #getDelegateInvocationHandler
            asmMethod = Method.getMethod(DeltaSpikeProxy.class.getDeclaredMethod("getDelegateInvocationHandler"));
            mg = new GeneratorAdapter(Opcodes.ACC_PUBLIC, asmMethod, null, null, cw);

            mg.visitCode();

            mg.loadThis();
            mg.getField(proxyType, FIELDNAME_DELEGATE_INVOCATION_HANDLER, invocationHandlerType);
            mg.returnValue();

            mg.visitMaxs(2, 1);
            mg.visitEnd();
        } catch (NoSuchMethodException e) {
            throw new IllegalStateException("Unable to implement " + DeltaSpikeProxy.class.getName(), e);
        }
    }

    private static void defineSuperAccessorMethod(ClassWriter cw, java.lang.reflect.Method method, Type superType,
            String superAccessorMethodSuffix) {
        Method originalAsmMethod = Method.getMethod(method);
        Method newAsmMethod = new Method(method.getName() + superAccessorMethodSuffix,
                originalAsmMethod.getReturnType(), originalAsmMethod.getArgumentTypes());
        GeneratorAdapter mg = new GeneratorAdapter(Opcodes.ACC_PUBLIC, newAsmMethod, null, null, cw);

        mg.visitCode();

        // call super method
        mg.loadThis();
        mg.loadArgs();
        mg.visitMethodInsn(Opcodes.INVOKESPECIAL, superType.getInternalName(), method.getName(),
                Type.getMethodDescriptor(method), false);
        mg.returnValue();

        // finish the method
        mg.endMethod();
        mg.visitMaxs(10, 10);
        mg.visitEnd();
    }

    private static void defineMethod(ClassWriter cw, java.lang.reflect.Method method,
            Class manualInvocationHandlerClass) {
        Type methodType = Type.getType(method);

        ArrayList<Type> exceptionsToCatch = new ArrayList<Type>();
        for (Class<?> exception : method.getExceptionTypes()) {
            if (!RuntimeException.class.isAssignableFrom(exception)) {
                exceptionsToCatch.add(Type.getType(exception));
            }
        }

        // push the method definition
        int modifiers = (Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED) & method.getModifiers();
        Method asmMethod = Method.getMethod(method);
        GeneratorAdapter mg = new GeneratorAdapter(modifiers, asmMethod, null, getTypes(method.getExceptionTypes()),
                cw);

        // copy annotations
        for (Annotation annotation : method.getDeclaredAnnotations()) {
            mg.visitAnnotation(Type.getDescriptor(annotation.annotationType()), true).visitEnd();
        }

        mg.visitCode();

        Label tryBlockStart = mg.mark();

        mg.loadThis();
        loadCurrentMethod(mg, method, methodType);
        loadArguments(mg, method, methodType);

        // invoke our ProxyInvocationHandler
        mg.invokeStatic(Type.getType(manualInvocationHandlerClass),
                Method.getMethod("Object staticInvoke(Object, java.lang.reflect.Method, Object[])"));

        // cast the result
        mg.unbox(methodType.getReturnType());

        // build try catch
        Label tryBlockEnd = mg.mark();

        // push return
        mg.returnValue();

        // catch runtime exceptions and rethrow it
        Label rethrow = mg.mark();
        mg.visitVarInsn(Opcodes.ASTORE, 1);
        mg.visitVarInsn(Opcodes.ALOAD, 1);
        mg.throwException();
        mg.visitTryCatchBlock(tryBlockStart, tryBlockEnd, rethrow, Type.getInternalName(RuntimeException.class));

        // catch checked exceptions and rethrow it
        boolean throwableCatched = false;
        if (exceptionsToCatch.size() > 0) {
            rethrow = mg.mark();
            mg.visitVarInsn(Opcodes.ASTORE, 1);
            mg.visitVarInsn(Opcodes.ALOAD, 1);
            mg.throwException();

            // catch declared exceptions and rethrow it...
            for (Type exceptionType : exceptionsToCatch) {
                if (exceptionType.getClassName().equals(Throwable.class.getName())) {
                    throwableCatched = true;
                }
                mg.visitTryCatchBlock(tryBlockStart, tryBlockEnd, rethrow, exceptionType.getInternalName());
            }
        }

        // if throwable isn't alreached cachted, catch it and wrap it with an UndeclaredThrowableException and throw it
        if (!throwableCatched) {
            Type uteType = Type.getType(UndeclaredThrowableException.class);
            Label wrapAndRethrow = mg.mark();

            mg.visitVarInsn(Opcodes.ASTORE, 1);
            mg.newInstance(uteType);
            mg.dup();
            mg.visitVarInsn(Opcodes.ALOAD, 1);
            mg.invokeConstructor(uteType, Method.getMethod("void <init>(java.lang.Throwable)"));
            mg.throwException();

            mg.visitTryCatchBlock(tryBlockStart, tryBlockEnd, wrapAndRethrow,
                    Type.getInternalName(Throwable.class));
        }

        // finish the method
        mg.endMethod();
        mg.visitMaxs(10, 10);
        mg.visitEnd();
    }

    /**
     * Generates:
     * <pre>
     * Method method =
     *      method.getDeclaringClass().getMethod("methodName", new Class[] { args... });
     * </pre>
     * @param mg
     * @param method
     * @param methodType
     */
    private static void loadCurrentMethod(GeneratorAdapter mg, java.lang.reflect.Method method, Type methodType) {
        mg.push(Type.getType(method.getDeclaringClass()));
        mg.push(method.getName());

        // create the Class[]
        mg.push(methodType.getArgumentTypes().length);
        mg.newArray(TYPE_CLASS);

        // push parameters into array
        for (int i = 0; i < methodType.getArgumentTypes().length; i++) {
            // keep copy of array on stack
            mg.dup();

            // push index onto stack
            mg.push(i);
            mg.push(methodType.getArgumentTypes()[i]);
            mg.arrayStore(TYPE_CLASS);
        }

        // invoke getMethod() with the method name and the array of types
        mg.invokeVirtual(TYPE_CLASS,
                Method.getMethod("java.lang.reflect.Method getDeclaredMethod(String, Class[])"));
    }

    /**
     * Defines a new Object[] and push all method argmuments into the array.
     *
     * @param mg
     * @param method
     * @param methodType
     */
    private static void loadArguments(GeneratorAdapter mg, java.lang.reflect.Method method, Type methodType) {
        // create the Object[]
        mg.push(methodType.getArgumentTypes().length);
        mg.newArray(TYPE_OBJECT);

        // push parameters into array
        for (int i = 0; i < methodType.getArgumentTypes().length; i++) {
            // keep copy of array on stack
            mg.dup();

            // push index onto stack
            mg.push(i);

            mg.loadArg(i);
            mg.valueOf(methodType.getArgumentTypes()[i]);
            mg.arrayStore(TYPE_OBJECT);
        }
    }

    private static Type[] getTypes(Class<?>... src) {
        Type[] result = new Type[src.length];
        for (int i = 0; i < result.length; i++) {
            result[i] = Type.getType(src[i]);
        }
        return result;
    }

    /**
     * Adapted from http://asm.ow2.org/doc/faq.html#Q5
     *
     * @param b
     *
     * @return Class<?>
     */
    private static Class<?> loadClass(ClassLoader loader, String className, byte[] b,
            ProtectionDomain protectionDomain) {
        // override classDefine (as it is protected) and define the class.
        try {
            java.lang.reflect.Method method = ClassLoader.class.getDeclaredMethod("defineClass", String.class,
                    byte[].class, int.class, int.class, ProtectionDomain.class);

            // protected method invocation
            boolean accessible = method.isAccessible();
            if (!accessible) {
                method.setAccessible(true);
            }
            try {
                return (Class<?>) method.invoke(loader, className, b, Integer.valueOf(0), Integer.valueOf(b.length),
                        protectionDomain);
            } finally {
                if (!accessible) {
                    method.setAccessible(false);
                }
            }
        } catch (Exception e) {
            throw e instanceof RuntimeException ? ((RuntimeException) e) : new RuntimeException(e);
        }
    }
}