package openmods.reflection;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import openmods.Log;
import openmods.utils.SneakyThrower;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.logging.log4j.Level;
public class ReflectionHelper {
public static class MethodNotFound extends RuntimeException {
private static final long serialVersionUID = 4931028064550261584L;
private MethodNotFound(Class<?> cls, String[] names, Class<?>[] args) {
super("Method not found: " + cls + "." + Arrays.toString(names) + Arrays.toString(args));
}
}
public static class FieldNotFound extends RuntimeException {
private static final long serialVersionUID = 6119776323412256889L;
private FieldNotFound(Class<?> cls, String... names) {
super("Field not found: " + cls + "." + Arrays.toString(names));
}
}
private static class NullMarker {
public final Class<?> cls;
private NullMarker(Class<?> cls) {
this.cls = cls;
}
}
private static class TypeMarker {
public final Class<?> cls;
public final Object value;
private TypeMarker(Class<?> cls, Object value) {
this.cls = cls;
this.value = value;
}
}
public static Object nullValue(Class<?> cls) {
return new NullMarker(cls);
}
public static Object typed(Object value, Class<?> cls) {
return new TypeMarker(cls, value);
}
public static Object primitive(char value) {
return new TypeMarker(char.class, value);
}
public static Object primitive(long value) {
return new TypeMarker(long.class, value);
}
public static Object primitive(int value) {
return new TypeMarker(int.class, value);
}
public static Object primitive(short value) {
return new TypeMarker(short.class, value);
}
public static Object primitive(byte value) {
return new TypeMarker(byte.class, value);
}
public static Object primitive(float value) {
return new TypeMarker(float.class, value);
}
public static Object primitive(double value) {
return new TypeMarker(double.class, value);
}
public static Object primitive(boolean value) {
return new TypeMarker(boolean.class, value);
}
@SuppressWarnings("unchecked")
public static <T> T getProperty(Class<?> klazz, Object instance, String... fields) {
Field field = getField(klazz == null? instance.getClass() : klazz, fields);
try {
return (T)field.get(instance);
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
public static <T> T getProperty(String className, Object instance, String... fields) {
return getProperty(getClass(className), instance, fields);
}
public static <T> T getProperty(Object instance, String... fields) {
return getProperty(instance.getClass(), instance, fields);
}
public static void setProperty(Class<?> klazz, Object instance, Object value, String... fields) {
Field field = getField(klazz == null? instance.getClass() : klazz, fields);
try {
field.set(instance, value);
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
public static void setProperty(String className, Object instance, Object value, String... fields) {
setProperty(getClass(className), instance, value, fields);
}
public static void setProperty(Object instance, Object value, String... fields) {
setProperty(instance.getClass(), instance, value, fields);
}
public static <T> T callStatic(Class<?> klazz, String methodName, Object... args) {
return call(klazz, null, ArrayUtils.toArray(methodName), args);
}
public static <T> T call(Object instance, String methodName, Object... args) {
return call(instance.getClass(), instance, ArrayUtils.toArray(methodName), args);
}
public static <T> T call(Object instance, String[] methodNames, Object... args) {
return call(instance.getClass(), instance, methodNames, args);
}
public static <T> T call(Class<?> cls, Object instance, String methodName, Object... args) {
return call(cls, instance, ArrayUtils.toArray(methodName), args);
}
@SuppressWarnings("unchecked")
public static <T> T call(Class<?> klazz, Object instance, String[] methodNames, Object... args) {
Method m = getMethod(klazz, methodNames, args);
for (int i = 0; i < args.length; i++) {
final Object arg = args[i];
if (arg instanceof NullMarker) args[i] = null;
if (arg instanceof TypeMarker) args[i] = ((TypeMarker)arg).value;
}
m.setAccessible(true);
try {
return (T)m.invoke(instance, args);
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
public static Method getMethod(Class<?> klazz, String[] methodNames, Object... args) {
if (klazz == null) return null;
Class<?> argTypes[] = new Class<?>[args.length];
for (int i = 0; i < args.length; i++) {
final Object arg = args[i];
Preconditions.checkNotNull(arg, "No nulls allowed, use wrapper types");
if (arg instanceof NullMarker) argTypes[i] = ((NullMarker)arg).cls;
else if (arg instanceof TypeMarker) argTypes[i] = ((TypeMarker)arg).cls;
else argTypes[i] = arg.getClass();
}
for (String name : methodNames) {
Method result = getDeclaredMethod(klazz, name, argTypes);
if (result != null) return result;
}
throw new MethodNotFound(klazz, methodNames, argTypes);
}
public static Method getMethod(Class<?> klazz, String[] methodNames, Class<?>... types) {
if (klazz == null) return null;
for (String name : methodNames) {
Method result = getDeclaredMethod(klazz, name, types);
if (result != null) return result;
}
throw new MethodNotFound(klazz, methodNames, types);
}
public static Set<Class<?>> getAllInterfaces(Class<?> clazz) {
ImmutableSet.Builder<Class<?>> result = ImmutableSet.builder();
Class<?> current = clazz;
while (current != null) {
result.add(current.getInterfaces());
current = current.getSuperclass();
}
return result.build();
}
public static Method getDeclaredMethod(Class<?> clazz, String name, Class<?>[] argsTypes) {
while (clazz != null) {
try {
final Method m = clazz.getDeclaredMethod(name, argsTypes);
ReflectionLog.logLoad(m);
return m;
} catch (NoSuchMethodException e) {} catch (Exception e) {
throw Throwables.propagate(e);
}
clazz = clazz.getSuperclass();
}
return null;
}
public static List<Method> getAllMethods(Class<?> clazz) {
List<Method> methods = Lists.newArrayList();
while (clazz != null) {
for (Method m : clazz.getDeclaredMethods()) {
methods.add(m);
ReflectionLog.logLoad(m);
}
clazz = clazz.getSuperclass();
}
return methods;
}
public static Field getField(Class<?> klazz, String... fields) {
for (String field : fields) {
Class<?> current = klazz;
while (current != null) {
try {
final Field f = current.getDeclaredField(field);
f.setAccessible(true);
ReflectionLog.logLoad(f);
return f;
} catch (NoSuchFieldException e) {} catch (Exception e) {
throw Throwables.propagate(e);
}
current = current.getSuperclass();
}
}
throw new FieldNotFound(klazz, fields);
}
public static Class<?> getClass(String className) {
if (Strings.isNullOrEmpty(className)) return null;
try {
final Class<?> cls = Class.forName(className);
ReflectionLog.logLoad(cls);
return cls;
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
public static Class<?> getClass(String... classNames) {
for (String className : classNames) {
Preconditions.checkNotNull(className);
try {
final Class<?> cls = Class.forName(className);
ReflectionLog.logLoad(cls);
return cls;
} catch (ClassNotFoundException e) {
Log.log(Level.DEBUG, e, "Class %s not found", className);
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
throw SneakyThrower.sneakyThrow(new ClassNotFoundException(Arrays.toString(classNames)));
}
}