package com.laytonsmith.PureUtilities.Common;
import java.io.PrintStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
*
*/
public class ReflectionUtils {
private ReflectionUtils() {
}
public static class ReflectionException extends RuntimeException {
public ReflectionException(Throwable cause) {
super(cause);
}
/**
* Returns the underlying checked exception that was thrown by the
* reflective operation.
*
* @return
*/
@Override
public Throwable getCause() {
return super.getCause();
}
}
/**
* Constructs a new instance of the specified object, assuming it has a no
* arg constructor. It will bypass access restrictions if possible.
*
* @param <T> The class type returned, specified by the class type requested
* @param clazz
* @return
* @throws ReflectionException
*/
public static <T> T newInstance(Class<T> clazz) throws ReflectionException {
return newInstance(clazz, new Class[]{}, new Object[]{});
}
/**
* Constructs a new instance of the specified object, using the constructor
* that matches the argument types, and passes in the arguments specified.
* It will bypass access restrictions if possible.
*
* @param <T> The class type returned, specified by the class type requested
* @param clazz
* @param argTypes
* @param args
* @return
* @throws ReflectionException
*/
public static <T> T newInstance(Class<T> clazz, Class[] argTypes, Object[] args) throws ReflectionException {
try {
Constructor<T> c = clazz.getDeclaredConstructor(argTypes);
c.setAccessible(true);
return c.newInstance(args);
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException ex) {
throw new ReflectionException(ex);
}
}
/**
* Gets the value from a static class member, disregarding the access
* restrictions.
*
* @param clazz
* @param variableName
* @return
*/
public static Object get(Class clazz, String variableName) throws ReflectionException {
return get(clazz, null, variableName);
}
/**
* Gets a member from a class, disregarding the access restrictions. If
* accessing a static variable, the instance may be null. If variableName
* contains a dot, then it recursively digs down and grabs that value. So,
* given the following class definitions:
* <pre>
* class A { B bObj; }
* class B { C cObj; }
* class C { String obj; }
* </pre> Then if clazz were A.class, and variableName were "bObj.cObj.obj",
* then C's String obj would be returned.
*
* @param clazz
* @param instance
* @param variableName
* @return
*/
public static Object get(Class clazz, Object instance, String variableName) throws ReflectionException {
try {
if (variableName.contains(".")) {
String split[] = variableName.split("\\.");
Object myInstance = instance;
Class myClazz = clazz;
for (String var : split) {
myInstance = get(myClazz, myInstance, var);
myClazz = myInstance.getClass();
}
return myInstance;
} else {
Field f = clazz.getDeclaredField(variableName);
f.setAccessible(true);
return f.get(instance);
}
} catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException ex) {
throw new ReflectionException(ex);
}
}
/**
* Sets the value of a member in a static class, disregarding access
* restrictions and the final modifier.
*
* @param clazz
* @param variableName
* @param value
*/
public static void set(Class clazz, String variableName, Object value) throws ReflectionException {
set(clazz, null, variableName, value);
}
/**
* Sets the value of a member in a specific instance of an object,
* disregarding access restrictions and the final modifier.
*
* @param clazz
* @param instance
* @param variableName
* @param value
*/
public static void set(Class clazz, Object instance, String variableName, Object value) throws ReflectionException {
try {
if (variableName.contains(".")) {
String split[] = variableName.split("\\.");
Object myInstance = instance;
Class myClazz = clazz;
int count = 0;
for (String var : split) {
if (count == split.length - 1) {
//Only the last one needs to be set
break;
}
myInstance = get(myClazz, myInstance, var);
myClazz = myInstance.getClass();
count++;
}
set(myClazz, myInstance, split[split.length - 1], value);
} else {
Field f = clazz.getDeclaredField(variableName);
f.setAccessible(true);
//This is the really evil stuff here, this is what removes the final modifier.
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL);
f.set(instance, value);
}
} catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException ex) {
throw new ReflectionException(ex);
}
}
/**
* Invokes a no argument method, disregarding access restrictions, and
* returns the result.
*
* @param clazz
* @param instance
* @param methodName
* @return
*/
public static Object invokeMethod(Class clazz, Object instance, String methodName) throws ReflectionException {
return invokeMethod(clazz, instance, methodName, new Class[]{}, new Object[]{});
}
/**
* Grabs the method from the instance object automatically. If multiple
* methods match the given name, the most appropriate one is selected based
* on the argument types. {@code instance} may not be null.
*
* @param instance
* @param methodName
* @param params
* @return
* @throws ReflectionException
*/
@SuppressWarnings({"ThrowableInstanceNotThrown", "ThrowableInstanceNeverThrown"})
public static Object invokeMethod(Object instance, String methodName, Object... params) throws ReflectionException {
Class c = instance.getClass();
Class[] argTypes;
{
List<Class> cl = new ArrayList<>();
for (Object o : params) {
if (o != null) {
cl.add(o.getClass());
} else {
//If it's null, we'll just add null, and check it below
cl.add(null);
}
}
argTypes = cl.toArray(new Class[cl.size()]);
}
while (c != null) {
method:
for (Method m : c.getDeclaredMethods()) {
if (methodName.equals(m.getName())) {
try {
if (m.getParameterTypes().length == argTypes.length) {
Class[] args = m.getParameterTypes();
//Check to see that these arguments are subclasses
//of the method's parameters. If so, this is our method,
//otherwise, not.
for (int i = 0; i < argTypes.length; i++) {
// Null types match everything, so if argTypes[i] is null, then we
// don't care what the actual method type is.
if (argTypes[i] != null && !args[i].isAssignableFrom(argTypes[i])) {
continue method;
}
}
m.setAccessible(true);
return m.invoke(instance, params);
}
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
throw new ReflectionException(ex);
}
}
}
c = c.getSuperclass();
}
throw new ReflectionException(new NoSuchMethodException(methodName + " was not found in any of the searched classes."));
}
/**
* Grabs the method from the instance object automatically. {@code instance}
* may not be null.
*
* @param instance
* @param methodName
* @return
* @throws ReflectionException
*/
@SuppressWarnings({"ThrowableInstanceNotThrown", "ThrowableInstanceNeverThrown"})
public static Object invokeMethod(Object instance, String methodName) throws ReflectionException {
Class c = instance.getClass();
while (c != null) {
for (Method m : c.getDeclaredMethods()) {
if (methodName.equals(m.getName())) {
try {
return m.invoke(instance);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
throw new ReflectionException(ex);
}
}
}
c = c.getSuperclass();
}
throw new ReflectionException(new NoSuchMethodException(methodName + " was not found in any of the searched classes."));
}
/**
* Invokes a method with the parameters specified, disregarding access
* restrictions, and returns the result.
*
* @param clazz
* @param instance
* @param methodName
* @param argTypes
* @param args
* @return
*/
public static Object invokeMethod(Class clazz, Object instance, String methodName, Class[] argTypes, Object[] args) throws ReflectionException {
try {
Method m = clazz.getDeclaredMethod(methodName, argTypes);
m.setAccessible(true);
return m.invoke(instance, args);
} catch (InvocationTargetException | NoSuchMethodException | IllegalArgumentException | IllegalAccessException | SecurityException ex) {
throw new ReflectionException(ex);
}
}
/**
* Shorthand for {@link #PrintObjectTrace(instance, instanceOnly, null)}
* @param instance
* @param instanceOnly
*/
public static void PrintObjectTrace(Object instance, boolean instanceOnly) {
PrintObjectTrace(instance, instanceOnly, null);
}
/**
* Meant mostly as a debug tool, takes an object and prints out the object's
* non-static field information at this current point in time, to the
* specified PrintStream. This method will not throw any SecurityExceptions
* if a value cannot be reflectively accessed, but instead will print an
* error message for that single value.
*
* @param instance The object to explore. If this is null, "The object is
* null" is printed, and the method exits.
* @param instanceOnly If true, only the object's class members will be
* printed, otherwise, the method will recurse up the object's inheritance
* hierarchy, and prints everything.
* @param output The print stream to output to, or System.out if null.
*/
public static void PrintObjectTrace(Object instance, boolean instanceOnly, PrintStream output) {
if (output == null) {
output = System.out;
}
if (instance == null) {
output.println("The object is null");
return;
}
Class iClass = instance.getClass();
do {
for (Field f : iClass.getDeclaredFields()) {
if ((f.getModifiers() & Modifier.STATIC) > 0) {
continue;
}
String value = "null";
try {
f.setAccessible(true);
Object o = ReflectionUtils.get(iClass, instance, f.getName());
if (o != null) {
value = o.toString();
}
} catch (SecurityException e) {
value = "Could not access value due to a SecurityException";
}
output.println("(" + f.getType() + ") " + f.getName() + ": " + value);
}
} while (!instanceOnly && (iClass = iClass.getSuperclass()) != null);
}
/**
* Gets a set of all classes that this class extends or implements. In other words,
* {@link Class#isAssignableFrom(java.lang.Class)} will return true for
* all returned classes.
* @param c
* @return
*/
public static Set<Class> getAllExtensions(Class c){
Set<Class> cs = new HashSet<>();
Class cc = c.getSuperclass();
while(cc != null){
cs.add(cc);
for(Class ccc : cc.getInterfaces()){
cs.addAll(getAllExtensions(ccc));
}
cc = cc.getSuperclass();
}
for(Class i : c.getInterfaces()){
cs.addAll(getAllExtensions(i));
cs.add(i);
}
return cs;
}
/**
* Checks to see if a method with the given signature exists.
* @param c The class to check
* @param methodName The method name
* @param returnType The return type of the method, or if it is otherwise castable to this. Sending
* null or Object.class implies that the return type doesn't matter.
* @param params The signature of the method
* @return True, if the method with this signature exists.
* @throws ReflectionException If a SecurityException is thrown by the underlying
* code, this will be thrown.
*/
public static boolean hasMethod(Class<?> c, String methodName, Class returnType, Class ... params) throws ReflectionException {
if(returnType == null){
returnType = Object.class;
}
Method m;
try {
m = c.getClass().getMethod(methodName, params);
} catch (NoSuchMethodException ex) {
return false;
} catch (SecurityException ex) {
throw new ReflectionException(ex);
}
return returnType.isAssignableFrom(m.getReturnType());
}
}