Package de.netseeker.ejoe.handler.gen

Source Code of de.netseeker.ejoe.handler.gen.DynProxyGenerator

/*********************************************************************
* DynProxyGenerator.java
* created on 21.07.2006 by netseeker
* $Id: DynProxyGenerator.java,v 1.15 2006/11/12 20:34:43 netseeker Exp $
*
*
* ====================================================================
*
*  Copyright 2006 netseeker aka Michael Manske
*
*  Licensed 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.
* ====================================================================
*
* This file is part of the EJOE framework.
* For more information on the author, please see
* <http://www.manskes.de/>.
*
*********************************************************************/

package de.netseeker.ejoe.handler.gen;

import java.lang.reflect.Constructor;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;

import javassist.CannotCompileException;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;
import de.netseeker.ejoe.EJConstants;
import de.netseeker.ejoe.cache.LRUMap;
import de.netseeker.ejoe.handler.BaseRemotingHandler;
import de.netseeker.ejoe.util.ContentStringBuilder;

/**
* Javassist based default proxy generator for accelerated access on reflected methods and constructors. This generator
* creates and caches one class for each class-method-pair respectively class-constrcutor-pair.
*
* @author netseeker
* @see http://www.jboss.org/products/javassist
* @since 0.3.9.1
*/
public class DynProxyGenerator implements IProxyGenerator
{
    private static final Logger    logger       = Logger.getLogger( DynProxyGenerator.class.getName() );

    private static final CtClass[] NO_ARGS      = {};

    /**
     * LRU cache for the generated proxy classes
     */
    private static LRUMap          cache        = new LRUMap( 5000 );

    /**
     * cache for Wrapper#[prmitive]Value method names
     */
    private static LRUMap          valueOfCache = new LRUMap( 100 );

    private ClassPool              _pool        = ClassPool.getDefault();

    private static Random          random       = new Random();

    private static Object          mutex        = new Object();

    /**
     * Creates a new instance of DynProxyGenerator This constructor will throw an IllegalStateException if the current
     * java version is lesser than 1.5.0
     */
    public DynProxyGenerator()
    {
        if ( EJConstants.JAVA_VERSION_INT < 150 )
        {
            throw new IllegalStateException( "DynProxyGenerator supports Java >= 1.5.0 only!" );
        }

        _pool.insertClassPath( new ClassClassPath( this.getClass() ) );
    }

