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