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