    /**
     * Either returns a copy of an already cached instance of a proxy or creates a new one, instaniate it, add it to
     * cache and return a copy.
     *
     * @param tClasName the targetted class
     * @param mName the targetted method/constructor name
     * @param params array of arguments for the targetted method/constructor
     * @return a copy of an instance of IDynAccess
     * @throws Exception
     */
    public IDynAccess getDynAccessProxy( Class tClass, String mName, Object[] params ) throws Exception
    {

        String tClasName = tClass.getName();
        int index = tClasName.lastIndexOf( '.' );
        String pName = null;
        if ( index != -1 )
        {
            pName = tClasName.substring( index + 1 );
        }
        else
        {
            pName = tClasName;
        }

        pName = pName.replaceAll( "\\[", "" );
        pName = pName.replaceAll( "\\]", "" );
        Class[] types = getParamTypes( params );
        String cacheKey = pName + '#' + mName + '(' + ContentStringBuilder.toString( types ) + ')';

        // first try to locate the proxy in the cache
        IDynAccess dynaProxy = (IDynAccess) cache.get( cacheKey );

        // if not found then create a new one
        if ( dynaProxy == null )
        {
            // because we are just using class+method names as class names for
            // our proxy classes we have to add a unique suffix to avoid name-collisions
            int uid = -1;
            synchronized ( mutex )
            {
                uid = random.nextInt( Integer.MAX_VALUE );
            }
            String proxyName = pName + "DynAccessProxy" + uid;

            logger.log( Level.FINEST, "generated name of proxy class: " + proxyName );

            // pre-check if the requested method is a constructor
            boolean isConstructor = tClass.getSimpleName().equals( mName );
            Class[] realTypes = new Class[types.length];
            Member targetMtd = null;

            /*
             * try to find a method/constructor in the target class which: 1. has the same name 2. takes the given
             * paramter types or their primitive or wrapper counterparts
             */
            if ( !isConstructor )
                targetMtd = getCompatibleMethod( tClass, mName, types, realTypes );
            else
                targetMtd = getCompatibleConstructor( tClass, mName, types, realTypes );

            // ooops, nothing found
            if ( targetMtd == null ) throw new NoSuchMethodException( cacheKey );

            // create a clean, empty proxy class
            CtClass clas = _pool.makeClass( proxyName );
            CtClass target = _pool.get( tClasName );
            CtClass ctIDynProxy = _pool.get( IDynAccess.class.getName() );

            // add target object field to class
            CtField field = new CtField( target, "m_dynTarget", clas );
            clas.addField( field );

            // add public default constructor method to class
            CtConstructor cons = new CtConstructor( NO_ARGS, clas );
            cons.setBody( "m_dynTarget = new " + tClasName + "();" );
            clas.addConstructor( cons );

            // add public <code>createCopy</code> method
            CtMethod ctMeth = new CtMethod( ctIDynProxy, "createCopy", NO_ARGS, clas );
            ctMeth.setBody( "return new " + proxyName + "();" );
            clas.addMethod( ctMeth );

            // add public <code>setDynTarget</code> method
            ctMeth = new CtMethod( CtClass.voidType, "setDynTarget", new CtClass[] { _pool.get( "java.lang.Object" ) },
                                   clas );
            ctMeth.setBody( "m_dynTarget = (" + tClasName + ")$1;" );
            clas.addMethod( ctMeth );

            // add public <code>invokeDynMethod</code> method
            ctMeth = buildMethodAccessor( clas, targetMtd, types, realTypes, isConstructor );
            clas.addMethod( ctMeth );

            // apply our IDynAccess interface to the new proxy class
            clas.addInterface( _pool.get( IDynAccess.class.getName() ) );

            // create a new instance of the new proxy
            dynaProxy = (IDynAccess) clas.toClass().newInstance();
            // cache the instance
            cache.put( cacheKey, dynaProxy );
        }

        // return a copy to avoid synchronize-issues
        return dynaProxy.createCopy();
    }

    /**
     * @param clas
     * @param method
     * @param types
     * @param realTypes
     * @param isConstructor
     * @return
     * @throws CannotCompileException
     */
    private CtMethod buildMethodAccessor( CtClass clas, Member method, Class[] types, Class[] realTypes,
                                          boolean isConstructor ) throws CannotCompileException
    {
        StringBuffer sb = new StringBuffer(
                                            "public Object invokeDynMethod(String method, Object[] params) throws Exception { " );
        boolean wrap = false;

        if ( !isConstructor )
        {
            Class retType = ((Method) method).getReturnType();

            if ( retType.isPrimitive() )
            {
                sb.append( " return new " );
                sb.append( BaseRemotingHandler.getWrapperForPrimitive( retType ).getSimpleName() );
                sb.append( "( m_dynTarget." );
                wrap = true;
            }
            else
                sb.append( " return m_dynTarget." );
        }
        else
        {
            sb.append( "return new " );
        }

        sb.append( method.getName() );
        sb.append( '(' );

        for ( int i = 0; i < types.length; i++ )
        {
            sb.append( "((" );
            sb.append( types[i].getSimpleName() );
            sb.append( ')' );
            sb.append( "params[" );
            sb.append( i );
            sb.append( "])" );
            if ( realTypes[i] != null && (!types[i].equals( realTypes[i] )) )
            {
                String valueOf = getValueOfName( types[i], realTypes[i] );
                if ( valueOf != null )
                {
                    sb.append( '.' );
                    sb.append( valueOf );
                    sb.append( "()" );
                }
                else
                {
                    throw new IllegalArgumentException( "Can't autobox " + types[i].getSimpleName() + " to "
                            + realTypes[i].getSimpleName() );
                }
            }

            if ( i < types.length - 1 )
            {
                sb.append( ',' );
            }
        }

        if ( wrap )
            sb.append( ") ); }" );
        else
            sb.append( "); }" );

        // System.out.print(sb);

        return CtNewMethod.make( sb.toString(), clas );
    }

