package net.glowstone.util;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
public class ReflectionProcessor {
private String line;
private Object[] context;
/**
* Creates a new reflection processor instance
*
* @param line the reflection line. The format of this line is defined by the following;
* Use $ to reference the first context (equivalent to $1)
* Use $x to reference a specific context, where 'x' is the index of the context (starting from 1)
* To reference a static method/field call, specifying the full package is required
* More info here: https://github.com/momothereal/OneLineReflection/blob/master/README.md
* @param context the context(s) of the reflection line
*/
public ReflectionProcessor(String line, Object... context) {
this.line = line;
this.context = context;
}
/**
* Processes the given reflective line
*
* @return the resultant value of the reflective line
*/
public Object process() {
String tmpPackage = "";
Object cxt = null;
String[] sections = splitUpper();
for (int i = 0; i < sections.length; i++) {
String section = sections[i];
if (section.equals("$") || section.equals("this")) { // Context #1
cxt = context[0];
} else if (section.startsWith("$") && section.substring(1, section.length()).matches("[0-9]+")) { // Context #X
int index = Integer.valueOf(section.replace("$", "")) - 1;
cxt = context[index];
} else if (section.startsWith("\"") && section.endsWith("\"")) { // String literal
section = section.substring(1, section.length() - 1);
if (i == sections.length - 1)
return section;
cxt = section;
} else if (section.equals("true") || section.equals("false")) { // Boolean literal
Boolean value = Boolean.valueOf(section);
if (i == sections.length - 1)
return value;
cxt = value;
} else if (section.replaceAll("[0-9]+", "").equals("")) { // Integer literal
Integer value = Integer.valueOf(section);
if (i == sections.length - 1)
return value;
cxt = value;
} else if (section.replaceAll("[0-9]+", "").toLowerCase().equals("l")) { // Long literal
Long value = Long.valueOf(section.substring(0, section.length() - 1));
if (i == sections.length - 1)
return value;
cxt = value;
} else if (section.contains("(") && section.contains(")")) { // Method
String name = getMethodName(section);
String[] parameters = getMethodParams(section);
if (i == sections.length - 1)
return invokeMethod(cxt, name, parameters);
cxt = invokeMethod(cxt, name, parameters);
} else {
Object field = invokeField(cxt, section);
if (field == null) { // Class
tmpPackage += section + ".";
Object clazz = invokeClass(tmpPackage);
if (clazz != null) {
if (i == sections.length - 1)
return clazz;
cxt = clazz;
tmpPackage = "";
}
} else { // Field
if (i == sections.length - 1)
return field;
cxt = field;
}
}
}
return cxt;
}
/**
* Returns the returned value of an invoked method in a contextual object
*
* @param context the object
* @param name the name of the method
* @return the invokation's return value
*/
private Object invokeMethod(Object context, String name, String[] parameters) {
try {
ArrayList<Object> params = new ArrayList<>();
for (String parameter : parameters) {
Object result = new ReflectionProcessor(parameter, this.context).process();
if (result != null) {
params.add(result);
}
}
Class[] classes = new Class[params.size()];
for (int i = 0; i < params.size(); i++) {
Object param = params.get(i);
classes[i] = param.getClass();
}
Class clazz = context.getClass();
if (context instanceof Class)
clazz = (Class) context;
Method method = getMethod(name, clazz, classes);
if (!method.isAccessible())
method.setAccessible(true);
return method.invoke(context, params.toArray(new Object[params.size()]));
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
return null;
}
private Method getMethod(String name, Class clazz, Class[] parameters) {
try {
return clazz.getMethod(name, parameters);
} catch (NoSuchMethodException e) {
for (Method method : clazz.getMethods()) {
if (!method.getName().equals(name)) {
continue;
}
if (method.getParameterCount() != parameters.length) {
continue;
}
boolean matches = true;
a:
for (Class<?> param : method.getParameterTypes()) {
for (Class p : parameters) {
if (!p.equals(param)) {
matches = false;
break a;
}
}
}
if (matches) {
return method;
}
matches = true;
b:
for (Class<?> param : method.getParameterTypes()) {
for (Class p : parameters) {
if (!param.isAssignableFrom(p)) {
matches = false;
break b;
}
}
}
if (matches) {
return method;
}
}
}
return null;
}
/**
* Returns the value of a field in a contextual object
*
* @param context the object
* @param name the name of the field
* @return the field's value in context
*/
private Object invokeField(Object context, String name) {
try {
Field field = context.getClass().getField(name);
if (!field.isAccessible())
field.setAccessible(true);
return field.get(context);
} catch (Exception ignored) {
}
return null;
}
/**
* Invokes a class from its full-length name, including package
*
* @param name the name of the class, preceded with its package
* @return the specified class, null if it is non-existent
*/
private Class invokeClass(String name) {
if (name.endsWith("."))
name = name.substring(0, name.length() - 1);
try {
return (Class) ClassLoader.getSystemClassLoader().loadClass(name);
} catch (ClassNotFoundException e) {
return null;
}
}
/**
* Gets the parameters inside a method parentheses enclosure
*
* @param section the method and its parameters, which are enclosed in parentheses and separated with commas (,)
* @return the parameters
*/
private String[] getMethodParams(String section) {
int level = 0;
boolean inString = false;
String current = "";
List<String> parameters = new ArrayList<>();
char[] charArray = section.toCharArray();
for (int i = 0; i < charArray.length; i++) {
char c = charArray[i];
if (c == '(' && !inString) {
level++;
if (level > 1) {
current += c;
}
continue;
}
if (c == ')' && !inString) {
level--;
if (level == 0) {
parameters.add(current);
current = "";
} else {
current += c;
}
continue;
}
if (level == 1 && c == ',' && !inString) {
parameters.add(current);
current = "";
continue;
}
if (!inString && c == ' ')
continue;
if (c == '\"' && section.charAt(i - 1) != '\\') {
inString = !inString;
}
if (level > 0)
current += c;
}
return parameters.toArray(new String[parameters.size()]);
}
/**
* Gets the name of a method from a segment
*
* @param section the segment containing the method (including parentheses)
* @return the name of the method
*/
private String getMethodName(String section) {
StringBuilder builder = new StringBuilder();
for (char c : section.toCharArray()) {
if (c == '(')
return builder.toString();
builder.append(c);
}
return builder.toString();
}
private String[] splitUpper() {
ArrayList<String> sections = new ArrayList<>();
String current = "";
int level = 0;
for (char c : line.toCharArray()) {
if (level == 0 && c == '.') {
sections.add(current);
current = "";
continue;
}
if (c == '(') {
level++;
}
if (c == ')') {
level--;
}
current += c;
}
if (!current.equals(""))
sections.add(current);
return sections.toArray(new String[sections.size()]);
}
}