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()]); } }