1 package org.apache.velocity.util.introspection; 2 3 /* 4 * Licensed to the Apache Software Foundation (ASF) under one 5 * or more contributor license agreements. See the NOTICE file 6 * distributed with this work for additional information 7 * regarding copyright ownership. The ASF licenses this file 8 * to you under the Apache License, Version 2.0 (the 9 * "License"); you may not use this file except in compliance 10 * with the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, 15 * software distributed under the License is distributed on an 16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 * KIND, either express or implied. See the License for the 18 * specific language governing permissions and limitations 19 * under the License. 20 */ 21 22 import org.slf4j.Logger; 23 24 import java.lang.reflect.Method; 25 import java.lang.reflect.Modifier; 26 import java.util.HashMap; 27 import java.util.Map; 28 import java.util.concurrent.ConcurrentHashMap; 29 30 /** 31 * A cache of introspection information for a specific class instance. 32 * Keys {@link java.lang.reflect.Method} objects by a concatenation of the 33 * method name and the names of classes that make up the parameters. 34 * 35 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> 36 * @author <a href="mailto:bob@werken.com">Bob McWhirter</a> 37 * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a> 38 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> 39 * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a> 40 * @author Nathan Bubna 41 * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a> 42 * @version $Id$ 43 */ 44 public class ClassMap 45 { 46 /** Set true if you want to debug the reflection code */ 47 private static final boolean debugReflection = false; 48 49 /** Class logger */ 50 private final Logger log; 51 52 /** 53 * Class passed into the constructor used to as 54 * the basis for the Method map. 55 */ 56 private final Class<?> clazz; 57 58 private final MethodCache methodCache; 59 60 /** 61 * Standard constructor 62 * @param clazz The class for which this ClassMap gets constructed. 63 * @param log logger 64 */ ClassMap(final Class<?> clazz, final Logger log)65 public ClassMap(final Class<?> clazz, final Logger log) 66 { 67 this(clazz, log, null); 68 } 69 70 /** 71 * Standard constructor 72 * @param clazz The class for which this ClassMap gets constructed. 73 * @param log logger 74 * @param conversionHandler conversion handler 75 * @since 2.0 76 */ ClassMap(final Class<?> clazz, final Logger log, final TypeConversionHandler conversionHandler)77 public ClassMap(final Class<?> clazz, final Logger log, final TypeConversionHandler conversionHandler) 78 { 79 this.clazz = clazz; 80 this.log = log; 81 82 if (debugReflection) 83 { 84 log.debug("================================================================="); 85 log.debug("== Class: {}", clazz); 86 } 87 88 methodCache = createMethodCache(conversionHandler); 89 90 if (debugReflection) 91 { 92 log.debug("================================================================="); 93 } 94 } 95 96 /** 97 * Returns the class object whose methods are cached by this map. 98 * 99 * @return The class object whose methods are cached by this map. 100 */ getCachedClass()101 public Class<?> getCachedClass() 102 { 103 return clazz; 104 } 105 106 /** 107 * Find a Method using the method name and parameter objects. 108 * 109 * @param name The method name to look up. 110 * @param params An array of parameters for the method. 111 * @return A Method object representing the method to invoke or null. 112 * @throws MethodMap.AmbiguousException When more than one method is a match for the parameters. 113 */ findMethod(final String name, final Object[] params)114 public Method findMethod(final String name, final Object[] params) 115 throws MethodMap.AmbiguousException 116 { 117 return methodCache.get(name, params); 118 } 119 120 /** 121 * Populate the Map of direct hits. These 122 * are taken from all the public methods 123 * that our class, its parents and their implemented interfaces provide. 124 */ createMethodCache(TypeConversionHandler conversionHandler)125 private MethodCache createMethodCache(TypeConversionHandler conversionHandler) 126 { 127 MethodCache methodCache = new MethodCache(log, conversionHandler); 128 // 129 // Looks through all elements in the class hierarchy. This one is bottom-first (i.e. we start 130 // with the actual declaring class and its interfaces and then move up (superclass etc.) until we 131 // hit java.lang.Object. That is important because it will give us the methods of the declaring class 132 // which might in turn be abstract further up the tree. 133 // 134 // We also ignore all SecurityExceptions that might happen due to SecurityManager restrictions (prominently 135 // hit with Tomcat 5.5). 136 // 137 // We can also omit all that complicated getPublic, getAccessible and upcast logic that the class map had up 138 // until Velocity 1.4. As we always reflect all elements of the tree (that's what we have a cache for), we will 139 // hit the public elements sooner or later because we reflect all the public elements anyway. 140 // 141 // Ah, the miracles of Java for(;;) ... 142 for (Class<?> classToReflect = getCachedClass(); classToReflect != null ; classToReflect = classToReflect.getSuperclass()) 143 { 144 if (Modifier.isPublic(classToReflect.getModifiers())) 145 { 146 populateMethodCacheWith(methodCache, classToReflect); 147 } 148 Class<?> [] interfaces = classToReflect.getInterfaces(); 149 for (Class<?> anInterface : interfaces) 150 { 151 populateMethodCacheWithInterface(methodCache, anInterface); 152 } 153 } 154 // return the already initialized cache 155 return methodCache; 156 } 157 158 /* recurses up interface heirarchy to get all super interfaces (VELOCITY-689) */ populateMethodCacheWithInterface(MethodCache methodCache, Class<?> iface)159 private void populateMethodCacheWithInterface(MethodCache methodCache, Class<?> iface) 160 { 161 if (Modifier.isPublic(iface.getModifiers())) 162 { 163 populateMethodCacheWith(methodCache, iface); 164 } 165 Class<?>[] supers = iface.getInterfaces(); 166 for (Class<?> aSuper : supers) 167 { 168 populateMethodCacheWithInterface(methodCache, aSuper); 169 } 170 } 171 populateMethodCacheWith(MethodCache methodCache, Class<?> classToReflect)172 private void populateMethodCacheWith(MethodCache methodCache, Class<?> classToReflect) 173 { 174 if (debugReflection) 175 { 176 log.debug("Reflecting {}", classToReflect); 177 } 178 179 try 180 { 181 Method[] methods = classToReflect.getDeclaredMethods(); 182 for (Method method : methods) 183 { 184 int modifiers = method.getModifiers(); 185 if (Modifier.isPublic(modifiers)) 186 { 187 methodCache.put(method); 188 } 189 } 190 } 191 catch (SecurityException se) // Everybody feels better with... 192 { 193 log.debug("While accessing methods of {}:", classToReflect, se); 194 } 195 } 196 197 /** 198 * This is the cache to store and look up the method information. 199 * 200 * @author <a href="mailto:henning@apache.org">Henning P. Schmiedehausen</a> 201 * @version $Id$ 202 */ 203 private static final class MethodCache 204 { 205 private static final Object CACHE_MISS = new Object(); 206 207 private static final String NULL_ARG = Object.class.getName(); 208 209 private static final Map<Class<?>, String> convertPrimitives = new HashMap(); 210 211 static 212 { convertPrimitives.put(Boolean.TYPE, Boolean.class.getName())213 convertPrimitives.put(Boolean.TYPE, Boolean.class.getName()); convertPrimitives.put(Byte.TYPE, Byte.class.getName())214 convertPrimitives.put(Byte.TYPE, Byte.class.getName()); convertPrimitives.put(Character.TYPE, Character.class.getName())215 convertPrimitives.put(Character.TYPE, Character.class.getName()); convertPrimitives.put(Double.TYPE, Double.class.getName())216 convertPrimitives.put(Double.TYPE, Double.class.getName()); convertPrimitives.put(Float.TYPE, Float.class.getName())217 convertPrimitives.put(Float.TYPE, Float.class.getName()); convertPrimitives.put(Integer.TYPE, Integer.class.getName())218 convertPrimitives.put(Integer.TYPE, Integer.class.getName()); convertPrimitives.put(Long.TYPE, Long.class.getName())219 convertPrimitives.put(Long.TYPE, Long.class.getName()); convertPrimitives.put(Short.TYPE, Short.class.getName())220 convertPrimitives.put(Short.TYPE, Short.class.getName()); 221 } 222 223 /** Class logger */ 224 private final Logger log; 225 226 /** 227 * Cache of Methods, or CACHE_MISS, keyed by method 228 * name and actual arguments used to find it. 229 */ 230 private final Map<Object, Object> cache = new ConcurrentHashMap<>(); 231 232 /** Map of methods that are searchable according to method parameters to find a match */ 233 private final MethodMap methodMap; 234 MethodCache(Logger log, TypeConversionHandler conversionHandler)235 private MethodCache(Logger log, TypeConversionHandler conversionHandler) 236 { 237 this.log = log; 238 methodMap = new MethodMap(conversionHandler); 239 } 240 241 /** 242 * Find a Method using the method name and parameter objects. 243 * 244 * Look in the methodMap for an entry. If found, 245 * it'll either be a CACHE_MISS, in which case we 246 * simply give up, or it'll be a Method, in which 247 * case, we return it. 248 * 249 * If nothing is found, then we must actually go 250 * and introspect the method from the MethodMap. 251 * 252 * @param name The method name to look up. 253 * @param params An array of parameters for the method. 254 * @return A Method object representing the method to invoke or null. 255 * @throws MethodMap.AmbiguousException When more than one method is a match for the parameters. 256 */ get(final String name, final Object [] params)257 public Method get(final String name, final Object [] params) 258 throws MethodMap.AmbiguousException 259 { 260 String methodKey = makeMethodKey(name, params); 261 262 Object cacheEntry = cache.get(methodKey); 263 if (cacheEntry == CACHE_MISS) 264 { 265 // We looked this up before and failed. 266 return null; 267 } 268 269 if (cacheEntry == null) 270 { 271 try 272 { 273 // That one is expensive... 274 cacheEntry = methodMap.find(name, params); 275 } 276 catch(MethodMap.AmbiguousException ae) 277 { 278 /* 279 * that's a miss :-) 280 */ 281 cache.put(methodKey, CACHE_MISS); 282 throw ae; 283 } 284 285 cache.put(methodKey, 286 (cacheEntry != null) ? cacheEntry : CACHE_MISS); 287 } 288 289 // Yes, this might just be null. 290 return (Method) cacheEntry; 291 } 292 put(Method method)293 private void put(Method method) 294 { 295 String methodKey = makeMethodKey(method); 296 297 // We don't overwrite methods because we fill the 298 // cache from defined class towards java.lang.Object 299 // and that would cause overridden methods to appear 300 // as if they were not overridden. 301 if (cache.get(methodKey) == null) 302 { 303 cache.put(methodKey, method); 304 methodMap.add(method); 305 if (debugReflection) 306 { 307 log.debug("Adding {}", method); 308 } 309 } 310 } 311 312 /** 313 * Make a methodKey for the given method using 314 * the concatenation of the name and the 315 * types of the method parameters. 316 * 317 * @param method to be stored as key 318 * @return key for ClassMap 319 */ makeMethodKey(final Method method)320 private String makeMethodKey(final Method method) 321 { 322 Class<?>[] parameterTypes = method.getParameterTypes(); 323 int args = parameterTypes.length; 324 if (args == 0) 325 { 326 return method.getName(); 327 } 328 329 StringBuilder methodKey = new StringBuilder((args+1)*16).append(method.getName()); 330 331 for (Class<?> parameterType : parameterTypes) 332 { 333 /* 334 * If the argument type is primitive then we want 335 * to convert our primitive type signature to the 336 * corresponding Object type so introspection for 337 * methods with primitive types will work correctly. 338 * 339 * The lookup map (convertPrimitives) contains all eight 340 * primitives (boolean, byte, char, double, float, int, long, short) 341 * known to Java. So it should never return null for the key passed in. 342 */ 343 if (parameterType.isPrimitive()) 344 { 345 methodKey.append((String) convertPrimitives.get(parameterType)); 346 } else 347 { 348 methodKey.append(parameterType.getName()); 349 } 350 } 351 352 return methodKey.toString(); 353 } 354 makeMethodKey(String method, Object[] params)355 private String makeMethodKey(String method, Object[] params) 356 { 357 int args = params.length; 358 if (args == 0) 359 { 360 return method; 361 } 362 363 StringBuilder methodKey = new StringBuilder((args+1)*16).append(method); 364 365 for (Object arg : params) 366 { 367 if (arg == null) 368 { 369 methodKey.append(NULL_ARG); 370 } 371 else 372 { 373 methodKey.append(arg.getClass().getName()); 374 } 375 } 376 377 return methodKey.toString(); 378 } 379 } 380 } 381