[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