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 }