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