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