/******************************************************************************* * Copyright (c) 2008-present Stuart McCulloch * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Stuart McCulloch - 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<String, Method> OBJECT_METHOD_MAP; // ---------------------------------------------------------------------- // Static initialization // ---------------------------------------------------------------------- static { // pre-seed common methods that should be wrapped OBJECT_METHOD_MAP = new HashMap<String, Method>(); 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, "<init>", '(' + 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, "<init>", "()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, "<init>", "()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<Method> getWrappableMethods( final Class<?> clazz ) { final Map<String, Method> methodMap = new HashMap<String, Method>( 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(); } }