libRefTools/src/com/tofvesson/reflection/SafeReflection.java
Gabriel Tofvesson cf32cf3ae3 Major fix
- Merged SafeReflection
2016-11-13 15:35:30 +01:00

392 lines
16 KiB
Java

package com.tofvesson.reflection;
import java.lang.reflect.Constructor;
import sun.misc.Unsafe;
import sun.reflect.ConstructorAccessor;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
/**
* Safe tools to help simplify code when dealing with reflection.
*/
@SuppressWarnings("unused")
public class SafeReflection {
private static final Unsafe unsafe;
static{
Unsafe u = null;
try{
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
u = (Unsafe) f.get(null);
}catch(Exception ignored){} // Exception is never thrown
unsafe = u;
}
/**
* Gets the constructor from the defined class with the specified parameters.
* @param c Class to get constructor from.
* @param params Definition of parameters that the constructor requires.
* @param <T> Return-type of constructor.
* @return Constructor with specified parameter requirements or null if constructor doesn't exist.
*/
public static <T> Constructor<T> getConstructor(Class<T> c, Class<?>... params){
try{
Constructor<T> c1 = c.getConstructor(params);
c1.setAccessible(true);
return c1;
}catch(Exception e){}
return null;
}
/**
* Gets the first constructor available from the given class.
* @param c Class to get constructor from.
* @param <T> Return-type of constructor.
* @return Constructor or null if something goes horribly wrong.
*/
public static <T> Constructor<T> getFirstConstructor(Class<T> c){
try {
@SuppressWarnings("unchecked")
Constructor<T> c1 = (Constructor<T>) c.getDeclaredConstructors()[0];
c1.setAccessible(true);
return c1;
}catch (Exception e){}
return null;
}
/**
* Gets the method from the defined class by name and parameters.
* Method is accessible.
* @param c Class to find method in.
* @param name Name of method.
* @param params Parameters of method.
* @return Method or null if specified method wasn't found.
*/
public static Method getMethod(Class<?> c, String name, Class<?>... params){
try{
Method m = c.getDeclaredMethod(name, params);
m.setAccessible(true);
return m;
}catch(Exception e){}
return null;
}
/**
* Attempts to invoke a supplied static method with the given parameters.
* @param m Method to invoke.
* @param params Parameters to supply to method.
* @return Return value of method or null if method is null.
*/
public static Object invokeStaticMethod(Method m, Object... params){
try{ return invokeMethod(null, m, params); }catch(Exception e){}
return null;
}
/**
* Attempts to invoke a supplied method with the given parameters on the supplied object.
* @param inst Object to invoke method in.
* @param m Method to invoke.
* @param params Parameters to supply to method.
* @return Return value of method or null if method is null.
*/
public static Object invokeMethod(Object inst, Method m, Object... params){
if(m!=null) m.setAccessible(true);
try{ return m!=null?m.invoke(inst, params):null; }catch(Exception e){}
return null;
}
/**
* Finds the first method available with the specified name from the class.
* Meant to be used in cases where a class only has one version of a method.
* Method is accessible.
* @param c Class to find method in.
* @param name Name of method.
* @return Method or null if no method with given name exists.
*/
public static Method getFirstMethod(Class<?> c, String name){
try{
Method[] m = c.getDeclaredMethods();
for (Method aM : m) if(aM.getName().equals(name)){ aM.setAccessible(true); return aM;}
}catch(Exception e){}
return null;
}
/**
* Gets field object referring to field with specified name in defined class.
* @param c Class to find field in.
* @param name Name of field.
* @return Field or null if no field with specified name exists.
*/
public static Field getField(Class<?> c, String name){
try{
Field f = c.getDeclaredField(name);
f.setAccessible(true);
return f;
}catch(Exception e){}
return null;
}
/**
* Gets the static object stored in the field with the specified name in the defined class.
* @param c Class to find object in.
* @param name Name of field.
* @return Object or null if object is null or field doesn't exist.
*/
public static Object getStaticFieldObject(Class<?> c, String name){
Field f = getField(c, name);
try { return f!=null?f.get(null):null; } catch (Exception e) { }
return null;
}
/**
* Gets the object stored in the field with the specified name in the class of the defined object.
* @param o Object to find object in.
* @param name Name of field.
* @return Object or null if object is null or field doesn't exist.
*/
public static Object getFieldObject(Object o, String name){
Field f = getField(o.getClass(), name);
try{ return f!=null?f.get(o):null; }catch(Exception e){}
return null;
}
/**
* Gets a reference to the enclosing class from a defined inner (nested) object.
* @param nested Object instance of a nested class.
* @return "this" reference to the outer class or null if class of object instance is static or isn't nested.
*/
public static Object getEnclosingClassObjectRef(Object nested){
try{
Field f = nested.getClass().getDeclaredField("this$0");
f.setAccessible(true);
return f.get(nested);
}catch(Exception e){}
return null;
}
/**
* Checks whether a given class is an inner (nested) class.
* @param c Class to check.
* @return True if class is nested otherwise false.
*/
public static boolean isNestedClass(Class<?> c){ return c.getEnclosingClass()!=null; }
/**
* Get class file based on class name.
* @param c Class to find file of.
* @return File if class file exists.
*/
public static java.io.File getClassFile(Class c){ return new java.io.File(c.getResource(c.getSimpleName()+".class").getFile()); }
/**
* Allows you to create a completely custom Enum value. If the supplied value already exists, it will be returned.
* will not be created; the value of the existing one will be updated.
* @param clazz The class to attribute the new enum to.
* @param addToValuesArray Whether or not to update the internal, "immutable" values array with the new value (ignored if value already exists).
* @return A new/existing enum.
*/
@SuppressWarnings("unchecked")
public static <T extends Enum<T>> T customEnum(Class<T> clazz, boolean addToValuesArray, String name, EnumDefinition def){
T u;
try {
// Get a reference to the static method values() and get values
Method v = clazz.getDeclaredMethod("values");
v.setAccessible(true);
T[] values = (T[]) v.invoke(null);
// Return object if it already exists
for(T u2 : values) if(u2.name().equals(name)) return u2;
// Generate enum parameter definition
Class[] paramList = new Class[def.params.size()+2];
paramList[0] = String.class; // Name
paramList[1] = int.class; // Ordinal
int iterator = paramList.length;
for(Class c : def.params.values()) paramList[--iterator] = c; // Shit's fucking reversed
// Get enum constructor (inherited from Enum.class)
Constructor c = clazz.getDeclaredConstructor(paramList);
c.setAccessible(true);
Method m = Constructor.class.getDeclaredMethod("acquireConstructorAccessor");
m.setAccessible(true);
// Get constructor accessor since Constructor.newInstance throws an exception because Enums are "immutable"
ConstructorAccessor access = (ConstructorAccessor) m.invoke(c);
Object[] parameters = new Object[def.params.size()+2];
parameters[0] = name;
parameters[1] = values.length;
iterator = parameters.length;
for(Object o : def.params.keySet()) parameters[--iterator] = o;
// Create new instance of enum with valid name and ordinal
u = (T) access.newInstance(parameters);
// Get the final name field from Enum.class and make it temporarily modifiable
Field f = Enum.class.getDeclaredField("name");
f.setAccessible(true);
// Rename the newly created enum to the requested name
f.set(u, name);
if(!addToValuesArray) return u; // Stops here if
// Get the current values field from Enum (a bitch to modify)
f = clazz.getDeclaredField("$VALUES");
f.setAccessible(true);
T[] $VALUES = (T[]) Array.newInstance(clazz, values.length+1);
System.arraycopy(values, 0, $VALUES, 0, values.length); // Copy over values from old array
$VALUES[values.length] = u; // Add out custom enum to our local array
// Start doing magic by getting an instance of sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl.class
// Class is package-local so we can't reference it by anything other than Object
m = Field.class.getDeclaredMethod("getFieldAccessor", Object.class);
m.setAccessible(true);
Object UQSOFAImpl = m.invoke(f, u);
// Get "isReadOnly" flag ($VALUES is always read-only even if Field.setAccessible(true) is called)
// Flag is located in superclass (sun.reflect.UnsafeQualifiedStaticFieldAccessorImpl.class (also fucking package-local))
// Set flag to 'false' to allow for modification against Java's will
Field f1 = UQSOFAImpl.getClass().getSuperclass().getDeclaredField("isReadOnly");
f1.setAccessible(true);
f1.setBoolean(UQSOFAImpl, false);
// Invoke set() method on UnsafeQualifiedStaticObjectFieldAccessorImpl object which sets the
// private field $VALUES to our new array
m = UQSOFAImpl.getClass().getDeclaredMethod("set", Object.class, Object.class);
m.setAccessible(true);
m.invoke(UQSOFAImpl, f, $VALUES);
} catch (Exception wrongParams) { throw new RuntimeException(wrongParams); }
return u;
}
/**
* Create a new object without the hassle of having to construct it. WARNING: Not usually a good idea.
* @param clazz Class to instantiate.
* @return An object instance of the supplied class that hasn't been constructed.
* @throws InstantiationException Thrown if instantiating the object fails for some reason.
*/
public static <T> T createNewObject(Class<T> clazz) throws InstantiationException{
//noinspection unchecked
return (T) unsafe.allocateInstance(clazz);
}
/**
* A definition for custom enum creation.
*/
public static class EnumDefinition {
HashMap<Object, Class> params = new HashMap<>(); // Assign a specific type to each parameter
/**
* Put an object in the parameter list.
* @param value The parameter to supply. (Type is derived automatically)
* @return A reference to the EnumDefinition object this method was called on (for chaining).
*/
public EnumDefinition putObject(Object value){
params.put(value, value.getClass());
return this;
}
/**
* Put a primitive value in the parameter list.
* @param autoBoxed An autoboxed version of the parameter. (For example putPrimitive(5) will automatically become Integer)
* @return A reference to the EnumDefinition object this method was called on (for chaining).
* @throws NotAutoBoxedException Thrown if a value expected to be autoboxed isn't autoboxed.
*/
public EnumDefinition putPrimitive(Object autoBoxed) throws NotAutoBoxedException{
// All autoboxed versions of primitives have a reference to their boxed primitive
try {
params.put(autoBoxed, autoBoxed.getClass().getDeclaredField("value").getType());
}catch(Exception e){ throw new NotAutoBoxedException(); }
return this;
}
/**
* Safe wrapper of {@link #putPrimitive(Object)} for boolean.
* @param b Boolean parameter.
* @return A reference to the EnumDefinition object this method was called on (for chaining).
*/
public EnumDefinition putBoolean(boolean b){
try { return putPrimitive(b); } catch (NotAutoBoxedException e) { }
return this;
}
/**
* Safe wrapper of {@link #putPrimitive(Object)} for byte.
* @param b Byte parameter.
* @return A reference to the EnumDefinition object this method was called on (for chaining).
*/
public EnumDefinition putByte(byte b){
try { return putPrimitive(b); } catch (NotAutoBoxedException e) { }
return this;
}
/**
* Safe wrapper of {@link #putPrimitive(Object)} for char.
* @param c Character parameter.
* @return A reference to the EnumDefinition object this method was called on (for chaining).
*/
public EnumDefinition putChar(char c){
try { return putPrimitive(c); } catch (NotAutoBoxedException e) { }
return this;
}
/**
* Safe wrapper of {@link #putPrimitive(Object)} for short.
* @param s Short parameter.
* @return A reference to the EnumDefinition object this method was called on (for chaining).
*/
public EnumDefinition putShort(short s){
try { return putPrimitive(s); } catch (NotAutoBoxedException e) { }
return this;
}
/**
* Safe wrapper of {@link #putPrimitive(Object)} for int.
* @param i Integer parameter.
* @return A reference to the EnumDefinition object this method was called on (for chaining).
*/
public EnumDefinition putInt(int i){
try { return putPrimitive(i); } catch (NotAutoBoxedException e) { }
return this;
}
/**
* Safe wrapper of {@link #putPrimitive(Object)} for long.
* @param l Long parameter.
* @return A reference to the EnumDefinition object this method was called on (for chaining).
*/
public EnumDefinition putLong(long l){
try { return putPrimitive(l); } catch (NotAutoBoxedException e) { }
return this;
}
/**
* Safe wrapper of {@link #putPrimitive(Object)} for float.
* @param f Floating point parameter.
* @return A reference to the EnumDefinition object this method was called on (for chaining).
*/
public EnumDefinition putFloat(float f){
try { return putPrimitive(f); } catch (NotAutoBoxedException e) { }
return this;
}
/**
* Safe wrapper of {@link #putPrimitive(Object)} for double.
* @param d Double parameter.
* @return A reference to the EnumDefinition object this method was called on (for chaining).
*/
public EnumDefinition putDouble(double d){
try { return putPrimitive(d); } catch (NotAutoBoxedException e) { }
return this;
}
}
}