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