package com.sptci;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import java.util.Map;
import java.util.HashMap;

/**
 * A utility class to handle common reflection tasks.
 *
 * <p>Copyright 2006 Sans Pareil Technologies, Inc.</p>
 * @author Rakesh Vidyadharan 2006-02-07
 * @version $Id: ReflectionUtility.java,v 1.3 2006/02/09 16:09:58 rakesh Exp $
 */
public final class ReflectionUtility
{
  /**
   * Default constructor.  Cannot be instantiated.
   */
  private ReflectionUtility() {}

  /**
   * Return the <code>Field</code> instance with the specified name
   * from the <code>object</code>.  Sets the field accessible if
   * it is not accessible.
   *
   * @param name The name of the field to look up.
   * @param object The object from which the field is to be retrieved.
   * @throws NoSuchFieldException If no field with the specified name
   *   exists in <code>object</code>.
   */
  public static final Field fetchField( String name, Object object ) 
    throws NoSuchFieldException
  {
    Field field = object.getClass().getDeclaredField( name );

    if ( ! Modifier.isPublic( field.getModifiers() ) )
    {
      field.setAccessible( true );
    }

    return field;
  }

  /**
   * Return the <code>Object</code> instance represented by the 
   * specified name from <code>object</code>.
   *
   * @see #fetchField
   * @param name The name of the object to fetch.
   * @param object The object from which the field is to be retrieved.
   * @return Object The object instance for the specified field.
   * @throws NoSuchFieldException If no field with the specified name
   *   exists in <code>object</code>.
   * @throws IllegalAccessException If the field cannot be accessed
   *   due to custom security policies.
   */
  public static final Object fetchObject( String name, Object object ) 
    throws NoSuchFieldException, IllegalAccessException
  {
    return fetchField( name, object ).get( object );
  }

  /**
   * Return the declared fields for the specified <code>object</code>.
   *
   * @see #fetchFields( Class )
   * @param object The object whose declared fields are to be
   *   retrieved.
   * @return Map A map with the field names as <code>key</code> and
   *   the field as <code>value</code>
   */
  public static final Map<String, Field> fetchFields( Object object )
  {
    return fetchFields( object.getClass() );
  }

  /**
   * Return the declared fields for the specified <code>Class</code>.
   *
   * @param cls The class whose declared fields are to be
   *   retrieved.
   * @return Map A map with the field names as <code>key</code> and
   *   the field as <code>value</code>
   */
  public static final Map<String, Field> fetchFields( Class cls )
  {
    Map<String, Field> map = new HashMap<String, Field>();

    for ( Field field : cls.getDeclaredFields() ) 
    {
      if ( ! Modifier.isPublic( field.getModifiers() ) )
      {
        field.setAccessible( true );
      }

      map.put( field.getName(), field );
    }

    return map;
  }

  /**
   * Return the <code>Object</code> instances representing the declared 
   * fields for the specified <code>object</code>.
   *
   * @see #fetchFields
   * @param object The object for which objects for the declared fields 
   *   are to be retrieved.
   * @return Map A map with the field names as <code>key</code> and
   *   the object as <code>value</code>
   * @throws IllegalAccessException If a custom security policy prevents
   *   access to the fields.
   */
  public static final Map<String, Object> fetchObjects( Object object )
    throws IllegalAccessException
  {
    Map<String, Object> map = new HashMap<String, Object>();

    for ( Map.Entry<String, Field> entry : fetchFields( object ).entrySet() )
    {
      Object obj = entry.getValue().get( object );
      map.put( entry.getKey(), obj );
    }

    return map;
  }

  /**
   * Fetch the <code>Method</code> defined in the specified field with
   * the specified <code>name</code> that accepts the specified
   * parameters.
   *
   * @see #fetchMethod( Class, String, Class[] )
   * @param field The field whose method is to be retrieved.
   * @param name The name of the method to retrieve.
   * @param parameters The parameters accepted by the method.
   */
  public static final Method fetchMethod( Field field, String name, 
      Class[] parameters )
  {
    return fetchMethod( field.getType(), name, parameters );
  }

  /**
   * Fetch the <code>Method</code> defined in the specified object with
   * the specified <code>name</code> that accepts the specified
   * parameters.
   *
   * @see #fetchMethod( Class, String, Class[] )
   * @param object The object whose method is to be retrieved.
   * @param name The name of the method to retrieve.
   * @param parameters The parameters accepted by the method.
   */
  public static final Method fetchMethod( Object object, String name, 
      Class[] parameters )
  {
    return fetchMethod( object.getClass(), name, parameters );
  }

  /**
   * Fetch the <code>Method</code> defined in the specified class with
   * the specified <code>name</code> that accepts the specified
   * parameters.
   *
   * @param cls The cls whose method is to be retrieved.
   * @param name The name of the method to retrieve.
   * @param parameters The parameters accepted by the method.
   */
  public static final Method fetchMethod( Class cls, String name, 
      Class[] parameters )
  {
    Method method = null;
    while ( method == null && cls != null )
    {
      try
      {
        method = cls.getDeclaredMethod( name, parameters );
      }
      catch ( NoSuchMethodException nsme ) {}

      cls = cls.getSuperclass();
    }

    return method;
  }

  /**
   * Fetches the accessor method for the specified property in
   * the specified object.
   *
   * @see #fetchAccessor( String, Class )
   * @param property The property whose accessor is to be fetched.
   * @param object The object to which the property belongs.
   * @return Method The accessor method for the property.
   * @throws IntrospectionException If errors are encountered while
   *   introspecting the specified class.
   */
  public static final Method fetchAccessor( String property, 
      Object object ) throws IntrospectionException
  {
    return fetchAccessor( property, object.getClass() );
  }

  /**
   * Fetches the accessor method for the specified property in
   * the specified class.
   *
   * @param property The property whose accessor is to be fetched.
   * @param cls The class to which the property belongs.
   * @return Method The accessor method for the property.
   * @throws IntrospectionException If errors are encountered while
   *   introspecting the specified class.
   */
  public static final Method fetchAccessor( String property, Class cls )
    throws IntrospectionException
  {
    Method method = null;
    BeanInfo beanInfo = Introspector.getBeanInfo( cls );
    for ( PropertyDescriptor descriptor : 
        beanInfo.getPropertyDescriptors() )
    {
      if ( descriptor.getName().equals( property ) )
      {
        method = descriptor.getReadMethod();
      }
    }

    return method;
  }

  /**
   * Fetches the mutator method for the specified property in
   * the specified object.
   *
   * @see #fetchMutator( String, Class )
   * @param property The property whose mutator is to be fetched.
   * @param object The object to which the property belongs.
   * @return Method The mutator method for the property.
   * @throws IntrospectionException If errors are encountered while
   *   introspecting the specified class.
   */
  public static final Method fetchMutator( String property, 
      Object object ) throws IntrospectionException
  {
    return fetchMutator( property, object.getClass() );
  }

  /**
   * Fetches the mutator method for the specified property in
   * the specified class.
   *
   * @param property The property whose mutator is to be fetched.
   * @param cls The class to which the property belongs.
   * @return Method The mutator method for the property.
   * @throws IntrospectionException If errors are encountered while
   *   introspecting the specified class.
   */
  public static final Method fetchMutator( String property, Class cls )
    throws IntrospectionException
  {
    Method method = null;
    BeanInfo beanInfo = Introspector.getBeanInfo( cls );
    for ( PropertyDescriptor descriptor : 
        beanInfo.getPropertyDescriptors() )
    {
      if ( descriptor.getName().equals( property ) )
      {
        method = descriptor.getWriteMethod();
      }
    }

    return method;
  }
}
