Reflection Java

/*
Java Reflection in Action
Ira R. Forman and Nate Forman
ISBN 1932394184
Publisher: Manning Publications Co.
*/
/* Copyright 2002 -- Ira R. Forman and Nate Forman */
/**
 * This class provides a set of static methods that extend the Java metaobject
 * protocol.
 * 
 * @author: Ira R. Forman
 */
import java.lang.reflect.*;
import java.util.*;
abstract public class Mopex {
  /**
   * Returns a syntactically correct name for a class object. If the class
   * object represents an array, the proper number of square bracket pairs are
   * appended to the component type.
   * 
   * @return java.lang.String
   * @param cls
   *            java.lang.Class
   */
  //start extract classNameToString
  public static String getTypeName(Class cls) {
    if (!cls.isArray()) {
      return cls.getName();
    } else {
      return getTypeName(cls.getComponentType()) + "[]";
    }
  }
  //stop extract classNameToString
  /**
   * Returns an array of the superclasses of cls.
   * 
   * @return java.lang.Class[]
   * @param cls
   *            java.lang.Class
   */
  //start extract getSuperclasses
  public static Class[] getSuperclasses(Class cls) {
    int i = 0;
    for (Class x = cls.getSuperclass(); x != null; x = x.getSuperclass())
      i++;
    Class[] result = new Class[i];
    i = 0;
    for (Class x = cls.getSuperclass(); x != null; x = x.getSuperclass())
      result[i++] = x;
    return result;
  }
  //stop extract getSuperclasses
  /**
   * Returns an array of the instance variablies of the the specified class.
   * An instance variable is defined to be a non-static field that is declared
   * by the class or inherited.
   * 
   * @return java.lang.Field[]
   * @param cls
   *            java.lang.Class
   */
  //start extract getInstanceVariables
  public static Field[] getInstanceVariables(Class cls) {
    List accum = new LinkedList();
    while (cls != null) {
      Field[] fields = cls.getDeclaredFields();
      for (int i = 0; i < fields.length; i++) {
        if (!Modifier.isStatic(fields[i].getModifiers())) {
          accum.add(fields[i]);
        }
      }
      cls = cls.getSuperclass();
    }
    Field[] retvalue = new Field[accum.size()];
    return (Field[]) accum.toArray(retvalue);
  }
  //stop extract getInstanceVariables
  /**
   * Returns an array of fields that are the declared instance variables of
   * cls. An instance variable is a field that is not static.
   * 
   * @return java.lang.reflect.Field[]
   * @param cls
   *            java.lang.Class
   */
  //start extract getDeclaredIVS
  public static Field[] getDeclaredIVs(Class cls) {
    Field[] fields = cls.getDeclaredFields();
    // Count the IVs
    int numberOfIVs = 0;
    for (int i = 0; i < fields.length; i++) {
      if (!Modifier.isStatic(fields[i].getModifiers()))
        numberOfIVs++;
    }
    Field[] declaredIVs = new Field[numberOfIVs];
    // Populate declaredIVs
    int j = 0;
    for (int i = 0; i < fields.length; i++) {
      if (!Modifier.isStatic(fields[i].getModifiers()))
        declaredIVs[j++] = fields[i];
    }
    return declaredIVs;
  }
  //stop extract getDeclaredIVS
  /**
   * Return an array of the supported instance variables of this class. A
   * supported instance variable is not static and is either declared or
   * inherited from a superclass.
   * 
   * @return java.lang.reflect.Field[]
   * @param cls
   *            java.lang.Class
   */
  //start extract getSupportedIVS
  public static Field[] getSupportedIVs(Class cls) {
    if (cls == null) {
      return new Field[0];
    } else {
      Field[] inheritedIVs = getSupportedIVs(cls.getSuperclass());
      Field[] declaredIVs = getDeclaredIVs(cls);
      Field[] supportedIVs = new Field[declaredIVs.length
          + inheritedIVs.length];
      for (int i = 0; i < declaredIVs.length; i++) {
        supportedIVs[i] = declaredIVs[i];
      }
      for (int i = 0; i < inheritedIVs.length; i++) {
        supportedIVs[i + declaredIVs.length] = inheritedIVs[i];
      }
      return supportedIVs;
    }
  }
  //stop extract getSupportedIVS
  /**
   * Returns an array of the methods that are not static.
   * 
   * @return java.lang.reflect.Method[]
   * @param cls
   *            java.lang.Class
   */
  //start extract getInstanceMethods
  public static Method[] getInstanceMethods(Class cls) {
    List instanceMethods = new ArrayList();
    for (Class c = cls; c != null; c = c.getSuperclass()) {
      Method[] methods = c.getDeclaredMethods();
      for (int i = 0; i < methods.length; i++)
        if (!Modifier.isStatic(methods[i].getModifiers()))
          instanceMethods.add(methods[i]);
    }
    Method[] ims = new Method[instanceMethods.size()];
    for (int j = 0; j < instanceMethods.size(); j++)
      ims[j] = (Method) instanceMethods.get(j);
    return ims;
  }
  //stop extract getInstanceMethods
  /**
   * Returns an array of methods to which instances of this class respond.
   * 
   * @return java.lang.reflect.Method[]
   * @param cls
   *            java.lang.Class
   */
  //start extract getSupportedMethods
  public static Method[] getSupportedMethods(Class cls) {
    return getSupportedMethods(cls, null);
  }
  //stop extract getSupportedMethods
  /**
   * This method retrieves the modifiers of a Method without the unwanted
   * modifiers specified in the second parameter. Because this method uses
   * bitwise operations, multiple unwanted modifiers may be specified by
   * bitwise or.
   * 
   * @return int
   * @param m
   *            java.lang.Method
   * @param unwantedModifiers
   *            int
   */
  //start extract getModifiersWithout
  public static int getModifiersWithout(Method m, int unwantedModifiers) {
    int mods = m.getModifiers();
    return (mods ^ unwantedModifiers) & mods;
  }
  //stop extract getModifiersWithout
  /**
   * Returns a Method that has the signature specified by the calling
   * parameters.
   * 
   * @return Method
   * @param cls
   *            java.lang.Class
   * @param name
   *            String
   * @param paramTypes
   *            java.lang.Class[]
   */
  //start extract getSupportedMethod
  public static Method getSupportedMethod(Class cls, String name,
      Class[] paramTypes) throws NoSuchMethodException {
    if (cls == null) {
      throw new NoSuchMethodException();
    }
    try {
      return cls.getDeclaredMethod(name, paramTypes);
    } catch (NoSuchMethodException ex) {
      return getSupportedMethod(cls.getSuperclass(), name, paramTypes);
    }
  }
  //stop extract getSupportedMethod
  /**
   * Returns a Method array of the methods to which instances of the specified
   * respond except for those methods defined in the class specifed by limit
   * or any of its superclasses. Note that limit is usually used to eliminate
   * them methods defined by java.lang.Object.
   * 
   * @return Method[]
   * @param cls
   *            java.lang.Class
   * @param limit
   *            java.lang.Class
   */
  //start extract getSupportedMethods
  public static Method[] getSupportedMethods(Class cls, Class limit) {
    Vector supportedMethods = new Vector();
    for (Class c = cls; c != limit; c = c.getSuperclass()) {
      Method[] methods = c.getDeclaredMethods();
      for (int i = 0; i < methods.length; i++) {
        boolean found = false;
        for (int j = 0; j < supportedMethods.size(); j++)
          if (equalSignatures(methods[i], (Method) supportedMethods
              .elementAt(j))) {
            found = true;
            break;
          }
        if (!found)
          supportedMethods.add(methods[i]);
      }
    }
    Method[] mArray = new Method[supportedMethods.size()];
    for (int k = 0; k < mArray.length; k++)
      mArray[k] = (Method) supportedMethods.elementAt(k);
    return mArray;
  }
  //stop extract getSupportedMethods
  /**
   * This field is initialized with a method object for the equalSignatures
   * method. This is an optimization in that selectMethods can use this field
   * instead of calling getMethod each time it is called.
   */
  //start extract equalSignaturesMethod
  static private Method equalSignaturesMethod;
  static {
    Class[] fpl = { Method.class, Method.class };
    try {
      equalSignaturesMethod = Mopex.class.getMethod("equalSignatures",
          fpl);
    } catch (NoSuchMethodException e) {
      throw new RuntimeException(e);
    }
  }
  //stop extract equalSignaturesMethod
  /**
   * Determines if the signatures of two method objects are equal. In Java, a
   * signature comprises the method name and the array of of formal parameter
   * types. For two signatures to be equal, the method names must be the same
   * and the formal parameters must be of the same type (in the same order).
   * 
   * @return boolean
   * @param m1
   *            java.lang.Method
   * @param m2
   *            java.lang.Method
   */
  //start extract equalSignatures
  public static boolean equalSignatures(Method m1, Method m2) {
    if (!m1.getName().equals(m2.getName()))
      return false;
    if (!Arrays.equals(m1.getParameterTypes(), m2.getParameterTypes()))
      return false;
    return true;
  }
  //stop extract equalSignatures
  /**
   * Return a string that represents the signature of the specified method.
   * 
   * @return String
   * @param m
   *            java.lang.Method
   */
  //start extract signatureToString
  public static String signatureToString(Method m) {
    return m.getName() + "("
        + formalParametersToString(m.getParameterTypes()) + ")";
  }
  //stop extract signatureToString
  /**
   * Returns a string that can be used as a formal parameter list for a method
   * that has the parameter types of the specified array.
   * 
   * @return String
   * @param pts
   *            java.lang.Class[]
   */
  //start extract formalParametersToString
  public static String formalParametersToString(Class[] pts) {
    String result = "";
    for (int i = 0; i < pts.length; i++) {
      result += getTypeName(pts[i]) + " p" + i;
      if (i < pts.length - 1)
        result += ",";
    }
    return result;
  }
  //stop extract formalParametersToString
  /**
   * Returns a string that is an actual parameter list that matches the formal
   * parameter list produced by formalParametersToString.
   * 
   * @return String
   * @param pts
   *            java.lang.Class[]
   */
  //start extract actualParametersToString
  public static String actualParametersToString(Class[] pts) {
    String result = "";
    for (int i = 0; i < pts.length; i++) {
      result += "p" + i;
      if (i < pts.length - 1)
        result += ",";
    }
    return result;
  }
  //stop extract actualParametersToString
  /**
   * Returns a String that represents the header for a constructor.
   * 
   * @return String
   * @param c
   *            java.lang.Constructor
   */
  //start extract constructorHeaderToString
  public static String headerToString(Constructor c) {
    String mods = Modifier.toString(c.getModifiers());
    if (mods.length() == 0)
      return headerSuffixToString(c);
    else
      return mods + " " + headerSuffixToString(c);
  }
  //stop extract constructorHeaderToString
  /**
   * Returns a String that represents the header suffix for a constructor. The
   * term "header suffix" is not a standard Java term. We use it to mean the
   * Java header without the modifiers.
   * 
   * @return String
   * @param c
   *            java.lang.Constructor
   */
  //start extract constructorHeaderToString
  public static String headerSuffixToString(Constructor c) {
    String header = signatureToString(c);
    Class[] eTypes = c.getExceptionTypes();
    if (eTypes.length != 0)
      header += " throws " + classArrayToString(eTypes);
    return header;
  }
  //stop extract constructorHeaderToString
  /**
   * Returns a String that represents the signature for a constructor.
   * 
   * @return String
   * @param c
   *            java.lang.Constructor
   */
  //start extract constructorHeaderToString
  public static String signatureToString(Constructor c) {
    return c.getName() + "("
        + formalParametersToString(c.getParameterTypes()) + ")";
  }
  //stop extract constructorHeaderToString
  /**
   * Returns a String that represents the header of a method.
   * 
   * @return String
   * @param m
   *            java.lang.Method
   */
  //start extract headerToString
  public static String headerToString(Method m) {
    String mods = Modifier.toString(m.getModifiers());
    if (mods.length() == 0)
      return headerSuffixToString(m);
    else
      return mods + " " + headerSuffixToString(m);
  }
  //stop extract headerToString
  /**
   * Returns a String that represents the suffix of the header of a method.
   * The suffix of a header is not a standard Java term. We use the term to
   * mean the Java header without the method modifiers.
   * 
   * @return String
   * @param m
   *            java.lang.Method
   */
  //start extract headerToString
  public static String headerSuffixToString(Method m) {
    String header = getTypeName(m.getReturnType()) + " "
        + signatureToString(m);
    Class[] eTypes = m.getExceptionTypes();
    if (eTypes.length != 0) {
      header += " throws " + classArrayToString(eTypes);
    }
    return header;
  }
  //stop extract headerToString
  /**
   * Returns a String that is a comma separated list of the typenames of the
   * classes in the array pts.
   * 
   * @return String
   * @param pts
   *            java.lang.Class[]
   */
  //start extract classArrayToString
  public static String classArrayToString(Class[] pts) {
    String result = "";
    for (int i = 0; i < pts.length; i++) {
      result += getTypeName(pts[i]);
      if (i < pts.length - 1)
        result += ",";
    }
    return result;
  }
  //stop extract classArrayToString
  /**
   * Turns true if and only if the header suffixes of the two specified
   * methods are equal. The header suffix is defined to be the signature, the
   * return type, and the exception types.
   * 
   * @return boolean
   * @param m1
   *            java.lang.Method
   * @param m2
   *            java.lang.Method
   */
  public static boolean equalsHeaderSuffixes(Method m1, Method m2) {
    if (m1.getReturnType() != m2.getReturnType())
      return false;
    if (!Arrays.equals(m1.getExceptionTypes(), m2.getExceptionTypes()))
      return false;
    return equalSignatures(m1, m2);
  }
  /**
   * Creates constructor with the signature of c and a new name. It adds some
   * code after generating a super statement to call c. This method is used
   * when generating a subclass of class that declared c.
   * 
   * @return String
   * @param c
   *            java.lang.Constructor
   * @param name
   *            String
   * @param code
   *            String
   */
  //start extract createRenamedConstructor
  public static String createRenamedConstructor(Constructor c, String name,
      String code) {
    Class[] pta = c.getParameterTypes();
    String fpl = formalParametersToString(pta);
    String apl = actualParametersToString(pta);
    Class[] eTypes = c.getExceptionTypes();
    String result = name + "(" + fpl + ")\n";
    if (eTypes.length != 0)
      result += "    throws " + classArrayToString(eTypes) + "\n";
    result += "{\n    super(" + apl + ");\n" + code + "}\n";
    return result;
  }
  //stop extract createRenamedConstructor
  /**
   * Returns a String that is formatted as a Java method declaration having
   * the same header as the specified method but with the code parameter
   * substituted for the method body.
   * 
   * @return String
   * @param m
   *            java.lang.Method
   * @param code
   *            String
   */
  //start extract createReplacementMethod
  public static String createReplacementMethod(Method m, String code) {
    Class[] pta = m.getParameterTypes();
    String fpl = formalParametersToString(pta);
    Class[] eTypes = m.getExceptionTypes();
    String result = m.getName() + "(" + fpl + ")\n";
    if (eTypes.length != 0)
      result += "    throws " + classArrayToString(eTypes) + "\n";
    result += "{\n" + code + "}\n";
    return result;
  }
  //stop extract createReplacementMethod
  /**
   * Returns a string for a cooperative override of the method m. That is, The
   * string has the same return type and signature as m but the body has a
   * super call that is sandwiched between the strings code1 and code2.
   * 
   * @return String
   * @param m
   *            java.lang.Method
   * @param code1
   *            String
   * @param code2
   *            String
   */
  //start extract createCooperativeWrapper
  public static String createCooperativeWrapper(Method m, String code1,
      String code2) {
    Class[] pta = m.getParameterTypes();
    Class retType = m.getReturnType();
    String fpl = formalParametersToString(pta);
    String apl = actualParametersToString(pta);
    Class[] eTypes = m.getExceptionTypes();
    String result = retType.getName() + " " + m.getName() + "(" + fpl
        + ")\n";
    if (eTypes.length != 0)
      result += "    throws " + classArrayToString(eTypes) + "\n";
    result += "{\n" + code1 + "    ";
    if (retType != void.class)
      result += retType.getName() + " cooperativeReturnValue = ";
    result += "super." + m.getName() + "(" + apl + ");\n";
    result += code2;
    if (retType != void.class)
      result += "    return cooperativeReturnValue;\n";
    result += "}\n";
    return result;
  }
  /**
   * Returns the method object for the unique method named mName. If no such
   * method exists, a null is returned. If there is more than one such method,
   * a runtime exception is thrown.
   * 
   * @return Method
   * @param cls
   *            java.lang.Class
   * @param mName
   *            String
   */
  public static Method getUniquelyNamedMethod(Class cls, String mName) {
    Method result = null;
    Method[] mArray = cls.getDeclaredMethods();
    for (int i = 0; i < mArray.length; i++)
      if (mName.equals(mArray[i].getName())) {
        if (result == null)
          result = mArray[i];
        else
          throw new RuntimeException("name is not unique");
      }
    return result;
  }
  /**
   * Finds the first (from the bottom of the inheritance hierarchy) field with
   * the specified name. Note that Class.getField returns only public fields.
   * 
   * @return Field
   * @param cls
   *            java.lang.Class
   * @param name
   *            String
   */
  //start extract findField
  public static Field findField(Class cls, String name)
      throws NoSuchFieldException {
    if (cls != null) {
      try {
        return cls.getDeclaredField(name);
      } catch (NoSuchFieldException e) {
        return findField(cls.getSuperclass(), name);
      }
    } else {
      throw new NoSuchFieldException();
    }
  }
}