    /**
     * @param params
     * @return
     */
    private Class[] getParamTypes( Object[] params )
    {
        Class[] types = new Class[params.length];

        for ( int i = 0; i < params.length; i++ )
        {
            types[i] = params[i].getClass();
        }

        return types;
    }

    /**
     * @param clazz
     * @param mName
     * @param parameterTypes
     * @param targetParameterTypes
     * @return
     * @throws SecurityException
     */
    private Method getCompatibleMethod( Class clazz, String mName, Class[] parameterTypes, Class[] targetParameterTypes )
            throws SecurityException
    {

        try
        {
            Method mtd = clazz.getMethod( mName, parameterTypes );
            return mtd;
        }
        catch ( NoSuchMethodException e1 )
        {
            // can happen
        }

        Method[] mtds = clazz.getMethods();
        for ( int i = 0; i < mtds.length; i++ )
        {
            if ( mtds[i].getName().equals( mName ) )
            {
                Class[] types = mtds[i].getParameterTypes();
                if ( types.length == parameterTypes.length )
                {
                    if ( checkTypes( types, parameterTypes ) )
                    {
                        System.arraycopy( types, 0, targetParameterTypes, 0, types.length );
                        return mtds[i];
                    }
                }
            }
        }

        return null;
    }

    /**
     * @param clazz
     * @param cName
     * @param parameterTypes
     * @param targetParameterTypes
     * @return
     */
    private Constructor getCompatibleConstructor( Class clazz, String cName, Class[] parameterTypes,
                                                  Class[] targetParameterTypes )
    {
        try
        {
            Constructor cTor = clazz.getConstructor( parameterTypes );
            return cTor;
        }
        catch ( NoSuchMethodException e1 )
        {
            // can happen
        }

        Constructor[] ctors = clazz.getConstructors();
        for ( int i = 0; i < ctors.length; i++ )
        {
            Class[] types = ctors[i].getParameterTypes();
            if ( types.length == parameterTypes.length )
            {
                if ( checkTypes( types, parameterTypes ) )
                {
                    System.arraycopy( types, 0, targetParameterTypes, 0, types.length );
                    return ctors[i];
                }
            }
        }

        return null;
    }

    /**
     * @param types
     * @param parameterTypes
     * @return
     */
    private boolean checkTypes( Class[] types, Class[] parameterTypes )
    {
        for ( int i = 0; i < types.length; i++ )
        {
            Class paramType = parameterTypes[i];
            if ( types[i].isPrimitive() && !paramType.isPrimitive() )
            {
                paramType = BaseRemotingHandler.getPrimitiveForWrapper( parameterTypes[i] );
            }

            if ( !(types[i].isAssignableFrom( paramType )) )
            {
                return false;
            }
        }

        return true;
    }

    /**
     * @param wrapper
     * @param primitive
     * @return
     */
    private String getValueOfName( Class wrapper, Class primitive )
    {
        String primitiveName = primitive.getName();
        String mtdName = (String) valueOfCache.get( wrapper.getName() + ':' + primitiveName );

        if ( mtdName == null )
        {
            Method[] mtds = wrapper.getMethods();

            for ( int i = 0; i < mtds.length; i++ )
            {
                if ( mtds[i].getName().endsWith( "Value" ) && mtds[i].getParameterTypes().length == 0 )
                {
                    if ( mtds[i].getReturnType().equals( primitive ) )
                    {
                        mtdName = mtds[i].getName();
                        valueOfCache.put( wrapper.getName() + ':' + primitiveName, mtdName );
                        break;
                    }
                }
            }
        }

        return mtdName;
    }
}
TOP

Related Classes of de.netseeker.ejoe.handler.gen.DynProxyGenerator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.