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

org.apache.deltaspike.proxy.impl.AsmProxyClassGenerator Maven / Gradle / Ivy

There is a newer version: 1.8.1
Show newest version
/*
 * 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.impl;

import org.apache.deltaspike.proxy.spi.DeltaSpikeProxy;
import org.apache.deltaspike.proxy.impl.invocation.DelegateManualInvocationHandler;
import org.apache.deltaspike.proxy.impl.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.apache.deltaspike.proxy.spi.ProxyClassGenerator;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
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 class AsmProxyClassGenerator implements ProxyClassGenerator
{
    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);

    @Override
    public  Class generateProxyClass(ClassLoader classLoader,
            Class targetClass,
            String suffix,
            String superAccessorMethodSuffix,
            Class[] additionalInterfaces,
            java.lang.reflect.Method[] delegateMethods,
            java.lang.reflect.Method[] interceptMethods)
    {
        String proxyName = targetClass.getName() + suffix;
        String classFileName = proxyName.replace('.', '/');

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

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

        return proxyClass;
    }

    private static byte[] generateProxyClassBytes(Class targetClass,
            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 delegateInvocationHandlerType = Type.getType(InvocationHandler.class);

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

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

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

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

        // copy all annotations from the source class
        try
        {
            // ClassVisitor to intercept all annotation visits on the class
            ClassVisitor cv = new ClassVisitor(Opcodes.ASM5)
            {
                @Override
                public AnnotationVisitor visitAnnotation(String desc, boolean visible)
                {
                    return new CopyAnnotationVisitorAdapter(
                            super.visitAnnotation(desc, visible),
                            cw.visitAnnotation(desc, visible));
                }
            };

            // visit class to proxy with our visitor to copy all annotations from the source class to our ClassWriter
            String sourceClassFilename = targetClass.getName().replace('.', '/') + ".class";
            ClassReader cr = new ClassReader(targetClass.getClassLoader().getResourceAsStream(sourceClassFilename));
            cr.accept(cv, 0);
        }
        catch (Exception e)
        {
            throw new RuntimeException(e);
        }

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

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

        mg.visitCode();

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

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

        mg.visitCode();

        // invoke super constructor
        mg.loadThis();
        mg.invokeConstructor(superType, Method.getMethod("void  ()"));
        
        // set invocation handler
        mg.loadThis();
        mg.loadArg(0);
        mg.putField(proxyType, FIELDNAME_DELEGATE_INVOCATION_HANDLER, delegateInvocationHandlerType);
        
        mg.returnValue();
        mg.endMethod();

        mg.visitEnd();
    }

    private static void defineDeltaSpikeProxyMethods(ClassWriter cw, Type proxyType, Type delegateInvocationHandlerType)
    {
        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(delegateInvocationHandlerType);
            mg.putField(proxyType, FIELDNAME_DELEGATE_INVOCATION_HANDLER, delegateInvocationHandlerType);
            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, delegateInvocationHandlerType);
            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 exceptionsToCatch = new ArrayList();
        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.isEmpty())
        {
            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 (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:
     * 
     * Method method =
     *      method.getDeclaringClass().getMethod("methodName", new Class[] { args... });
     * 
* @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); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy