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    
047        private static WeakHashMap loaderToClassCache = new WeakHashMap();
048        protected final Method method;
049        protected final Object target;
050        protected final Object[] arguments;
051    
052    //**********************************************************************************************************************
053    // Static Methods
054    //**********************************************************************************************************************
055    
056        private static String createCastExpression( Class type, String objectToCast )
057        {
058            if( !type.isPrimitive() )
059            {
060                return "( " + ProxyUtils.getJavaClassName(type) + " )" + objectToCast;
061            }
062            else
063            {
064                return "( ( " + ProxyUtils.getWrapperClass(type).getName() + " )" + objectToCast + " )." +
065                        type.getName() + "Value()";
066            }
067        }
068    
069        private static Class createInvocationClass( ClassLoader classLoader, Method interfaceMethod )
070                throws CannotCompileException
071        {
072            Class invocationClass;
073            final CtClass ctClass = JavassistUtils.createClass(
074                    getSimpleName(interfaceMethod.getDeclaringClass()) + "_" + interfaceMethod.getName() +
075                            "_invocation",
076                    JavassistInvocation.class);
077            final CtConstructor constructor = new CtConstructor(
078                    JavassistUtils.resolve(new Class[] {Method.class, Object.class, Object[].class}),
079                    ctClass);
080            constructor.setBody("{\n\tsuper($$);\n}");
081            ctClass.addConstructor(constructor);
082            final CtMethod proceedMethod = new CtMethod(JavassistUtils.resolve(Object.class), "proceed",
083                    JavassistUtils.resolve(new Class[0]), ctClass);
084            final Class[] argumentTypes = interfaceMethod.getParameterTypes();
085            final StringBuffer proceedBody = new StringBuffer("{\n");
086            if( !Void.TYPE.equals(interfaceMethod.getReturnType()) )
087            {
088                proceedBody.append("\treturn ");
089                if( interfaceMethod.getReturnType().isPrimitive() )
090                {
091                    proceedBody.append("new ");
092                    proceedBody.append(ProxyUtils.getWrapperClass(interfaceMethod.getReturnType()).getName());
093                    proceedBody.append("( ");
094                }
095            }
096            else
097            {
098                proceedBody.append("\t");
099            }
100            proceedBody.append("( (");
101            proceedBody.append(ProxyUtils.getJavaClassName(interfaceMethod.getDeclaringClass()));
102            proceedBody.append(" )target ).");
103            proceedBody.append(interfaceMethod.getName());
104            proceedBody.append("(");
105            for( int i = 0; i < argumentTypes.length; ++i )
106            {
107                final Class argumentType = argumentTypes[i];
108                proceedBody.append(createCastExpression(argumentType, "arguments[" + i + "]"));
109                if( i != argumentTypes.length - 1 )
110                {
111                    proceedBody.append(", ");
112                }
113            }
114            if( !Void.TYPE.equals(interfaceMethod.getReturnType()) && interfaceMethod.getReturnType().isPrimitive() )
115            {
116                proceedBody.append(") );\n");
117            }
118            else
119            {
120                proceedBody.append(");\n");
121            }
122            if( Void.TYPE.equals(interfaceMethod.getReturnType()) )
123            {
124                proceedBody.append("\treturn null;\n");
125            }
126            proceedBody.append("}");
127            final String body = proceedBody.toString();
128            proceedMethod.setBody(body);
129            ctClass.addMethod(proceedMethod);
130            invocationClass = ctClass.toClass(classLoader);
131            return invocationClass;
132        }
133    
134        private static Map getClassCache( ClassLoader classLoader )
135        {
136            Map cache = ( Map ) loaderToClassCache.get(classLoader);
137            if( cache == null )
138            {
139                cache = new HashMap();
140                loaderToClassCache.put(classLoader, cache);
141            }
142            return cache;
143        }
144    
145        /**
146         * Returns a method invocation class specifically coded to invoke the supplied interface method.
147         *
148         * @param classLoader     the classloader to use
149         * @param interfaceMethod the interface method
150         * @return a method invocation class specifically coded to invoke the supplied interface method
151         * @throws CannotCompileException if a compilation error occurs
152         */
153        synchronized static Class getMethodInvocationClass( ClassLoader classLoader,
154                                                            Method interfaceMethod )
155                throws CannotCompileException
156        {
157            final Map classCache = getClassCache(classLoader);
158            final String key = toClassCacheKey(interfaceMethod);
159            final WeakReference invocationClassRef = ( WeakReference ) classCache.get(key);
160            Class invocationClass;
161            if( invocationClassRef == null )
162            {
163                invocationClass = createInvocationClass(classLoader, interfaceMethod);
164                classCache.put(key, new WeakReference(invocationClass));
165            }
166            else
167            {
168                synchronized( invocationClassRef )
169                {
170                    invocationClass = ( Class ) invocationClassRef.get();
171                    if( invocationClass == null )
172                    {
173                        invocationClass = createInvocationClass(classLoader, interfaceMethod);
174                        classCache.put(key, new WeakReference(invocationClass));
175                    }
176                }
177            }
178            return invocationClass;
179        }
180    
181        private static String getSimpleName( Class c )
182        {
183            final String name = c.getName();
184            final int ndx = name.lastIndexOf('.');
185            return ndx == -1 ? name : name.substring(ndx + 1);
186        }
187    
188        private static String toClassCacheKey( Method method )
189        {
190            return String.valueOf(method);
191        }
192    
193    //**********************************************************************************************************************
194    // Constructors
195    //**********************************************************************************************************************
196    
197        public JavassistInvocation( Method method, Object target, Object[] arguments )
198        {
199            this.method = method;
200            this.target = target;
201            this.arguments = arguments;
202        }
203    
204    //**********************************************************************************************************************
205    // Invocation Implementation
206    //**********************************************************************************************************************
207    
208        public Object[] getArguments()
209        {
210            return arguments;
211        }
212    
213        public Method getMethod()
214        {
215            return method;
216        }
217    
218        public Object getProxy()
219        {
220            return target;
221        }
222    }
223