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

org.eclipse.sisu.wire.DynamicGlue Maven / Gradle / Ivy

There is a newer version: 3.0.0-alpha-3
Show newest version
/*
 * Copyright (c) 2010-2024 Sonatype, Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *   Stuart McCulloch (Sonatype, Inc.) - initial API and implementation
 */
package org.eclipse.sisu.wire;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import javax.inject.Provider;

import org.eclipse.sisu.space.asm.ClassWriter;
import org.eclipse.sisu.space.asm.Label;
import org.eclipse.sisu.space.asm.MethodVisitor;
import org.eclipse.sisu.space.asm.Opcodes;
import org.eclipse.sisu.space.asm.Type;

/**
 * Utility methods for generating dynamic {@link Provider}-based proxies.
 */
final class DynamicGlue
{
    // ----------------------------------------------------------------------
    // Constants
    // ----------------------------------------------------------------------

    private static final String PROVIDER_NAME = Type.getInternalName( Provider.class );

    private static final String PROVIDER_DESC = Type.getDescriptor( Provider.class );

    private static final String PROVIDER_HANDLE = "__sisu__";

    private static final String OBJECT_NAME = Type.getInternalName( Object.class );

    private static final String OBJECT_DESC = Type.getDescriptor( Object.class );

    private static final String ILLEGAL_STATE_NAME = Type.getInternalName( IllegalStateException.class );

    private static final Map OBJECT_METHOD_MAP;

    // ----------------------------------------------------------------------
    // Static initialization
    // ----------------------------------------------------------------------

    static
    {
        // pre-seed common methods that should be wrapped
        OBJECT_METHOD_MAP = new HashMap();
        for ( final Method m : Object.class.getMethods() )
        {
            if ( isWrappable( m ) )
            {
                OBJECT_METHOD_MAP.put( signatureKey( m ), m );
            }
        }
    }

    // ----------------------------------------------------------------------
    // Constructors
    // ----------------------------------------------------------------------

    private DynamicGlue()
    {
        // static utility class, not allowed to create instances
    }

    // ----------------------------------------------------------------------
    // Utility methods
    // ----------------------------------------------------------------------

    /**
     * Generates a dynamic {@link Provider}-based proxy that reflects the given facade.
     * 
     * @param proxyName The proxy name
     * @param facade The expected facade
     * @return Generated proxy bytes
     */
    public static byte[] generateProxyClass( final String proxyName, final Class facade )
    {
        final String facadeName = Type.getInternalName( facade );

        final String superName;
        final String[] apiNames;

        if ( facade.isInterface() )
        {
            superName = OBJECT_NAME;
            apiNames = new String[] { facadeName };
        }
        else
        {
            superName = facadeName;
            apiNames = getInternalNames( facade.getInterfaces() );
        }

        final ClassWriter cw = new ClassWriter( ClassWriter.COMPUTE_MAXS );
        cw.visit( Opcodes.V1_6, Modifier.PUBLIC | Modifier.FINAL, proxyName, null, superName, apiNames );
        init( cw, superName, proxyName );

        for ( final Method m : getWrappableMethods( facade ) )
        {
            wrap( cw, proxyName, m );
        }

        cw.visitEnd();

        return cw.toByteArray();
    }

    // ----------------------------------------------------------------------
    // Implementation methods
    // ----------------------------------------------------------------------

    /**
     * Generates a constructor that accepts a {@link Provider} and stores it in an internal field.
     */
    private static void init( final ClassWriter cw, final String superName, final String proxyName )
    {
        cw.visitField( Modifier.PRIVATE | Modifier.FINAL, PROVIDER_HANDLE, PROVIDER_DESC, null, null ).visitEnd();

        final MethodVisitor v = cw.visitMethod( Modifier.PUBLIC, "", '(' + PROVIDER_DESC + ")V", null, null );

        v.visitCode();
        v.visitVarInsn( Opcodes.ALOAD, 0 );
        v.visitInsn( Opcodes.DUP );
        v.visitVarInsn( Opcodes.ALOAD, 1 );
        v.visitFieldInsn( Opcodes.PUTFIELD, proxyName, PROVIDER_HANDLE, PROVIDER_DESC );
        v.visitMethodInsn( Opcodes.INVOKESPECIAL, superName, "", "()V", false );
        v.visitInsn( Opcodes.RETURN );
        v.visitMaxs( 0, 0 );
        v.visitEnd();
    }

