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         StringBuilder key = new StringBuilder( 200 );
305 
306         key.append( "(" );
307 
308         for ( Class<?> param : params )
309         {
310             key.append( param.getName() );
311             key.append( "," );
312         }
313 
314         if ( params.length > 0 )
315         {
316             key.setLength( key.length() - 1 );
317         }
318 
319         key.append( ")" );
320 
321         Constructor<?> constructor;
322 
323         String paramKey = key.toString();
324 
325         synchronized ( paramKey.intern() )
326         {
327             constructor = (Constructor<?>) constructorMap.get( paramKey );
328 
329             if ( constructor == null )
330             {
331                 Constructor<?>[] cands = targetClass.getConstructors();
332 
333                 for ( Constructor<?> cand : cands )
334                 {
335                     Class<?>[] types = cand.getParameterTypes();
336 
337                     if ( params.length != types.length )
338                     {
339                         continue;
340                     }
341 
342                     for ( int j = 0, len2 = params.length; j < len2; j++ )
343                     {
344                         if ( !types[j].isAssignableFrom( params[j] ) )
345                         {
346                             continue;
347                         }
348                     }
349 
350                     // we got it, so store it!
351                     constructor = cand;
352                     constructorMap.put( paramKey, constructor );
353                 }
354             }
355         }
356 
357         if ( constructor == null )
358         {
359             throw new ReflectorException( "Error retrieving constructor object for: " + targetClass.getName()
360                 + paramKey );
361         }
362 
363         return constructor;
364     }
365 
366     public Object getObjectProperty( Object target, String propertyName )
367         throws ReflectorException
368     {
369         Object returnValue;
370 
371         if ( propertyName == null || propertyName.trim().length() < 1 )
372         {
373             throw new ReflectorException( "Cannot retrieve value for empty property." );
374         }
375 
376         String beanAccessor = "get" + Character.toUpperCase( propertyName.charAt( 0 ) );
377         if ( propertyName.trim().length() > 1 )
378         {
379             beanAccessor += propertyName.substring( 1 ).trim();
380         }
381 
382         Class<?> targetClass = target.getClass();
383         Class<?>[] emptyParams = {};
384 
385         Method method = _getMethod( targetClass, beanAccessor, emptyParams );
386         if ( method == null )
387         {
388             method = _getMethod( targetClass, propertyName, emptyParams );
389         }
390 
391         if ( method != null )
392         {
393             try
394             {
395                 returnValue = method.invoke( target, new Object[] {} );
396             }
397             catch ( IllegalAccessException e )
398             {
399                 throw new ReflectorException( "Error retrieving property \'" + propertyName + "\' from \'"
400                     + targetClass + "\'", e );
401             }
402             catch ( InvocationTargetException e )
403             {
404                 throw new ReflectorException( "Error retrieving property \'" + propertyName + "\' from \'"
405                     + targetClass + "\'", e );
406             }
407         }
408         else
409         {
410             returnValue = getField( target, propertyName, true );
411             if ( returnValue == null )
412             {
413                 // TODO: Check if exception is the right action! Field exists, but contains null
414                 throw new ReflectorException( "Neither method: \'" + propertyName + "\' nor bean accessor: \'"
415                     + beanAccessor + "\' can be found for class: \'" + targetClass + "\', and retrieval of field: \'"
416                     + propertyName + "\' returned null as value." );
417             }
418         }
419 
420         return returnValue;
421     }
422 
423     /**
424      * Return the method, checking the cache first and storing in cache if not already there..
425      * 
426      * @param targetClass The class to get the method from
427      * @param params The classes of the parameters which the method should match.
428      * @return the Method object that matches, never {@code null}
429      * @throws ReflectorException In case we can't retrieve the proper method.
430      */
431     public Method getMethod( Class<?> targetClass, String methodName, Class<?>... params )
432         throws ReflectorException
433     {
434         Method method = _getMethod( targetClass, methodName, params );
435 
436         if ( method == null )
437         {
438             throw new ReflectorException( "Method: \'" + methodName + "\' not found in class: \'" + targetClass + "\'" );
439         }
440 
441         return method;
442     }
443 
444     private Method _getMethod( Class<?> targetClass, String methodName, Class<?>... params )
445         throws ReflectorException
446     {
447         Map<String, Member> methodMap = getMethodMap( targetClass, methodName );
448 
449         StringBuilder key = new StringBuilder( 200 );
450 
451         key.append( "(" );
452 
453         for ( Class<?> param : params )
454         {
455             key.append( param.getName() );
456             key.append( "," );
457         }
458 
459         key.append( ")" );
460 
461         Method method;
462 
463         String paramKey = key.toString();
464 
465         synchronized ( paramKey.intern() )
466         {
467             method = (Method) methodMap.get( paramKey );
468 
469             if ( method == null )
470             {
471                 Method[] cands = targetClass.getMethods();
472 
473                 for ( Method cand : cands )
474                 {
475                     String name = cand.getName();
476 
477                     if ( !methodName.equals( name ) )
478                     {
479                         continue;
480                     }
481 
482                     Class<?>[] types = cand.getParameterTypes();
483 
484                     if ( params.length != types.length )
485                     {
486                         continue;
487                     }
488 
489                     for ( int j = 0, len2 = params.length; j < len2; j++ )
490                     {
491                         if ( !types[j].isAssignableFrom( params[j] ) )
492                         {
493                             continue;
494                         }
495                     }
496 
497                     // we got it, so store it!
498                     method = cand;
499                     methodMap.put( paramKey, method );
500                 }
501             }
502         }
503 
504         return method;
505     }
506 
507     /**
508      * Retrieve the cache of constructors for the specified class.
509      * 
510      * @param theClass the class to lookup.
511      * @return The cache of constructors.
512      * @throws ReflectorException in case of a lookup error.
513      */
514     private Map<String, Member> getConstructorMap( Class<?> theClass )
515         throws ReflectorException
516     {
517         return getMethodMap( theClass, CONSTRUCTOR_METHOD_NAME );
518     }
519 
520     /**
521      * Retrieve the cache of methods for the specified class and method name.
522      * 
523      * @param theClass the class to lookup.
524      * @param methodName The name of the method to lookup.
525      * @return The cache of constructors.
526      */
527     private Map<String, Member> getMethodMap( Class<?> theClass, String methodName )
528     {
529         Map<String, Member> methodMap;
530 
531         if ( theClass == null )
532         {
533             return null;
534         }
535 
536         String className = theClass.getName();
537 
538         synchronized ( className.intern() )
539         {
540             Map<String, Map<String, Member>> classMethods = classMaps.get( className );
541 
542             if ( classMethods == null )
543             {
544                 classMethods = new HashMap<String, Map<String, Member>>();
545                 methodMap = new HashMap<String, Member>();
546                 classMethods.put( methodName, methodMap );
547 
548                 classMaps.put( className, classMethods );
549             }
550             else
551             {
552                 String key = className + "::" + methodName;
553 
554                 synchronized ( key.intern() )
555                 {
556                     methodMap = classMethods.get( methodName );
557 
558                     if ( methodMap == null )
559                     {
560                         methodMap = new HashMap<String, Member>();
561                         classMethods.put( methodName, methodMap );
562                     }
563                 }
564             }
565         }
566 
567         return methodMap;
568     }
569 }