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 018package org.apache.commons.proxy2.javassist; 019 020import java.lang.ref.WeakReference; 021import java.lang.reflect.Method; 022import java.util.HashMap; 023import java.util.Map; 024import java.util.WeakHashMap; 025 026import javassist.CannotCompileException; 027import javassist.CtClass; 028import javassist.CtConstructor; 029import javassist.CtMethod; 030 031import org.apache.commons.lang3.ArrayUtils; 032import org.apache.commons.lang3.ObjectUtils; 033import org.apache.commons.proxy2.Invocation; 034import org.apache.commons.proxy2.ProxyUtils; 035 036/** 037 * A <a href="http://www.jboss.org/products/javassist">Javassist</a>-based {@link Invocation} implementation. This class 038 * actually serves as the superclass for all <a href="http://www.jboss.org/products/javassist">Javassist</a>-based 039 * method invocations. Subclasses are dynamically created to deal with specific interface methods (they're hard-wired). 040 * 041 * @since 1.0 042 */ 043public abstract class JavassistInvocation implements Invocation 044{ 045 //****************************************************************************************************************** 046 // Fields 047 //****************************************************************************************************************** 048 049 private static WeakHashMap<ClassLoader, Map<String, WeakReference<Class<?>>>> loaderToClassCache 050 = new WeakHashMap<ClassLoader, Map<String, WeakReference<Class<?>>>>(); 051 052 /** The proxy object */ 053 private final Object proxy; 054 055 /** The target object */ 056 private final Object target; 057 058 /** The invoked method */ 059 private final Method method; 060 061 /** The method arguments */ 062 private final Object[] arguments; 063 064 //****************************************************************************************************************** 065 // Static Methods 066 //****************************************************************************************************************** 067 068 private static String createCastExpression(Class<?> type, String objectToCast) 069 { 070 if (!type.isPrimitive()) 071 { 072 return "( " + ProxyUtils.getJavaClassName(type) + " )" + objectToCast; 073 } 074 else 075 { 076 return "( ( " + ProxyUtils.getWrapperClass(type).getName() + " )" + objectToCast + " )." + type.getName() 077 + "Value()"; 078 } 079 } 080 081 private static Class<?> createInvocationClass(ClassLoader classLoader, Method interfaceMethod) 082 throws CannotCompileException 083 { 084 final CtClass ctClass = JavassistUtils.createClass(getSimpleName(interfaceMethod.getDeclaringClass()) + "_" 085 + interfaceMethod.getName() + "_invocation", JavassistInvocation.class); 086 final CtConstructor constructor = new CtConstructor(JavassistUtils.resolve(new Class[] { Object.class, 087 Object.class, Method.class, Object[].class }), ctClass); 088 constructor.setBody("{\n\tsuper($$);\n}"); 089 ctClass.addConstructor(constructor); 090 final CtMethod proceedMethod = new CtMethod(JavassistUtils.resolve(Object.class), "proceed", 091 JavassistUtils.resolve(new Class[0]), ctClass); 092 final Class<?>[] argumentTypes = interfaceMethod.getParameterTypes(); 093 final StringBuilder proceedBody = new StringBuilder("{\n"); 094 if (!Void.TYPE.equals(interfaceMethod.getReturnType())) 095 { 096 proceedBody.append("\treturn "); 097 if (interfaceMethod.getReturnType().isPrimitive()) 098 { 099 proceedBody.append("new "); 100 proceedBody.append(ProxyUtils.getWrapperClass(interfaceMethod.getReturnType()).getName()); 101 proceedBody.append("( "); 102 } 103 } 104 else 105 { 106 proceedBody.append("\t"); 107 } 108 proceedBody.append("( ("); 109 proceedBody.append(ProxyUtils.getJavaClassName(interfaceMethod.getDeclaringClass())); 110 proceedBody.append(" )getTarget() )."); 111 proceedBody.append(interfaceMethod.getName()); 112 proceedBody.append("("); 113 for (int i = 0; i < argumentTypes.length; ++i) 114 { 115 final Class<?> argumentType = argumentTypes[i]; 116 proceedBody.append(createCastExpression(argumentType, "getArguments()[" + i + "]")); 117 if (i != argumentTypes.length - 1) 118 { 119 proceedBody.append(", "); 120 } 121 } 122 if (!Void.TYPE.equals(interfaceMethod.getReturnType()) && interfaceMethod.getReturnType().isPrimitive()) 123 { 124 proceedBody.append(") );\n"); 125 } 126 else 127 { 128 proceedBody.append(");\n"); 129 } 130 if (Void.TYPE.equals(interfaceMethod.getReturnType())) 131 { 132 proceedBody.append("\treturn null;\n"); 133 } 134 proceedBody.append("}"); 135 final String body = proceedBody.toString(); 136 proceedMethod.setBody(body); 137 ctClass.addMethod(proceedMethod); 138 139 @SuppressWarnings("deprecation") 140 final Class<?> invocationClass = ctClass.toClass(classLoader); 141 return invocationClass; 142 } 143 144 private static Map<String, WeakReference<Class<?>>> getClassCache(ClassLoader classLoader) 145 { 146 Map<String, WeakReference<Class<?>>> cache = loaderToClassCache.get(classLoader); 147 if (cache == null) 148 { 149 cache = new HashMap<String, WeakReference<Class<?>>>(); 150 loaderToClassCache.put(classLoader, cache); 151 } 152 return cache; 153 } 154 155 /** 156 * Returns a method invocation class specifically coded to invoke the supplied interface method. 157 * 158 * @param classLoader 159 * the classloader to use 160 * @param interfaceMethod 161 * the interface method 162 * @return a method invocation class specifically coded to invoke the supplied interface method 163 * @throws CannotCompileException 164 * if a compilation error occurs 165 */ 166 static synchronized Class<?> getMethodInvocationClass(ClassLoader classLoader, Method interfaceMethod) 167 throws CannotCompileException 168 { 169 final Map<String, WeakReference<Class<?>>> classCache = getClassCache(classLoader); 170 final String key = toClassCacheKey(interfaceMethod); 171 final WeakReference<Class<?>> invocationClassRef = classCache.get(key); 172 Class<?> invocationClass; 173 if (invocationClassRef == null) 174 { 175 invocationClass = createInvocationClass(classLoader, interfaceMethod); 176 classCache.put(key, new WeakReference<Class<?>>(invocationClass)); 177 } 178 else 179 { 180 synchronized (invocationClassRef) 181 { 182 invocationClass = invocationClassRef.get(); 183 if (invocationClass == null) 184 { 185 invocationClass = createInvocationClass(classLoader, interfaceMethod); 186 classCache.put(key, new WeakReference<Class<?>>(invocationClass)); 187 } 188 } 189 } 190 return invocationClass; 191 } 192 193 private static String getSimpleName(Class<?> c) 194 { 195 final String name = c.getName(); 196 final int ndx = name.lastIndexOf('.'); 197 return ndx == -1 ? name : name.substring(ndx + 1); 198 } 199 200 private static String toClassCacheKey(Method method) 201 { 202 return String.valueOf(method); 203 } 204 205 //****************************************************************************************************************** 206 // Constructors 207 //****************************************************************************************************************** 208 209 protected JavassistInvocation(Object proxy, Object target, Method method, Object[] arguments) 210 { 211 this.proxy = proxy; 212 this.target = target; 213 this.method = method; 214 this.arguments = ObjectUtils.defaultIfNull(ArrayUtils.clone(arguments), ProxyUtils.EMPTY_ARGUMENTS); 215 } 216 217 //****************************************************************************************************************** 218 // Invocation Implementation 219 //****************************************************************************************************************** 220 protected final Object getTarget() 221 { 222 return target; 223 } 224 225 @Override 226 public Object[] getArguments() 227 { 228 return arguments; 229 } 230 231 @Override 232 public Method getMethod() 233 { 234 return method; 235 } 236 237 @Override 238 public Object getProxy() 239 { 240 return proxy; 241 } 242}