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