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