| %line | %branch | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| org.apache.commons.jexl.util.introspection.ClassMap$MethodInfo | 
 | 
 | 
| 1 |  /* | |
| 2 |   * Copyright 2001-2002,2004 The Apache Software Foundation. | |
| 3 |   * | |
| 4 |   * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 |   * you may not use this file except in compliance with the License. | |
| 6 |   * You may obtain a copy of the License at | |
| 7 |   * | |
| 8 |   *      http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 |   * | |
| 10 |   * Unless required by applicable law or agreed to in writing, software | |
| 11 |   * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 |   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 |   * See the License for the specific language governing permissions and | |
| 14 |   * limitations under the License. | |
| 15 |   */ | |
| 16 | ||
| 17 |  package org.apache.commons.jexl.util.introspection; | |
| 18 | ||
| 19 |  import java.lang.reflect.Method; | |
| 20 |  import java.lang.reflect.Modifier; | |
| 21 |  import java.util.Hashtable; | |
| 22 |  import java.util.Map; | |
| 23 | ||
| 24 |  /** | |
| 25 |   * Taken from the Velocity tree so we can be self-sufficient | |
| 26 |   *  | |
| 27 |   * A cache of introspection information for a specific class instance. Keys | |
| 28 |   * {@link java.lang.Method} objects by a concatenation of the method name and | |
| 29 |   * the names of classes that make up the parameters. | |
| 30 |   *  | |
| 31 |   * @since 1.0 | |
| 32 |   * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> | |
| 33 |   * @author <a href="mailto:bob@werken.com">Bob McWhirter</a> | |
| 34 |   * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a> | |
| 35 |   * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> | |
| 36 |   * @version $Id: ClassMap.java 398459 2006-04-30 23:14:30Z dion $ | |
| 37 |   */ | |
| 38 | public class ClassMap { | |
| 39 |      /** represents a miss on the cached data. */ | |
| 40 | private static final class CacheMiss { | |
| 41 | } | |
| 42 | ||
| 43 |      /** constant for a miss on the cached data. */ | |
| 44 | private static final CacheMiss CACHE_MISS = new CacheMiss(); | |
| 45 | ||
| 46 |      /** represents null or missing arguments. */ | |
| 47 | private static final Object OBJECT = new Object(); | |
| 48 | ||
| 49 |      /** | |
| 50 |       * Class passed into the constructor used to as the basis for the Method | |
| 51 |       * map. | |
| 52 |       */ | |
| 53 | ||
| 54 |      private Class clazz; | |
| 55 | ||
| 56 |      /** | |
| 57 |       * Cache of Methods, or CACHE_MISS, keyed by method name and actual | |
| 58 |       * arguments used to find it. | |
| 59 |       */ | |
| 60 | private final Map methodCache = new Hashtable(); | |
| 61 | ||
| 62 |      /** map from method name and args to a {@link Method}. */ | |
| 63 | private final MethodMap methodMap = new MethodMap(); | |
| 64 | ||
| 65 |      /** | |
| 66 |       * Standard constructor. | |
| 67 |       * @param aClass the class to deconstruct.  | |
| 68 |       */ | |
| 69 |      public ClassMap(Class aClass) { | |
| 70 | clazz = aClass; | |
| 71 | populateMethodCache(); | |
| 72 | } | |
| 73 | ||
| 74 |      /** | |
| 75 |       * @return the class object whose methods are cached by this map. | |
| 76 |       */ | |
| 77 |      Class getCachedClass() { | |
| 78 |          return clazz; | |
| 79 | } | |
| 80 | ||
| 81 |      /** | |
| 82 |       * Find a Method using the methodKey provided. | |
| 83 |       *  | |
| 84 |       * Look in the methodMap for an entry. If found, it'll either be a | |
| 85 |       * CACHE_MISS, in which case we simply give up, or it'll be a Method, in | |
| 86 |       * which case, we return it. | |
| 87 |       *  | |
| 88 |       * If nothing is found, then we must actually go and introspect the method | |
| 89 |       * from the MethodMap. | |
| 90 |       *  | |
| 91 |       * @param name method name | |
| 92 |       * @param params method parameters | |
| 93 |       * @return CACHE_MISS or a {@link Method} | |
| 94 |       * @throws MethodMap.AmbiguousException if the method and parameters are ambiguous. | |
| 95 |       */ | |
| 96 |      public Method findMethod(String name, Object[] params) throws MethodMap.AmbiguousException { | |
| 97 | String methodKey = makeMethodKey(name, params); | |
| 98 | Object cacheEntry = methodCache.get(methodKey); | |
| 99 | ||
| 100 |          if (cacheEntry == CACHE_MISS) { | |
| 101 | return null; | |
| 102 | } | |
| 103 | ||
| 104 | if (cacheEntry == null) { | |
| 105 |              try { | |
| 106 | cacheEntry = methodMap.find(name, params); | |
| 107 |              } catch (MethodMap.AmbiguousException ae) { | |
| 108 |                  /* | |
| 109 |                   * that's a miss :) | |
| 110 |                   */ | |
| 111 | ||
| 112 | methodCache.put(methodKey, CACHE_MISS); | |
| 113 | ||
| 114 |                  throw ae; | |
| 115 | } | |
| 116 | ||
| 117 | if (cacheEntry == null) { | |
| 118 | methodCache.put(methodKey, CACHE_MISS); | |
| 119 |              } else { | |
| 120 | methodCache.put(methodKey, cacheEntry); | |
| 121 | } | |
| 122 | } | |
| 123 | ||
| 124 |          // Yes, this might just be null. | |
| 125 | ||
| 126 |          return (Method) cacheEntry; | |
| 127 | } | |
| 128 | ||
| 129 |      /** | |
| 130 |       * Populate the Map of direct hits. These are taken from all the public | |
| 131 |       * methods that our class provides. | |
| 132 |       */ | |
| 133 | private void populateMethodCache() { | |
| 134 | ||
| 135 |          /* | |
| 136 |           * get all publicly accessible methods | |
| 137 |           */ | |
| 138 | ||
| 139 | Method[] methods = getAccessibleMethods(clazz); | |
| 140 | ||
| 141 |          /* | |
| 142 |           * map and cache them | |
| 143 |           */ | |
| 144 | ||
| 145 | for (int i = 0; i < methods.length; i++) { | |
| 146 | Method method = methods[i]; | |
| 147 | ||
| 148 |              /* | |
| 149 |               * now get the 'public method', the method declared by a public | |
| 150 |               * interface or class. (because the actual implementing class may be | |
| 151 |               * a facade... | |
| 152 |               */ | |
| 153 | ||
| 154 | Method publicMethod = getPublicMethod(method); | |
| 155 | ||
| 156 |              /* | |
| 157 |               * it is entirely possible that there is no public method for the | |
| 158 |               * methods of this class (i.e. in the facade, a method that isn't on | |
| 159 |               * any of the interfaces or superclass in which case, ignore it. | |
| 160 |               * Otherwise, map and cache | |
| 161 |               */ | |
| 162 | ||
| 163 | if (publicMethod != null) { | |
| 164 | methodMap.add(publicMethod); | |
| 165 | methodCache.put(makeMethodKey(publicMethod), publicMethod); | |
| 166 | } | |
| 167 | } | |
| 168 | } | |
| 169 | ||
| 170 |      /** | |
| 171 |       * Make a methodKey for the given method using the concatenation of the name | |
| 172 |       * and the types of the method parameters. | |
| 173 |       */ | |
| 174 |      private String makeMethodKey(Method method) { | |
| 175 | Class[] parameterTypes = method.getParameterTypes(); | |
| 176 | ||
| 177 |          StringBuffer methodKey = new StringBuffer(method.getName()); | |
| 178 | ||
| 179 | for (int j = 0; j < parameterTypes.length; j++) { | |
| 180 |              /* | |
| 181 |               * If the argument type is primitive then we want to convert our | |
| 182 |               * primitive type signature to the corresponding Object type so | |
| 183 |               * introspection for methods with primitive types will work | |
| 184 |               * correctly. | |
| 185 |               */ | |
| 186 |              if (parameterTypes[j].isPrimitive()) { | |
| 187 |                  if (parameterTypes[j].equals(Boolean.TYPE)) | |
| 188 |                      methodKey.append("java.lang.Boolean"); | |
| 189 | else if (parameterTypes[j].equals(Byte.TYPE)) | |
| 190 |                      methodKey.append("java.lang.Byte"); | |
| 191 | else if (parameterTypes[j].equals(Character.TYPE)) | |
| 192 |                      methodKey.append("java.lang.Character"); | |
| 193 | else if (parameterTypes[j].equals(Double.TYPE)) | |
| 194 |                      methodKey.append("java.lang.Double"); | |
| 195 | else if (parameterTypes[j].equals(Float.TYPE)) | |
| 196 |                      methodKey.append("java.lang.Float"); | |
| 197 | else if (parameterTypes[j].equals(Integer.TYPE)) | |
| 198 |                      methodKey.append("java.lang.Integer"); | |
| 199 | else if (parameterTypes[j].equals(Long.TYPE)) | |
| 200 |                      methodKey.append("java.lang.Long"); | |
| 201 | else if (parameterTypes[j].equals(Short.TYPE)) | |
| 202 |                      methodKey.append("java.lang.Short"); | |
| 203 |              } else { | |
| 204 | methodKey.append(parameterTypes[j].getName()); | |
| 205 | } | |
| 206 | } | |
| 207 | ||
| 208 |          return methodKey.toString(); | |
| 209 | } | |
| 210 | ||
| 211 | private static String makeMethodKey(String method, Object[] params) { | |
| 212 |          StringBuffer methodKey = new StringBuffer().append(method); | |
| 213 | ||
| 214 | for (int j = 0; j < params.length; j++) { | |
| 215 | Object arg = params[j]; | |
| 216 | ||
| 217 | if (arg == null) { | |
| 218 | arg = OBJECT; | |
| 219 | } | |
| 220 | ||
| 221 | methodKey.append(arg.getClass().getName()); | |
| 222 | } | |
| 223 | ||
| 224 |          return methodKey.toString(); | |
| 225 | } | |
| 226 | ||
| 227 |      /** | |
| 228 |       * Retrieves public methods for a class. In case the class is not public, | |
| 229 |       * retrieves methods with same signature as its public methods from public | |
| 230 |       * superclasses and interfaces (if they exist). Basically upcasts every | |
| 231 |       * method to the nearest acccessible method. | |
| 232 |       */ | |
| 233 | private static Method[] getAccessibleMethods(Class clazz) { | |
| 234 | Method[] methods = clazz.getMethods(); | |
| 235 | ||
| 236 |          /* | |
| 237 |           * Short circuit for the (hopefully) majority of cases where the clazz | |
| 238 |           * is public | |
| 239 |           */ | |
| 240 | ||
| 241 | if (Modclass="keyword">ifier.isPublic(clazz.getModclass="keyword">ifiers())) { | |
| 242 |              return methods; | |
| 243 | } | |
| 244 | ||
| 245 |          /* | |
| 246 |           * No luck - the class is not public, so we're going the longer way. | |
| 247 |           */ | |
| 248 | ||
| 249 |          MethodInfo[] methodInfos = new MethodInfo[methods.length]; | |
| 250 | ||
| 251 | for (int i = methods.length; i-- > 0;) { | |
| 252 |              methodInfos[i] = new MethodInfo(methods[i]); | |
| 253 | } | |
| 254 | ||
| 255 |          int upcastCount = getAccessibleMethods(clazz, methodInfos, 0); | |
| 256 | ||
| 257 |          /* | |
| 258 |           * Reallocate array in case some method had no accessible counterpart. | |
| 259 |           */ | |
| 260 | ||
| 261 |          if (upcastCount < methods.length) { | |
| 262 |              methods = new Method[upcastCount]; | |
| 263 | } | |
| 264 | ||
| 265 |          int j = 0; | |
| 266 | for (int i = 0; i < methodInfos.length; ++i) { | |
| 267 | MethodInfo methodInfo = methodInfos[i]; | |
| 268 |              if (methodInfo.upcast) { | |
| 269 | methods[j++] = methodInfo.method; | |
| 270 | } | |
| 271 | } | |
| 272 |          return methods; | |
| 273 | } | |
| 274 | ||
| 275 |      /** | |
| 276 |       * Recursively finds a match for each method, starting with the class, and | |
| 277 |       * then searching the superclass and interfaces. | |
| 278 |       *  | |
| 279 |       * @param clazz Class to check | |
| 280 |       * @param methodInfos array of methods we are searching to match | |
| 281 |       * @param upcastCount current number of methods we have matched | |
| 282 |       * @return count of matched methods | |
| 283 |       */ | |
| 284 | private static int getAccessibleMethods(Class clazz, MethodInfo[] methodInfos, class="keyword">int upcastCount) { | |
| 285 |          int l = methodInfos.length; | |
| 286 | ||
| 287 |          /* | |
| 288 |           * if this class is public, then check each of the currently | |
| 289 |           * 'non-upcasted' methods to see if we have a match | |
| 290 |           */ | |
| 291 | ||
| 292 | if (Modclass="keyword">ifier.isPublic(clazz.getModclass="keyword">ifiers())) { | |
| 293 | for (int i = 0; i < l && upcastCount < l; ++i) { | |
| 294 |                  try { | |
| 295 | MethodInfo methodInfo = methodInfos[i]; | |
| 296 | ||
| 297 |                      if (!methodInfo.upcast) { | |
| 298 | methodInfo.tryUpcasting(clazz); | |
| 299 | upcastCount++; | |
| 300 | } | |
| 301 |                  } catch (NoSuchMethodException e) { | |
| 302 |                      /* | |
| 303 |                       * Intentionally ignored - it means it wasn't found in the | |
| 304 |                       * current class | |
| 305 |                       */ | |
| 306 | } | |
| 307 | } | |
| 308 | ||
| 309 |              /* | |
| 310 |               * Short circuit if all methods were upcast | |
| 311 |               */ | |
| 312 | ||
| 313 |              if (upcastCount == l) { | |
| 314 |                  return upcastCount; | |
| 315 | } | |
| 316 | } | |
| 317 | ||
| 318 |          /* | |
| 319 |           * Examine superclass | |
| 320 |           */ | |
| 321 | ||
| 322 | Class superclazz = clazz.getSuperclass(); | |
| 323 | ||
| 324 | if (superclazz != null) { | |
| 325 | upcastCount = getAccessibleMethods(superclazz, methodInfos, upcastCount); | |
| 326 | ||
| 327 |              /* | |
| 328 |               * Short circuit if all methods were upcast | |
| 329 |               */ | |
| 330 | ||
| 331 |              if (upcastCount == l) { | |
| 332 |                  return upcastCount; | |
| 333 | } | |
| 334 | } | |
| 335 | ||
| 336 |          /* | |
| 337 |           * Examine interfaces. Note we do it even if superclazz == null. This is | |
| 338 |           * redundant as currently java.lang.Object does not implement any | |
| 339 |           * interfaces, however nothing guarantees it will not in future. | |
| 340 |           */ | |
| 341 | ||
| 342 | Class[] interfaces = clazz.getInterfaces(); | |
| 343 | ||
| 344 | for (int i = class="keyword">interfaces.length; i-- > 0;) { | |
| 345 | upcastCount = getAccessibleMethods(interfaces[i], methodInfos, upcastCount); | |
| 346 | ||
| 347 |              /* | |
| 348 |               * Short circuit if all methods were upcast | |
| 349 |               */ | |
| 350 | ||
| 351 |              if (upcastCount == l) { | |
| 352 |                  return upcastCount; | |
| 353 | } | |
| 354 | } | |
| 355 | ||
| 356 |          return upcastCount; | |
| 357 | } | |
| 358 | ||
| 359 |      /** | |
| 360 |       * For a given method, retrieves its publicly accessible counterpart. This | |
| 361 |       * method will look for a method with same name and signature declared in a | |
| 362 |       * public superclass or implemented interface of this method's declaring | |
| 363 |       * class. This counterpart method is publicly callable. | |
| 364 |       *  | |
| 365 |       * @param method a method whose publicly callable counterpart is requested. | |
| 366 |       * @return the publicly callable counterpart method. Note that if the | |
| 367 |       *         parameter method is itself declared by a public class, this | |
| 368 |       *         method is an identity function. | |
| 369 |       */ | |
| 370 | public static Method getPublicMethod(Method method) { | |
| 371 | Class clazz = method.getDeclaringClass(); | |
| 372 | ||
| 373 |          /* | |
| 374 |           * Short circuit for (hopefully the majority of) cases where the | |
| 375 |           * declaring class is public. | |
| 376 |           */ | |
| 377 | ||
| 378 | if ((clazz.getModclass="keyword">ifiers() & Modclass="keyword">ifier.PUBLIC) != 0) { | |
| 379 |              return method; | |
| 380 | } | |
| 381 | ||
| 382 |          return getPublicMethod(clazz, method.getName(), method.getParameterTypes()); | |
| 383 | } | |
| 384 | ||
| 385 |      /** | |
| 386 |       * Looks up the method with specified name and signature in the first public | |
| 387 |       * superclass or implemented interface of the class. | |
| 388 |       *  | |
| 389 |       * @param class the class whose method is sought | |
| 390 |       * @param name the name of the method | |
| 391 |       * @param paramTypes the classes of method parameters | |
| 392 |       */ | |
| 393 | private static Method getPublicMethod(Class clazz, String name, Class[] paramTypes) { | |
| 394 |          /* | |
| 395 |           * if this class is public, then try to get it | |
| 396 |           */ | |
| 397 | ||
| 398 | if ((clazz.getModclass="keyword">ifiers() & Modclass="keyword">ifier.PUBLIC) != 0) { | |
| 399 |              try { | |
| 400 |                  return clazz.getMethod(name, paramTypes); | |
| 401 |              } catch (NoSuchMethodException e) { | |
| 402 |                  /* | |
| 403 |                   * If the class does not have the method, then neither its | |
| 404 |                   * superclass nor any of its interfaces has it so quickly return | |
| 405 |                   * null. | |
| 406 |                   */ | |
| 407 | return null; | |
| 408 | } | |
| 409 | } | |
| 410 | ||
| 411 |          /* | |
| 412 |           * try the superclass | |
| 413 |           */ | |
| 414 | ||
| 415 | Class superclazz = clazz.getSuperclass(); | |
| 416 | ||
| 417 | if (superclazz != null) { | |
| 418 | Method superclazzMethod = getPublicMethod(superclazz, name, paramTypes); | |
| 419 | ||
| 420 | if (superclazzMethod != null) { | |
| 421 |                  return superclazzMethod; | |
| 422 | } | |
| 423 | } | |
| 424 | ||
| 425 |          /* | |
| 426 |           * and interfaces | |
| 427 |           */ | |
| 428 | ||
| 429 | Class[] interfaces = clazz.getInterfaces(); | |
| 430 | ||
| 431 | for (int i = 0; i < class="keyword">interfaces.length; ++i) { | |
| 432 | Method interfaceMethod = getPublicMethod(interfaces[i], name, paramTypes); | |
| 433 | ||
| 434 | if (interfaceMethod != null) { | |
| 435 |                  return interfaceMethod; | |
| 436 | } | |
| 437 | } | |
| 438 | ||
| 439 | return null; | |
| 440 | } | |
| 441 | ||
| 442 |      /** | |
| 443 |       * Used for the iterative discovery process for public methods. | |
| 444 |       */ | |
| 445 | private static final class MethodInfo { | |
| 446 | Method method; | |
| 447 | ||
| 448 | String name; | |
| 449 | ||
| 450 | Class[] parameterTypes; | |
| 451 | ||
| 452 |          boolean upcast; | |
| 453 | ||
| 454 | 0 |          MethodInfo(Method method) { | 
| 455 | 0 |              this.method = null; | 
| 456 | 0 |              name = method.getName(); | 
| 457 | 0 |              parameterTypes = method.getParameterTypes(); | 
| 458 | 0 |              upcast = false; | 
| 459 | 0 |          } | 
| 460 | ||
| 461 |          void tryUpcasting(Class clazz) throws NoSuchMethodException { | |
| 462 | 0 |              method = clazz.getMethod(name, parameterTypes); | 
| 463 | 0 |              name = null; | 
| 464 | 0 |              parameterTypes = null; | 
| 465 | 0 |              upcast = true; | 
| 466 | 0 |          } | 
| 467 | } | |
| 468 | } | 
| This report is generated by jcoverage, Maven and Maven JCoverage Plugin. |