/**
* The contents of this file are subject to the Open Software License
* Version 3.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.opensource.org/licenses/osl-3.0.txt
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*/
package org.mulgara.util;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
/**
* Utility methods for reflection.
*
* @created Aug 15, 2007
* @author Paula Gearon
* @copyright © 2007 <a href="mailto:pgearon@users.sourceforge.net">Paula Gearon</a>
* @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a>
*/
public class Reflect {
/**
* Create a new instance of the given class, using the supplied arguments.
* @param clazz The class to create an instance of.
* @param args The arguments to supply to the constructor of clazz.
* @return A new instance of clazz, constructed with the arguments of args.
*/
public static <T> T newInstance(Class<T> clazz, Object... args) {
try {
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 requested form: " + clazz.getName() + argList(args), e);
} catch (IllegalArgumentException e) {
throw new RuntimeException("Bad arguments supplied to constructor for: " + clazz.getName(), e);
} catch (InstantiationException e) {
throw new RuntimeException("Not legal to create objects of type: " + clazz.getName() + "(try using a subclass)", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Not permitted to access constructor for " + clazz.getName(), e);
} catch (InvocationTargetException e) {
// 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 (NoSuchMethodException 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, BooleanOp2<Class<?>,Class<?>> assignFrom) {
for (Constructor<T> con: (Constructor<T>[])clazz.getConstructors()) {
boolean match = true;
Class<?>[] paramTypes = con.getParameterTypes();
// Make sure the candidate constructor has the appropriate number of arguments.
if (paramTypes.length != argTypes.length) continue;
// If the argument count matches, test the argument classes one-by-one.
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,
* where args[x] instanceof return[x]
*/
public static Class<?>[] getTypes(Object[] args) {
Class<?>[] types = new Class<?>[args.length];
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 BooleanOp2} object that can test that the type defined in the second
* parameter can be assigned to the type in the first parameter.
*/
private static BooleanOp2<Class<?>,Class<?>> getAssignableTester() {
return new BooleanOp2<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 BooleanOp2} object that can test that the type defined in the second
* parameter can be assigned to the type in the first parameter.
*/
private static BooleanOp2<Class<?>,Class<?>> getNullAssignTester() {
return new BooleanOp2<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 with a {@link NoSuchMethodException}.
* @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.
*/
@SuppressWarnings("unused")
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;
}
}