View Javadoc
1   package org.apache.maven.shared.utils.reflection;
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.Constructor;
23  import java.lang.reflect.Field;
24  import java.lang.reflect.InvocationTargetException;
25  import java.lang.reflect.Member;
26  import java.lang.reflect.Method;
27  import java.util.HashMap;
28  import java.util.Map;
29  
30  /**
31   * Utility class used to instantiate an object using reflection. This utility hides many of the gory details needed to
32   * do this.
33   * 
34   * @author John Casey
35   */
36  final class Reflector
37  {
38      private static final String CONSTRUCTOR_METHOD_NAME = "$$CONSTRUCTOR$$";
39  
40      private static final String GET_INSTANCE_METHOD_NAME = "getInstance";
41  
42      private final Map<String, Map<String, Map<String, Member>>> classMaps =
43          new HashMap<String, Map<String, Map<String, Member>>>();
44  
45      /**
46       * Ensure no instances of Reflector are created...this is a utility.
47       */
48      public Reflector()
49      {
50      }
51  
52      /**
53       * Create a new instance of a class, given the array of parameters... Uses constructor caching to find a constructor
54       * that matches the parameter types, either specifically (first choice) or abstractly...
55       * 
56       * @param theClass The class to instantiate
57       * @param params The parameters to pass to the constructor
58       * @return The instantiated object
59       * @throws ReflectorException In case anything goes wrong here...
60       */
61      public Object newInstance( Class<?> theClass, Object... params )
62          throws ReflectorException
63      {
64          if ( params == null )
65          {
66              params = new Object[0];
67          }
68  
69          Class<?>[] paramTypes = new Class[params.length];
70  
71          for ( int i = 0, len = params.length; i < len; i++ )
72          {
73              paramTypes[i] = params[i].getClass();
74          }
75  
76          try
77          {
78              Constructor<?> con = getConstructor( theClass, paramTypes );
79  
80              return con.newInstance( params );
81          }
82          catch ( InstantiationException ex )
83          {
84              throw new ReflectorException( ex );
85          }
86          catch ( InvocationTargetException ex )
87          {
88              throw new ReflectorException( ex );
89          }
90          catch ( IllegalAccessException ex )
91          {
92              throw new ReflectorException( ex );
93          }
94      }
95  
96      /**
97       * Retrieve the singleton instance of a class, given the array of parameters... Uses constructor caching to find a
98       * constructor that matches the parameter types, either specifically (first choice) or abstractly...
99       * 
100      * @param theClass The class to retrieve the singleton of
101      * @param initParams The parameters to pass to the constructor
102      * @return The singleton object
103      * @throws ReflectorException In case anything goes wrong here...
104      */
105     public Object getSingleton( Class<?> theClass, Object... initParams )
106         throws ReflectorException
107     {
108         Class<?>[] paramTypes = new Class[initParams.length];
109 
110         for ( int i = 0, len = initParams.length; i < len; i++ )
111         {
112             paramTypes[i] = initParams[i].getClass();
113         }
114 
115         try
116         {
117             Method method = getMethod( theClass, GET_INSTANCE_METHOD_NAME, paramTypes );
118 
119             return method.invoke( null, initParams );
120         }
121         catch ( InvocationTargetException ex )
122         {
123             throw new ReflectorException( ex );
124         }
125         catch ( IllegalAccessException ex )
126         {
127             throw new ReflectorException( ex );
128         }
129     }
130 
131     /**
132      * Invoke the specified method on the specified target with the specified params...
133      * 
134      * @param target The target of the invocation
135      * @param methodName The method name to invoke
136      * @param params The parameters to pass to the method invocation
137      * @return The result of the method call
138      * @throws ReflectorException In case of an error looking up or invoking the method.
139      */
140     public Object invoke( Object target, String methodName, Object... params )
141         throws ReflectorException
142     {
143         if ( params == null )
144         {
145             params = new Object[0];
146         }
147 
148         Class<?>[] paramTypes = new Class[params.length];
149 
150         for ( int i = 0, len = params.length; i < len; i++ )
151         {
152             paramTypes[i] = params[i].getClass();
153         }
154 
155         try
156         {
157             Method method = getMethod( target.getClass(), methodName, paramTypes );
158 
159             return method.invoke( target, params );
160         }
161         catch ( InvocationTargetException ex )
162         {
163             throw new ReflectorException( ex );
164         }
165         catch ( IllegalAccessException ex )
166         {
167             throw new ReflectorException( ex );
168         }
169     }
170 
171     public Object getStaticField( Class<?> targetClass, String fieldName )
172         throws ReflectorException
173     {
174         try
175         {
176             Field field = targetClass.getField( fieldName );
177 
178             return field.get( null );
179         }
180         catch ( SecurityException e )
181         {
182             throw new ReflectorException( e );
183         }
184         catch ( NoSuchFieldException e )
185         {
186             throw new ReflectorException( e );
187         }
188         catch ( IllegalArgumentException e )
189         {
190             throw new ReflectorException( e );
191         }
192         catch ( IllegalAccessException e )
193         {
194             throw new ReflectorException( e );
195         }
196     }
197 
198     public Object getField( Object target, String fieldName )
199         throws ReflectorException
200     {
201         return getField( target, fieldName, false );
202     }
203 
204     public Object getField( Object target, String fieldName, boolean breakAccessibility )
205         throws ReflectorException
206     {
207         Class<?> targetClass = target.getClass();
208         while ( targetClass != null )
209         {
210             try
211             {
212                 Field field = targetClass.getDeclaredField( fieldName );
213 
214                 boolean accessibilityBroken = false;
215                 if ( !field.isAccessible() && breakAccessibility )
216                 {
217                     field.setAccessible( true );
218                     accessibilityBroken = true;
219                 }
220 
221                 Object result = field.get( target );
222 
223                 if ( accessibilityBroken )
224                 {
225                     field.setAccessible( false );
226                 }
227 
228                 return result;
229             }
230             catch ( SecurityException e )
231             {
232                 throw new ReflectorException( e );
233             }
234             catch ( NoSuchFieldException e )
235             {
236                 if ( targetClass == Object.class )
237                 {
238                     throw new ReflectorException( e );
239                 }
240                 targetClass = targetClass.getSuperclass();
241             }
242             catch ( IllegalAccessException e )
243             {
244                 throw new ReflectorException( e );
245             }
246         }
247         // Never reached, but needed to satisfy compiler
248         return null;
249     }
250 
251     /**
252      * Invoke the specified static method with the specified params...
253      * 
254      * @param targetClass The target class of the invocation
255      * @param methodName The method name to invoke
256      * @param params The parameters to pass to the method invocation
257      * @return The result of the method call
258      * @throws ReflectorException In case of an error looking up or invoking the method.
259      */
260     public Object invokeStatic( Class<?> targetClass, String methodName, Object... params )
261         throws ReflectorException
262     {
263         if ( params == null )
264         {
265             params = new Object[0];
266         }
267 
268         Class<?>[] paramTypes = new Class[params.length];
269 
270         for ( int i = 0, len = params.length; i < len; i++ )
271         {
272             paramTypes[i] = params[i].getClass();
273         }
274 
275         try
276         {
277             Method method = getMethod( targetClass, methodName, paramTypes );
278 
279             return method.invoke( null, params );
280         }
281         catch ( InvocationTargetException ex )
282         {
283             throw new ReflectorException( ex );
284         }
285         catch ( IllegalAccessException ex )
286         {
287             throw new ReflectorException( ex );
288         }
289     }
290 
291     /**
292      * Return the constructor, checking the cache first and storing in cache if not already there..
293      * 
294      * @param targetClass The class to get the constructor from
295      * @param params The classes of the parameters which the constructor should match.
296      * @return the Constructor object that matches, never {@code null}
297      * @throws ReflectorException In case we can't retrieve the proper constructor.
298      */
299     public Constructor<?> getConstructor( Class<?> targetClass, Class<?>... params )
300         throws ReflectorException
301     {
302         Map<String, Member> constructorMap = getConstructorMap( targetClass );
303 
304         @SuppressWarnings( "checkstyle:magicnumber" )
305         StringBuilder key = new StringBuilder( 200 );
306 
307         key.append( "(" );
308 
309         for ( Class<?> param : params )
310         {
311             key.append( param.getName() );
312             key.append( "," );
313         }
314 
315         if ( params.length > 0 )
316         {
317             key.setLength( key.length() - 1 );
318         }
319 
320         key.append( ")" );
321 
322         Constructor<?> constructor;
323 
324         String paramKey = key.toString();
325 
326         synchronized ( paramKey.intern() )
327         {
328             constructor = (Constructor<?>) constructorMap.get( paramKey );
329 
330             if ( constructor == null )
331             {
332                 Constructor<?>[] cands = targetClass.getConstructors();
333 
334                 for ( Constructor<?> cand : cands )
335                 {
336                     Class<?>[] types = cand.getParameterTypes();
337 
338                     if ( params.length != types.length )
339                     {
340                         continue;
341                     }
342 
343                     for ( int j = 0, len2 = params.length; j < len2; j++ )
344                     {
345                         if ( !types[j].isAssignableFrom( params[j] ) )
346                         {
347                             continue;
348                         }
349                     }
350 
351                     // we got it, so store it!
352                     constructor = cand;
353                     constructorMap.put( paramKey, constructor );
354                 }
355             }
356         }
357 
358         if ( constructor == null )
359         {
360             throw new ReflectorException( "Error retrieving constructor object for: " + targetClass.getName()
361                 + paramKey );
362         }
363 
364         return constructor;
365     }
366 
367     public Object getObjectProperty( Object target, String propertyName )
368         throws ReflectorException
369     {
370         Object returnValue;
371 
372         if ( propertyName == null || propertyName.trim().length() < 1 )
373         {
374             throw new ReflectorException( "Cannot retrieve value for empty property." );
375         }
376 
377         String beanAccessor = "get" + Character.toUpperCase( propertyName.charAt( 0 ) );
378         if ( propertyName.trim().length() > 1 )
379         {
380             beanAccessor += propertyName.substring( 1 ).trim();
381         }
382 
383         Class<?> targetClass = target.getClass();
384         Class<?>[] emptyParams = {};
385 
386         Method method = _getMethod( targetClass, beanAccessor, emptyParams );
387         if ( method == null )
388         {
389             method = _getMethod( targetClass, propertyName, emptyParams );
390         }
391 
392         if ( method != null )
393         {
394             try
395             {
396                 returnValue = method.invoke( target, new Object[] {} );
397             }
398             catch ( IllegalAccessException e )
399             {
400                 throw new ReflectorException( "Error retrieving property \'" + propertyName + "\' from \'"
401                     + targetClass + "\'", e );
402             }
403             catch ( InvocationTargetException e )
404             {
405                 throw new ReflectorException( "Error retrieving property \'" + propertyName + "\' from \'"
406                     + targetClass + "\'", e );
407             }
408         }
409         else
410         {
411             returnValue = getField( target, propertyName, true );
412             if ( returnValue == null )
413             {
414                 // TODO: Check if exception is the right action! Field exists, but contains null
415                 throw new ReflectorException( "Neither method: \'" + propertyName + "\' nor bean accessor: \'"
416                     + beanAccessor + "\' can be found for class: \'" + targetClass + "\', and retrieval of field: \'"
417                     + propertyName + "\' returned null as value." );
418             }
419         }
420 
421         return returnValue;
422     }
423 
424     /**
425      * Return the method, checking the cache first and storing in cache if not already there..
426      * 
427      * @param targetClass The class to get the method from
428      * @param params The classes of the parameters which the method should match.
429      * @return the Method object that matches, never {@code null}
430      * @throws ReflectorException In case we can't retrieve the proper method.
431      */
432     public Method getMethod( Class<?> targetClass, String methodName, Class<?>... params )
433         throws ReflectorException
434     {
435         Method method = _getMethod( targetClass, methodName, params );
436 
437         if ( method == null )
438         {
439             throw new ReflectorException( "Method: \'" + methodName + "\' not found in class: \'" + targetClass
440                                           + "\'" );
441         }
442 
443         return method;
444     }
445 
446     @SuppressWarnings( "checkstyle:methodname" )
447     private Method _getMethod( Class<?> targetClass, String methodName, Class<?>... params )
448         throws ReflectorException
449     {
450         Map<String, Member> methodMap = getMethodMap( targetClass, methodName );
451 
452         @SuppressWarnings( "checkstyle:magicnumber" )
453         StringBuilder key = new StringBuilder( 200 );
454 
455         key.append( "(" );
456 
457         for ( Class<?> param : params )
458         {
459             key.append( param.getName() );
460             key.append( "," );
461         }
462 
463         key.append( ")" );
464 
465         Method method;
466 
467         String paramKey = key.toString();
468 
469         synchronized ( paramKey.intern() )
470         {
471             method = (Method) methodMap.get( paramKey );
472 
473             if ( method == null )
474             {
475                 Method[] cands = targetClass.getMethods();
476 
477                 for ( Method cand : cands )
478                 {
479                     String name = cand.getName();
480 
481                     if ( !methodName.equals( name ) )
482                     {
483                         continue;
484                     }
485 
486                     Class<?>[] types = cand.getParameterTypes();
487 
488                     if ( params.length != types.length )
489                     {
490                         continue;
491                     }
492 
493                     for ( int j = 0, len2 = params.length; j < len2; j++ )
494                     {
495                         if ( !types[j].isAssignableFrom( params[j] ) )
496                         {
497                             continue;
498                         }
499                     }
500 
501                     // we got it, so store it!
502                     method = cand;
503                     methodMap.put( paramKey, method );
504                 }
505             }
506         }
507 
508         return method;
509     }
510 
511     /**
512      * Retrieve the cache of constructors for the specified class.
513      * 
514      * @param theClass the class to lookup.
515      * @return The cache of constructors.
516      * @throws ReflectorException in case of a lookup error.
517      */
518     private Map<String, Member> getConstructorMap( Class<?> theClass )
519         throws ReflectorException
520     {
521         return getMethodMap( theClass, CONSTRUCTOR_METHOD_NAME );
522     }
523 
524     /**
525      * Retrieve the cache of methods for the specified class and method name.
526      * 
527      * @param theClass the class to lookup.
528      * @param methodName The name of the method to lookup.
529      * @return The cache of constructors.
530      */
531     private Map<String, Member> getMethodMap( Class<?> theClass, String methodName )
532     {
533         Map<String, Member> methodMap;
534 
535         if ( theClass == null )
536         {
537             return null;
538         }
539 
540         String className = theClass.getName();
541 
542         synchronized ( className.intern() )
543         {
544             Map<String, Map<String, Member>> classMethods = classMaps.get( className );
545 
546             if ( classMethods == null )
547             {
548                 classMethods = new HashMap<String, Map<String, Member>>();
549                 methodMap = new HashMap<String, Member>();
550                 classMethods.put( methodName, methodMap );
551 
552                 classMaps.put( className, classMethods );
553             }
554             else
555             {
556                 String key = className + "::" + methodName;
557 
558                 synchronized ( key.intern() )
559                 {
560                     methodMap = classMethods.get( methodName );
561 
562                     if ( methodMap == null )
563                     {
564                         methodMap = new HashMap<String, Member>();
565                         classMethods.put( methodName, methodMap );
566                     }
567                 }
568             }
569         }
570 
571         return methodMap;
572     }
573 }