[Mulgara-svn] r485 - branches/nw-interface/src/jar/util/java/org/mulgara/util

pag at mulgara.org pag at mulgara.org
Thu Oct 18 22:01:41 UTC 2007


Author: pag
Date: 2007-10-18 17:01:40 -0500 (Thu, 18 Oct 2007)
New Revision: 485

Modified:
   branches/nw-interface/src/jar/util/java/org/mulgara/util/Reflect.java
Log:
Created a whole fallback mechanism for finding constructors based on derived and null argument types.  Lots of debug info included as well.

Modified: branches/nw-interface/src/jar/util/java/org/mulgara/util/Reflect.java
===================================================================
--- branches/nw-interface/src/jar/util/java/org/mulgara/util/Reflect.java	2007-10-18 22:00:27 UTC (rev 484)
+++ branches/nw-interface/src/jar/util/java/org/mulgara/util/Reflect.java	2007-10-18 22:01:40 UTC (rev 485)
@@ -11,7 +11,9 @@
  */
 package org.mulgara.util;
 
+import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
 
 /**
  * Utility methods for reflection.
@@ -30,13 +32,12 @@
    * @return A new instance of clazz, constructed with the arguments of args.
    */
   public static <T> T newInstance(Class<T> clazz, Object... args) {
-    T result = null;
     try {
-      result = clazz.getConstructor(getTypes(args)).newInstance(args);
+      return findConstructor(clazz, args).newInstance(args);
     } catch (SecurityException e) {
       throw new RuntimeException("Not permitted to create " + clazz.getName(), e);
     } catch (NoSuchMethodException e) {
-      throw new RuntimeException("No constructor of the request form for: " + clazz.getName(), e);
+      throw new RuntimeException("No constructor of the requested form: " + clazz.getName() + "\n" + fullLog(clazz, e, args), e);
     } catch (IllegalArgumentException e) {
       throw new RuntimeException("Bad arguments supplied to constructor for: " + clazz.getName(), e);
     } catch (InstantiationException e) {
@@ -47,10 +48,66 @@
       // wrap the exception, since we don't know what type it is
       throw new RuntimeException(e.getCause());
     }
+  }
+
+
+  /**
+   * Do an exhaustive search for a constructor, given a list of parameters.
+   * If multiple constructors would match, then only the first is returned.
+   * @param <T> The class type to retrieve a constructor for.
+   * @param clazz The class object representing the class T.
+   * @param args The argument list to use with the constructor.
+   * @return A constructor that can be used on the given arguments.
+   * @throws NoSuchMethodException No such constructor could be found.
+   */
+  public static <T> Constructor<T> findConstructor(Class<T> clazz, Object... args) throws NoSuchMethodException {
+    Class<?>[] argTypes = getTypes(args);
+
+    // do a standard search
+    try {
+      return clazz.getConstructor(argTypes);
+    } catch (Exception e) { /* failed - try again */ }
+
+    // search for constructors with supertype parameters
+    Constructor<T> result = openConstructorSearch(clazz, argTypes, getAssignableTester());
+    // search for constructors allowing nulls as parameters
+    if (result == null) result = openConstructorSearch(clazz, argTypes, getNullAssignTester());
+    
+    if (result == null) throw new NoSuchMethodException("Unable to find a method for: " + clazz.getName() + "<init>(" + Arrays.toString(argTypes) + ")");
     return result;
   }
 