    /**
     * Generates a wrapper that dereferences the stored {@link Provider} and invokes the given method.
     */
    private static void wrap( final ClassWriter cw, final String proxyName, final Method method )
    {
        final String methodName = method.getName();
        final String descriptor = Type.getMethodDescriptor( method );
        final String[] exceptions = getInternalNames( method.getExceptionTypes() );
        final Label handleNullTarget = new Label();

        // simple delegating proxy, so don't need synchronization on wrapper method
        final int modifiers = method.getModifiers() & ~( Modifier.ABSTRACT | Modifier.NATIVE | Modifier.SYNCHRONIZED );
        final MethodVisitor v = cw.visitMethod( modifiers, methodName, descriptor, null, exceptions );

        v.visitCode();

        final Class declaringClazz = method.getDeclaringClass();
        final String declaringName = Type.getInternalName( declaringClazz );

        final boolean isObjectMethod = OBJECT_METHOD_MAP.containsKey( signatureKey( method ) );

        // delegate all non-Object methods as well as 'toString'
        if ( !isObjectMethod || "toString".equals( methodName ) )
        {
            // dereference and check target
            v.visitVarInsn( Opcodes.ALOAD, 0 );
            v.visitFieldInsn( Opcodes.GETFIELD, proxyName, PROVIDER_HANDLE, PROVIDER_DESC );
            v.visitMethodInsn( Opcodes.INVOKEINTERFACE, PROVIDER_NAME, "get", "()" + OBJECT_DESC, true );

            v.visitInsn( Opcodes.DUP );
            v.visitJumpInsn( Opcodes.IFNULL, handleNullTarget );

            final boolean isInterface = declaringClazz.isInterface();
            if ( !isInterface && Object.class != declaringClazz )
            {
                // must check target before setting up INVOKEVIRTUAL
                v.visitTypeInsn( Opcodes.CHECKCAST, declaringName );
            }

            int slot = 1;
            for ( final Type t : Type.getArgumentTypes( method ) )
            {
                v.visitVarInsn( t.getOpcode( Opcodes.ILOAD ), slot );
                slot = slot + t.getSize();
            }

            // invoke original method on target
            final int invoke = isInterface ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL;
            v.visitMethodInsn( invoke, declaringName, methodName, descriptor, isInterface );
            v.visitInsn( Type.getReturnType( method ).getOpcode( Opcodes.IRETURN ) );

            v.visitLabel( handleNullTarget );
            v.visitInsn( Opcodes.POP );

            if ( !isObjectMethod )
            {
                // no fall-back available, report illegal state
                v.visitTypeInsn( Opcodes.NEW, ILLEGAL_STATE_NAME );
                v.visitInsn( Opcodes.DUP );
                v.visitMethodInsn( Opcodes.INVOKESPECIAL, ILLEGAL_STATE_NAME, "", "()V", false );
                v.visitInsn( Opcodes.ATHROW );
            }
        }

        if ( isObjectMethod )
        {
            v.visitVarInsn( Opcodes.ALOAD, 0 );

            int slot = 1;
            for ( final Type t : Type.getArgumentTypes( method ) )
            {
                v.visitVarInsn( t.getOpcode( Opcodes.ILOAD ), slot );
                slot = slot + t.getSize();
            }

            // fall-back to superclass implementation for all Object methods
            v.visitMethodInsn( Opcodes.INVOKESPECIAL, declaringName, methodName, descriptor, false );
            v.visitInsn( Type.getReturnType( method ).getOpcode( Opcodes.IRETURN ) );
        }

        v.visitMaxs( 0, 0 );
        v.visitEnd();
    }

    /**
     * Returns the internal names of the given classes.
     */
    private static String[] getInternalNames( final Class... clazzes )
    {
        final String[] names = new String[clazzes.length];
        for ( int i = 0; i < names.length; i++ )
        {
            names[i] = Type.getInternalName( clazzes[i] );
        }
        return names;
    }

    /**
     * Returns the methods that should be wrapped for delegation in the given class.
     */
    private static Collection getWrappableMethods( final Class clazz )
    {
        final Map methodMap = new HashMap( OBJECT_METHOD_MAP );
        for ( final Method m : clazz.getMethods() )
        {
            if ( isWrappable( m ) )
            {
                methodMap.put( signatureKey( m ), m );
            }
        }
        return methodMap.values();
    }

    /**
     * Returns {@code true} if the given method should be wrapped; otherwise {@code false}.
     */
    private static boolean isWrappable( final Method method )
    {
        return ( method.getModifiers() & ( Modifier.STATIC | Modifier.FINAL ) ) == 0;
    }

    /**
     * Returns a signature-based key that identifies the given method in the current class.
     */
    private static String signatureKey( final Method method )
    {
        final StringBuilder buf = new StringBuilder( method.getName() );
        for ( final Class t : method.getParameterTypes() )
        {
            buf.append( ':' ).append( t );
        }
        return buf.toString();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy