content: package com.alecgorge.minecraft.jsonapi.dynamic;
  
  import java.lang.reflect.Array;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.HashMap;
  import java.util.List;
  
  import org.bukkit.Server;
  import org.bukkit.plugin.Plugin;
  
  import com.alecgorge.minecraft.jsonapi.APIException;
  import com.alecgorge.minecraft.jsonapi.JSONAPI;
  
  public class Call {
  	private static Server Server = JSONAPI.instance.getServer();
  
  	private Class<?>[] signature = new Class<?>[] {};
  	private ArrayList<Object> stack = new ArrayList<Object>();
  	private ArrayList<String> flags = new ArrayList<String>();
  	private HashMap<Integer, String> defaults = new HashMap<Integer, String>();
  	public static boolean debug = false;
  	private int expectedArgs = 0;
  
  	private boolean useDirectMethod = false;
  	private Object object;
  	private java.lang.reflect.Method method;
  
  	public Call(String input, ArgumentList args, ArrayList<String> flags) {
  		signature = args.getTypes();
  		this.flags = flags;
  		parseString(input);
  	}
  
  	public Call(Object o, java.lang.reflect.Method m, ArgumentList args) {
  		useDirectMethod = true;
  		signature = args.getTypes();
  		method = m;
  		object = o;
  	}
  
  	private void debug(String in) {
  		JSONAPI.dbug(in);
  	}
  
  	public int getNumberOfExpectedArgs() {
  		return expectedArgs;
  	}
  
  	public Object call(Object[] params) throws Exception {
  		if (useDirectMethod) {
  			return method.invoke(object, params);
  		}
  
  		int size = stack.size();
  
  		ArrayList<Object> oParams = new ArrayList<Object>(Arrays.asList(params));
  
  		oParams.ensureCapacity(oParams.size() + defaults.size());
  		for (Integer i : defaults.keySet()) {
  			oParams.add(i, defaults.get(i));
  		}
  
  		if (JSONAPI.shouldDebug) {
  			debug("oParams:" + oParams.toString());
  			debug("Stack:" + stack);
  		}
  
  		Object lastResult = null;
  		for (int i = 0; i < size; i++) {
  			Object v = stack.get(i);
  			if (JSONAPI.shouldDebug) {
  				debug("v:" + v.getClass().getCanonicalName());
  			}
  			if (v instanceof Server || v instanceof APIWrapperMethods || (i == 0 && v instanceof Plugin)) {
  				lastResult = v;
  			} else if (v instanceof SubField) {
  				SubField obj = (SubField) v;
  
  				if (JSONAPI.shouldDebug) {
  					debug("Requesting field: " + obj.getName());
  				}
  
  				if (obj.getName().equals("length") && lastResult.getClass().isArray()) {
  					lastResult = Array.getLength(lastResult);
  				} else {
  					java.lang.reflect.Field field;
  					try {
  						field = lastResult.getClass().getField(obj.getName());
  					} catch (NoSuchFieldException e) {
  						field = lastResult.getClass().getDeclaredField(obj.getName());
  					} catch (NoClassDefFoundError e) {
  						lastResult = null;
  						
  						return lastResult;
  					}
  					field.setAccessible(true);
  
  					lastResult = field.get(lastResult);
  				}
  			} else if (v instanceof SubCall) {
  				SubCall obj = (SubCall) v;
  
  				if (JSONAPI.shouldDebug) {
  					debug("Calling method: '" + obj.getName() + "' with signature: '" + obj.requiresArgs() + "' '" + Arrays.asList(sigForIndices(obj.requiresArgs())) + "'.");
  					debug("Last result:" + (lastResult == null ? null : lastResult.toString()));
  					debug("Invoking method: '" + obj.getName() + "' with args: '" + Arrays.asList(indicies(oParams, obj.requiresArgs())) + "'.");
  				}
  
  				Object[] args = indicies(oParams, obj.requiresArgs());
  				Class<?>[] sig = sigForIndices(obj.requiresArgs());
  				for (int x = 0; x < args.length; x++) {
  					Object val = args[x];
  					if ((val.getClass().equals(Long.class) || val.getClass().equals(Double.class) || val.getClass().equals(String.class) || val.getClass().equals(long.class) || val.getClass().equals(double.class)) && (sig[x].equals(Integer.class) || sig[x].equals(int.class))) {
  						args[x] = Integer.valueOf(val.toString());
  						val = args[x];
  					} else if ((val.getClass().equals(Integer.class) || val.getClass().equals(Long.class) || val.getClass().equals(String.class) || val.getClass().equals(long.class) || val.getClass().equals(int.class)) && (sig[x].equals(Double.class) || sig[x].equals(double.class))) {
  						args[x] = Double.valueOf(val.toString());
  						val = args[x];
  					} else if ((val.getClass().equals(Integer.class) || val.getClass().equals(Double.class) || val.getClass().equals(String.class) || val.getClass().equals(int.class) || val.getClass().equals(double.class)) && (sig[x].equals(Long.class) || sig[x].equals(long.class))) {
  						args[x] = Long.valueOf(val.toString());
  						val = args[x];
  					} else if ((val.getClass().equals(Integer.class) || val.getClass().equals(Double.class) || val.getClass().equals(String.class) || val.getClass().equals(int.class) || val.getClass().equals(double.class)) && (sig[x].equals(Boolean.class) || sig[x].equals(boolean.class))) {
  						if(val instanceof String) {
  							args[x] = false;
  							if(((String) val).toLowerCase().equals("true")) {
  								args[x] = true;
  							}
  						}
  						else {
  							args[x] = (Integer)val != 0;
  						}
  						val = args[x];
  					} else if (val instanceof List) {
  						sig[x] = List.class;
  					}
  					
  					if (JSONAPI.shouldDebug) {
  						debug("Arg " + x + ": '" + val + "', type: " + val.getClass().getName());
  						debug("Sig type: " + sig[x].getName());
  					}
  				}
  
  				if (flags.contains("NO_EXCEPTIONS") || flags.contains("FALSE_ON_EXCEPTION")) {
  					try {
  						java.lang.reflect.Method thisMethod;
  						try {
  							thisMethod = lastResult.getClass().getMethod(obj.getName(), sig);
  						} catch (NoSuchMethodException e) {
  							thisMethod = lastResult.getClass().getDeclaredMethod(obj.getName(), sig);
  						}
  						thisMethod.setAccessible(true);
  						lastResult = thisMethod.invoke(lastResult, args);
  					} catch (Exception e) {
  						if (flags.contains("FALSE_ON_EXCEPTION")) {
  							return false;
  						}
  						return null;
  					}
  				} else {
  					java.lang.reflect.Method thisMethod = null;
  					try {
  						thisMethod = lastResult.getClass().getMethod(obj.getName(), sig);
  					} catch (NoSuchMethodException e) {
  						thisMethod = lastResult.getClass().getDeclaredMethod(obj.getName(), sig);
  					} catch (NullPointerException e) {
  						throw new APIException("this returned null: " + stack.get(i - 1));
  					}
  					thisMethod.setAccessible(true);
  					lastResult = thisMethod.invoke(lastResult, args);
  				}
  
  				if (JSONAPI.shouldDebug) {
  					debug("New value:" + lastResult);
  				}
  			}
  		}
  
  		return lastResult;
  	}
  
  	public Object[] indicies(ArrayList<Object> o, ArrayList<Integer> i) {
  		if (i == null) {
  			return new Object[] {};
  		}
  
  		Object[] ret = new Object[i.size()];
  		for (int y = 0; y < ret.length; y++) {
  			ret[y] = o.get(i.get(y));
  		}
  
  		return ret;
  	}
  
  	public Class<?>[] sigForIndices(ArrayList<Integer> i) {
  		if (i == null) {
  			return new Class<?>[] {};
  		}
  
  		Class<?>[] ret = new Class<?>[i.size()];
  		for (int y = 0; y < ret.length; y++) {
  			ret[y] = signature[i.get(y)];
  		}
  
  		return ret;
  	}
  
  	public void parseString(String input) {
  		String[] parts = input.split("\\.(?=[a-zA-Z])");
  
  		for (int i = 0; i < parts.length; i++) {
  			String v = parts[i];
  			// if(i == 0) {
  			if (v.equals("Server")) {
  				stack.add(Server);
  			} else if (v.equals("this")) {
  				stack.add(APIWrapperMethods.getInstance());
  			} else if (v.equals("Plugins")) { // handles
  												// Plugins.PLUGINNAME.pluginMethod(0,1,2)
  				String v2 = parts[i + 1];
  				stack.add(Server.getPluginManager().getPlugin(v2));
  				i++;
  				continue;
  			}
  			// }
  			else {
  				// no args
  				if (v.endsWith("()")) {
  					stack.add(new SubCall(v.substring(0, v.length() - 2), new ArrayList<Integer>()));
  				}
  
  				// field
  				else if (!v.endsWith(")")) {
  					stack.add(new SubField(v));
  				}
  
  				// args
  				else {
  					// find the position of the args
  					int startPos = v.lastIndexOf("(");
  
  					// take the stuff in the ('s and )'s
  					String[] argParts = v.substring(startPos + 1, v.length() - 1).split(",");
  
  					ArrayList<Integer> argPos = new ArrayList<Integer>();
  
  					// put all string 0, 1, 2, 3 etc into a int[]
  					int multiplier = 0;
  
  					for (int x = 0; x < argParts.length; x++) {
  						if (argParts[x].trim().startsWith("\"") && argParts[x].trim().endsWith("\"")) {
  							defaults.put(x, argParts[x].trim().substring(1, argParts[x].trim().length() - 1));
  
  							ArrayList<Class<?>> cc = new ArrayList<Class<?>>(Arrays.asList(signature));
  							cc.add(x, String.class);
  							signature = cc.toArray(signature);
  
  							if (argPos.size() > 0) {
  								argPos.add(argPos.get(argPos.size() - 1) + 1);
  							} else {
  								argPos.add(0);
  							}
  
  							multiplier++;
  						} else {
  							try {
  								argPos.add(Integer.parseInt(argParts[x].trim()) + (multiplier));
  							} catch (NumberFormatException e) {
  								e.printStackTrace();
  							}
  						}
  					}
  
  					expectedArgs = argPos.size() - multiplier;
  
  					// add this "method" onto the stack
  					stack.add(new SubCall(v.substring(0, startPos), argPos));
  				}
  			}
  		}
  	}
  
  	static class SubField {
  		private String name;
  
  		public SubField(String name) {
  			this.name = name;
  		}
  
  		public String getName() {
  			return name;
  		}
  
  		@Override
  		public String toString() {
  			return "." + name;
  		}
  	}
  
  	static class SubCall {
  		private String name;
  		private ArrayList<Integer> argPos;
  
  		public SubCall(String name, ArrayList<Integer> argPos) {
  			this.name = name;
  			this.argPos = argPos;
  		}
  
  		public ArrayList<Integer> requiresArgs() {
  			return (argPos.size() > 0 ? argPos : null);
  		}
  
  		public String getName() {
  			return name;
  		}
  
  		@Override
  		public String toString() {
  			StringBuilder argList = new StringBuilder();
  			for (Integer i : argPos) {
  				argList.append(i).append(", ");
  			}
  
  			return "." + name + "(" + argList.toString() + ")";
  		}
  	}
  }