+
   /**
+   * Search for the first constructor that matches a given argument type list,
+   * given a testing function for argument compatibility. More than one constructor
+   * may match, in which case only the first is returned.
+   * @param <T> The type of constructor to return.
+   * @param clazz The class to get a constructor for.
+   * @param argTypes An array of the required types acceptable to the constructor.
+   *        This list may include nulls.
+   * @param assignFrom The function for testing if the constructor parameter may be
+   *        given an data of the type described in the argTypes array.
+   * @return A constructor for clazz if one could be found, otherwise <code>null</code>.
+   */
+  @SuppressWarnings("unchecked")
+  private static <T> Constructor<T> openConstructorSearch(Class<T> clazz, Class<?>[] argTypes, Tester2<Class<?>,Class<?>> assignFrom) {
+    for (Constructor<T> con: (Constructor<T>[])clazz.getConstructors()) {
+      boolean match = true;
+      Class<?>[] paramTypes = con.getParameterTypes();
+      for (int p = 0; p < paramTypes.length; p++) {
+        if (match && !assignFrom.test(paramTypes[p], argTypes[p])) {
+          match = false;
+          break;
+        }
+      }
+      if (match) return con;
+    }
+    return null;
+  }
+
+
+  /**
    * Get a type list of objects.
    * @param args An array of objects to obtain types for.
    * @return An array containing the types of the objects from args,
@@ -58,7 +115,83 @@
    */
   public static Class<?>[] getTypes(Object[] args) {
     Class<?>[] types = new Class<?>[args.length];
-    for (int a = 0; a < args.length; a++) types[a] = args[a].getClass();
+    for (int a = 0; a < args.length; a++) types[a] = (args[a] == null) ? null : args[a].getClass();
     return types;
   }
+
+
+  /**
+   * Converts an array of argument data into a parenthesized string of comma-separated names.
+   * @param args The data to convert to type names.
+   * @return A parenthesized type list.
+   */
+  private static String argList(Object[] args) {
+    StringBuffer result = new StringBuffer("(");
+    Class<?>[] types = getTypes(args);
+    for (int i = 0; i < types.length; i++) {
+      if (i != 0) result.append(", ");
+      result.append(types[i].getName());
+    }
+    result.append(")");
+    return result.toString();
+  }
+
+
+  /**
+   * Create a tester that can test for assignability of types, excluding the case when
+   * the given type is null.  This differs from {@link Class#isAssignableFrom} in that
+   * it will not throw a NullPointerException.
+   * @return A {@link Tester2} object that can test that the type defined in the second
+   *  parameter can be assigned to the type in the first parameter.
+   */
+  private static Tester2<Class<?>,Class<?>> getAssignableTester() {
+    return new Tester2<Class<?>,Class<?>>() {
+      public boolean test(Class<?> o1, Class<?> o2) {
+        return o2 != null && o1.isAssignableFrom(o2);
+       }
+    };
+  }
+
+
+  /**
+   * Create a tester that can test for assignability of types, including the case when
+   * the given type is null.
+   * @return A {@link Tester2} object that can test that the type defined in the second
+   *  parameter can be assigned to the type in the first parameter.
+   */
+  private static Tester2<Class<?>,Class<?>> getNullAssignTester() {
+    return new Tester2<Class<?>,Class<?>>() {
+      public boolean test(Class<?> o1, Class<?> o2) {
+        return o2 == null || o1.isAssignableFrom(o2);
+      }
+    };
+  }
+
+
+  /**
+   * Debug only method for printing out the complete stack trace and attempts at
+   * finding a constructor, when getConstructor fails.
+   * @param clazz The class that a constructor was searched for in.
+   * @param e The exception that required this log.
+   * @param args The arguments used to find the constructor.
+   * @return A string with all the available details coming from the failure to find
+   * the constructor.
+   */
+  private static String fullLog(Class<?> clazz, Throwable e, Object[] args) {
+    String result = argList(args) + "[" + e.getMessage() + "]\n" + StringUtil.strackTraceToString(e);
+    result += "Available constructors:\n";
+    for (Constructor<?> con: clazz.getConstructors()) {
+      result += "  <init>(";
+      boolean match = true;
+      Class<?>[] paramTypes = con.getParameterTypes();
+      for (int p = 0; p < paramTypes.length; p++) {
+        if (p != 0) result += ", ";
+        result += paramTypes[p].getCanonicalName();
+        if (match && !paramTypes[p].isAssignableFrom(args[p].getClass())) match = false;
+      }
+      result += ")";
+      result += (match ? "*\n" : "\n");
+    }
+    return result;
+  }
 }




More information about the Mulgara-svn mailing list