001    package com.sptci;
002    
003    import java.beans.BeanInfo;
004    import java.beans.Introspector;
005    import java.beans.IntrospectionException;
006    import java.beans.PropertyDescriptor;
007    
008    import java.lang.reflect.Constructor;
009    import java.lang.reflect.Field;
010    import java.lang.reflect.Method;
011    import java.lang.reflect.Modifier;
012    
013    import java.util.Arrays;
014    import java.util.Map;
015    import java.util.HashMap;
016    
017    /**
018     * A utility class to handle common reflection tasks.
019     *
020     * <p>Copyright 2006 Sans Pareil Technologies, Inc.</p>
021     * @author Rakesh Vidyadharan 2006-02-07
022     * @version $Id: ReflectionUtility.java 3296 2007-05-26 11:53:26Z rakesh $
023     */
024    public final class ReflectionUtility
025    {
026      /**
027       * A map used to cache classes and their fields for efficiency.
028       */
029      private static final Map<Class,Map<String,Field>> fields =
030          new HashMap<Class,Map<String,Field>>();
031      
032      /**
033       * A map used to cache classes and their methods for efficiency.
034       */
035      private static final Map<Class,Map<MethodEntry,Method>> methods =
036          new HashMap<Class,Map<MethodEntry,Method>>();
037      
038      /**
039       * A map used to cache classes and their constructors for efficiency.
040       */
041      private static final Map<Class,Map<MethodEntry,Constructor>> constructors =
042          new HashMap<Class,Map<MethodEntry,Constructor>>();
043    
044      /**
045       * Default constructor.  Cannot be instantiated.
046       */
047      private ReflectionUtility() {}
048      
049      /**
050       * Return the <code>Constructor</code> instance for the specified
051       * class.
052       *
053       * @param cls The fully qualified name of the class.
054       * @param parameterTypes The class type(s) of the constructor parameter(s).
055       * @throws ClassNotFoundException If the specified class could not be located.
056       * @throws NoSuchMethodException If the specified constructor method could
057       *   not be found.
058       */
059      public static final Constructor fetchConstructor( final String cls, 
060          Class... parameterTypes )
061          throws ClassNotFoundException, NoSuchMethodException
062      {
063        return fetchConstructor( Class.forName( cls ), parameterTypes );
064      }
065      
066      /**
067       * Return the <code>Constructor</code> instance for the specified
068       * class.
069       *
070       * @param cls The class whose constructor is to be fetched.
071       * @param parameterTypes The class type(s) of the constructor parameter(s).
072       * @throws NoSuchMethodException If the specified constructor method could
073       *   not be found.
074       */
075      public static final Constructor fetchConstructor( final Class cls, 
076          Class... parameterTypes ) throws NoSuchMethodException
077      {
078        MethodEntry entry = new MethodEntry( null, parameterTypes );
079        if ( constructors.containsKey( cls ) )
080        {
081          Map<MethodEntry,Constructor> map = constructors.get( cls );
082          if ( map.containsKey( entry ) )      
083          {
084            return map.get( entry );
085          }
086        }
087        
088        Constructor constructor = cls.getDeclaredConstructor( parameterTypes );
089        if ( ! Modifier.isPublic( constructor.getModifiers() ) )
090        {
091          constructor.setAccessible( true );
092        }
093        
094        if ( ! constructors.containsKey( cls ) )
095        {
096          constructors.put( cls, new HashMap<MethodEntry,Constructor>() );
097        }
098        constructors.get( cls ).put( entry, constructor );
099        
100        return constructor;
101      }
102    
103      /**
104       * Return the <code>Field</code> instance with the specified name
105       * from the <code>object</code>.  Sets the field accessible if
106       * it is not accessible.
107       *
108       * @see #fetchField( String, Class )
109       * @param name The name of the field to look up.
110       * @param object The object from which the field is to be retrieved.
111       * @throws NoSuchFieldException If no field with the specified name
112       *   exists in <code>object</code>.
113       */
114      public static final Field fetchField( final String name, final Object object ) 
115        throws NoSuchFieldException
116      {
117        return fetchField( name, object.getClass() );
118      }
119    
120      /**
121       * Return the {@link java.lang.reflect.Field} instance with the specified name
122       * from the {@link java.lang.Class} specified.  Sets the field accessible if
123       * it is not accessible.
124       *
125       * @param name The name of the field to look up.
126       * @param cls The class from which the field is to be retrieved.
127       * @return Returns the appropriate field instance.
128       * @throws NoSuchFieldException If no field with the specified name
129       *   exists in <code>object</code>.
130       */
131      public static final Field fetchField( final String name, final Class cls ) 
132        throws NoSuchFieldException
133      {
134        if ( fields.containsKey( cls ) )
135        {
136          Map<String,Field> map = fields.get( cls );
137          if ( map.containsKey( name ) )
138          {
139            Field field = map.get( name );
140            if ( field == null )
141              throw new NoSuchFieldException( cls.getName() + "." + name );
142            return field;
143          }
144        }
145        
146        Field field = null;
147        NoSuchFieldException exception = null;
148        Class c = cls;
149    
150        while ( field == null && c != null )
151        {
152          try
153          {
154            field = c.getDeclaredField( name );
155    
156            if ( ! Modifier.isPublic( field.getModifiers() ) )
157            {
158              field.setAccessible( true );
159            }
160          }
161          catch ( NoSuchFieldException nsfex )
162          {
163            exception = nsfex;
164          }
165    
166          c = c.getSuperclass();
167        }
168                
169        if ( ! fields.containsKey( cls ) )
170        {
171          fields.put( cls, new HashMap<String,Field>() );
172        }
173        fields.get( cls ).put( name, field );
174        
175        if ( field == null && exception != null ) throw exception;
176        return field;
177      }
178    
179      /**
180       * Return the <code>Object</code> instance represented by the 
181       * specified name from <code>object</code>.
182       *
183       * @see #fetchField
184       * @param name The name of the object to fetch.
185       * @param object The object from which the field is to be retrieved.
186       * @return Object The object instance for the specified field.
187       * @throws NoSuchFieldException If no field with the specified name
188       *   exists in <code>object</code>.
189       * @throws IllegalAccessException If the field cannot be accessed
190       *   due to custom security policies.
191       */
192      public static final Object fetchObject( final String name, final Object object ) 
193        throws NoSuchFieldException, IllegalAccessException
194      {
195        return fetchField( name, object ).get( object );
196      }
197    
198      /**
199       * Return the declared fields for the specified <code>object</code>.
200       *
201       * @see #fetchFields( Class )
202       * @param object The object whose declared fields are to be
203       *   retrieved.
204       * @return Map A map with the field names as <code>key</code> and
205       *   the field as <code>value</code>
206       */
207      public static final Map<String, Field> fetchFields( final Object object )
208      {
209        return fetchFields( object.getClass() );
210      }
211    
212      /**
213       * Return the declared fields for the specified <code>Class</code>.
214       *
215       * @param cls The class whose declared fields are to be
216       *   retrieved.
217       * @return Map A map with the field names as <code>key</code> and
218       *   the field as <code>value</code>
219       */
220      public static final Map<String, Field> fetchFields( final Class cls )
221      {
222        Map<String, Field> map = new HashMap<String, Field>();
223    
224        Class c = cls;
225        while ( c != null )
226        {
227          for ( Field field : c.getDeclaredFields() ) 
228          {
229            if ( ! Modifier.isPublic( field.getModifiers() ) )
230            {
231              field.setAccessible( true );
232            }
233    
234            map.put( field.getName(), field );
235          }
236    
237          c = c.getSuperclass();
238        }
239    
240        fields.put( cls, map );
241        return map;
242      }
243    
244      /**
245       * Return the <code>Object</code> instances representing the declared 
246       * fields for the specified <code>object</code>.
247       *
248       * @see #fetchFields
249       * @param object The object for which objects for the declared fields 
250       *   are to be retrieved.
251       * @return Map A map with the field names as <code>key</code> and
252       *   the object as <code>value</code>
253       * @throws IllegalAccessException If a custom security policy prevents
254       *   access to the fields.
255       */
256      public static final Map<String, Object> fetchObjects( final Object object )
257        throws IllegalAccessException
258      {
259        Map<String, Object> map = new HashMap<String, Object>();
260    
261        for ( Map.Entry<String, Field> entry : fetchFields( object ).entrySet() )
262        {
263          Object obj = entry.getValue().get( object );
264          map.put( entry.getKey(), obj );
265        }
266    
267        return map;
268      }
269    
270      /**
271       * Fetch the <code>Method</code> defined in the specified field with
272       * the specified <code>name</code> that accepts the specified
273       * parameters.  Please note that this method does not throw a
274       * {@link java.lang.NoSuchMethodException}, but returns <code>null</code>
275       * instead.
276       *
277       * @see #fetchMethod( Class, String, Class[] )
278       * @param field The field whose method is to be retrieved.
279       * @param name The name of the method to retrieve.
280       * @param parameters The parameters accepted by the method.
281       */
282      public static final Method fetchMethod( final Field field, final String name, 
283          Class... parameters )
284      {
285        return fetchMethod( field.getType(), name, parameters );
286      }
287    
288      /**
289       * Fetch the <code>Method</code> defined in the specified object with
290       * the specified <code>name</code> that accepts the specified
291       * parameters.  Please note that this method does not throw a
292       * {@link java.lang.NoSuchMethodException}, but returns <code>null</code>
293       * instead.
294       *
295       * @see #fetchMethod( Class, String, Class[] )
296       * @param object The object whose method is to be retrieved.
297       * @param name The name of the method to retrieve.
298       * @param parameters The parameters accepted by the method.
299       */
300      public static final Method fetchMethod( final Object object, final String name, 
301          Class... parameters )
302      {
303        return fetchMethod( object.getClass(), name, parameters );
304      }
305    
306      /**
307       * Fetch the <code>Method</code> defined in the specified class with
308       * the specified <code>name</code> that accepts the specified
309       * parameters.  Please note that this method does not throw a
310       * {@link java.lang.NoSuchMethodException}, but returns <code>null</code>
311       * instead.
312       *
313       * @param cls The cls whose method is to be retrieved.
314       * @param name The name of the method to retrieve.
315       * @param parameters The parameters accepted by the method.
316       * @return The method instance for the specified name or <code>null</code>
317       *   if no such method could be found.
318       */
319      public static final Method fetchMethod( final Class cls, final String name, 
320          Class... parameters )
321      {
322        MethodEntry entry = new MethodEntry( name, parameters );
323        if ( methods.containsKey( cls ) )
324        {
325          Map<MethodEntry,Method> map = methods.get( cls );
326          if ( map.containsKey( entry ) )
327          {
328            return map.get( entry );
329          }
330        }
331        
332        Method method = null;
333        Class c = cls;
334        while ( method == null && c != null )
335        {
336          try
337          {
338            method = c.getDeclaredMethod( name, parameters );
339            if ( ! Modifier.isPublic( method.getModifiers() ) )
340            {
341              method.setAccessible( true );
342            }
343          }
344          catch ( NoSuchMethodException nsme ) {}
345    
346          c = c.getSuperclass();
347        }
348                
349        if ( ! methods.containsKey( cls ) )
350        {
351          methods.put( cls, new HashMap<MethodEntry,Method>() );
352        }
353        methods.get( cls ).put( entry, method );
354        
355        return method;
356      }
357    
358      /**
359       * Fetches the accessor method for the specified property in
360       * the specified object.
361       *
362       * @see #fetchAccessor( String, Class )
363       * @param property The property whose accessor is to be fetched.
364       * @param object The object to which the property belongs.
365       * @return Method The accessor method for the property.
366       * @throws IntrospectionException If errors are encountered while
367       *   introspecting the specified class.
368       */
369      public static final Method fetchAccessor( final String property, 
370          final Object object ) throws IntrospectionException
371      {
372        return fetchAccessor( property, object.getClass() );
373      }
374    
375      /**
376       * Fetches the accessor method for the specified property in
377       * the specified class.
378       *
379       * @param property The property whose accessor is to be fetched.
380       * @param cls The class to which the property belongs.
381       * @return Method The accessor method for the property.
382       * @throws IntrospectionException If errors are encountered while
383       *   introspecting the specified class.
384       */
385      public static final Method fetchAccessor( final String property,
386          final Class cls ) throws IntrospectionException
387      {
388        Method method = null;
389        BeanInfo beanInfo = Introspector.getBeanInfo( cls );
390        for ( PropertyDescriptor descriptor : 
391            beanInfo.getPropertyDescriptors() )
392        {
393          if ( descriptor.getName().equals( property ) )
394          {
395            method = descriptor.getReadMethod();
396          }
397        }
398    
399        return method;
400      }
401    
402      /**
403       * Fetches the mutator method for the specified property in
404       * the specified object.
405       *
406       * @see #fetchMutator( String, Class )
407       * @param property The property whose mutator is to be fetched.
408       * @param object The object to which the property belongs.
409       * @return Method The mutator method for the property.
410       * @throws IntrospectionException If errors are encountered while
411       *   introspecting the specified class.
412       */
413      public static final Method fetchMutator( final String property, 
414          final Object object ) throws IntrospectionException
415      {
416        return fetchMutator( property, object.getClass() );
417      }
418    
419      /**
420       * Fetches the mutator method for the specified property in
421       * the specified class.
422       *
423       * @param property The property whose mutator is to be fetched.
424       * @param cls The class to which the property belongs.
425       * @return Method The mutator method for the property.
426       * @throws IntrospectionException If errors are encountered while
427       *   introspecting the specified class.
428       */
429      public static final Method fetchMutator( final String property,
430          final Class cls ) throws IntrospectionException
431      {
432        Method method = null;
433        BeanInfo beanInfo = Introspector.getBeanInfo( cls );
434        for ( PropertyDescriptor descriptor : 
435            beanInfo.getPropertyDescriptors() )
436        {
437          if ( descriptor.getName().equals( property ) )
438          {
439            method = descriptor.getWriteMethod();
440          }
441        }
442    
443        return method;
444      }
445      
446      /**
447       * A holder object to store a combination of a method name and parameter
448       * array.
449       */
450      private static class MethodEntry
451      {
452        /**
453         * The name of the method.
454         */
455        private final String name;
456        
457        /**
458         * The array of types of the method parameters.
459         */
460        private final Class[] parameters;
461        
462        /**
463         * Create a new instance of the holder object using the specified values.
464         *
465         * @param name The {@link ReflectionUtility.MethodEntry#name} value to use.
466         * @param parameters The {@link ReflectionUtility.MethodEntry#parameters}
467         *   value to use.
468         */
469        private MethodEntry( final String name, final Class[] parameters )
470        {
471          this.name = name;
472          this.parameters = parameters;
473        }
474        
475        /**
476         * Over-ridden to implement proper equality checking.  Objects are equal
477         * if they are of the same class and have the same values for {@link
478         * ReflectionUtility.MethodEntry#name} and {@link 
479         * ReflectionUtility.MethodEntry#parameters}.
480         *
481         * @param object The object that is to be checked for equality.
482         */
483        @Override
484        public boolean equals( Object object )
485        {
486          boolean result = false;
487          if ( object instanceof MethodEntry )
488          {
489            MethodEntry entry = (MethodEntry) object;
490            result = ( name == entry.name ) || 
491                ( ( name != null ) && name.equals( entry.name ) );
492            result = result && ( ( parameters == entry.parameters ) || 
493                Arrays.equals( parameters, entry.parameters ) );
494          }
495          
496          return result;
497        }
498        
499        /**
500         * Computes a hash code for the object.
501         *
502         * @return The hash code computed using the object fields.
503         */
504        @Override
505        public int hashCode()
506        {
507          int hash = 7;
508          hash = ( 31 * hash ) + ( ( name == null ) ? 0 : name.hashCode() );
509          hash = ( 31 * hash ) + Arrays.hashCode( parameters );
510          return hash;
511        }
512      }
513    }