001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    package org.apache.commons.proxy.factory.javassist;
019    
020    import javassist.CannotCompileException;
021    import javassist.CtClass;
022    import javassist.CtConstructor;
023    import javassist.CtMethod;
024    import org.apache.commons.proxy.Invocation;
025    import org.apache.commons.proxy.ProxyUtils;
026    
027    import java.lang.ref.WeakReference;
028    import java.lang.reflect.Method;
029    import java.util.HashMap;
030    import java.util.Map;
031    import java.util.WeakHashMap;
032    
033    /**
034     * A <a href="http://www.jboss.org/products/javassist">Javassist</a>-based {@link Invocation} implementation.  This
035     * class actually serves as the superclass for all <a href="http://www.jboss.org/products/javassist">Javassist</a>-based
036     * method invocations.  Subclasses are dynamically created to deal with specific interface methods (they're hard-wired).
037     * 
038     * @author James Carman
039     * @since 1.0
040     */
041    public abstract class JavassistInvocation implements Invocation
042    {
043    //----------------------------------------------------------------------------------------------------------------------
044    // Fields
045    //----------------------------------------------------------------------------------------------------------------------
046        private static WeakHashMap loaderToClassCache = new WeakHashMap();
047        protected final Method method;
048        protected final Object target;
049        protected final Object[] arguments;
050    
051    //----------------------------------------------------------------------------------------------------------------------
052    // Static Methods
053    //----------------------------------------------------------------------------------------------------------------------
054    
055        private static String createCastExpression( Class type, String objectToCast )
056        {
057            if( !type.isPrimitive() )
058            {
059                return "( " + ProxyUtils.getJavaClassName( type ) + " )" + objectToCast;
060            }
061            else
062            {
063                return "( ( " + ProxyUtils.getWrapperClass( type ).getName() + " )" + objectToCast + " )." +
064                       type.getName() + "Value()";
065            }
066        }
067    
068        private static Class createInvocationClass( ClassLoader classLoader, Method interfaceMethod )
069                throws CannotCompileException
070        {
071            Class invocationClass;
072            final CtClass ctClass = JavassistUtils.createClass(
073                    getSimpleName( interfaceMethod.getDeclaringClass() ) + "_" + interfaceMethod.getName() +
074                    "_invocation",
075                    JavassistInvocation.class );
076            final CtConstructor constructor = new CtConstructor(
077                    JavassistUtils.resolve( new Class[]{ Method.class, Object.class, Object[].class } ),
078                    ctClass );
079            constructor.setBody( "{\n\tsuper($$);\n}" );
080            ctClass.addConstructor( constructor );
081            final CtMethod proceedMethod = new CtMethod( JavassistUtils.resolve( Object.class ), "proceed",
082                                                         JavassistUtils.resolve( new Class[0] ), ctClass );
083            final Class[] argumentTypes = interfaceMethod.getParameterTypes();
084            final StringBuffer proceedBody = new StringBuffer( "{\n" );
085            if( !Void.TYPE.equals( interfaceMethod.getReturnType() ) )
086            {
087                proceedBody.append( "\treturn " );
088                if( interfaceMethod.getReturnType().isPrimitive() )
089                {
090                    proceedBody.append( "new " );
091                    proceedBody.append( ProxyUtils.getWrapperClass( interfaceMethod.getReturnType() ).getName() );
092                    proceedBody.append( "( " );
093                }
094            }
095            else
096            {
097                proceedBody.append( "\t" );
098            }
099            proceedBody.append( "( (" );
100            proceedBody.append( ProxyUtils.getJavaClassName( interfaceMethod.getDeclaringClass() ) );
101            proceedBody.append( " )target )." );
102            proceedBody.append( interfaceMethod.getName() );
103            proceedBody.append( "(" );
104            for( int i = 0; i < argumentTypes.length; ++i )
105            {
106                final Class argumentType = argumentTypes[i];
107                proceedBody.append( createCastExpression( argumentType, "arguments[" + i + "]" ) );
108                if( i != argumentTypes.length - 1 )
109                {
110                    proceedBody.append( ", " );
111                }
112            }
113            if( !Void.TYPE.equals( interfaceMethod.getReturnType() ) && interfaceMethod.getReturnType().isPrimitive() )
114            {
115                proceedBody.append( ") );\n" );
116            }
117            else
118            {
119                proceedBody.append( ");\n" );
120            }
121            if( Void.TYPE.equals( interfaceMethod.getReturnType() ) )
122            {
123                proceedBody.append( "\treturn null;\n" );
124            }
125            proceedBody.append( "}" );
126            final String body = proceedBody.toString();
127            proceedMethod.setBody( body );
128            ctClass.addMethod( proceedMethod );
129            invocationClass = ctClass.toClass( classLoader );
130            return invocationClass;
131        }
132    
133        private static Map getClassCache( ClassLoader classLoader )
134        {
135            Map cache = ( Map ) loaderToClassCache.get( classLoader );
136            if( cache == null )
137            {
138                cache = new HashMap();
139                loaderToClassCache.put( classLoader, cache );
140            }
141            return cache;
142        }
143    
144        /**
145         * Returns a method invocation class specifically coded to invoke the supplied interface method.
146         *
147         * @param classLoader the classloader to use
148         * @param interfaceMethod the interface method
149         * @return a method invocation class specifically coded to invoke the supplied interface method
150         * @throws CannotCompileException if a compilation error occurs
151         */
152        synchronized static Class getMethodInvocationClass( ClassLoader classLoader,
153                                                                   Method interfaceMethod )
154                throws CannotCompileException
155        {
156            final Map classCache = getClassCache( classLoader );
157            final String key = toClassCacheKey( interfaceMethod );
158            final WeakReference invocationClassRef = ( WeakReference ) classCache.get( key );
159            Class invocationClass;
160            if( invocationClassRef == null )
161            {
162                invocationClass = createInvocationClass( classLoader, interfaceMethod );
163                classCache.put( key, new WeakReference( invocationClass ) );
164            }
165            else
166            {
167                synchronized( invocationClassRef )
168                {
169                    invocationClass = ( Class ) invocationClassRef.get();
170                    if( invocationClass == null )
171                    {
172                        invocationClass = createInvocationClass( classLoader, interfaceMethod );
173                        classCache.put( key, new WeakReference( invocationClass ) );
174                    }
175                }
176            }
177            return invocationClass;
178        }
179    
180        private static String getSimpleName( Class c )
181        {
182            final String name = c.getName();
183            final int ndx = name.lastIndexOf( '.' );
184            return ndx == -1 ? name : name.substring( ndx + 1 );
185        }
186    
187        private static String toClassCacheKey( Method method )
188        {
189            return String.valueOf( method );
190        }
191    
192    //----------------------------------------------------------------------------------------------------------------------
193    // Constructors
194    //----------------------------------------------------------------------------------------------------------------------
195    
196        public JavassistInvocation( Method method, Object target, Object[] arguments )
197        {
198            this.method = method;
199            this.target = target;
200            this.arguments = arguments;
201        }
202    
203    //----------------------------------------------------------------------------------------------------------------------
204    // Invocation Implementation
205    //----------------------------------------------------------------------------------------------------------------------
206    
207        public Object[] getArguments()
208        {
209            return arguments;
210        }
211    
212        public Method getMethod()
213        {
214            return method;
215        }
216    
217        public Object getProxy()
218        {
219            return target;
220        }
221    }
222