package jeql.engine;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import jeql.api.command.Command;
import jeql.api.error.ExecutionException;
import jeql.api.error.JeqlException;
import jeql.man.CommandParamMethod;
import jeql.man.CommandUtil;
import jeql.man.ManUtil;
import jeql.monitor.Monitor;
import jeql.syntax.CommandParameterNode;
public class CommandInvoker
{
public static final String DEFAULT_METHOD_NAME = "default";
private static final String DEFAULT_SETTER = "setDefault";
private static final String DEFAULT_GETTER = "getDefault";
public static final String SET_PREFIX = "set";
public static final String GET_PREFIX = "get";
private String cmdName;
private Class cmdClass;
private Method[] methods;
//temp local storage - not thread-safe
private Scope scope;
private boolean[] argUsed;
public CommandInvoker(String cmdName, Class cmdClass)
{
this.cmdName = cmdName;
this.cmdClass = cmdClass;
methods = cmdClass.getMethods();
}
public String getName() { return cmdName; }
public String getDescription() { return ManUtil.description(cmdClass); }
public Map<String, CommandParamMethod> getParameters() {
return CommandUtil.getParameters(getCommandClass());
}
public Class getCommandClass()
{
return cmdClass;
}
public void invoke(List args, Scope scope)
{
this.scope = scope;
argUsed = new boolean[args.size()];
Command cmdObject = null;
try {
cmdObject = (Command) cmdClass.newInstance();
setParameters(cmdObject, args);
cmdObject.execute(scope);
getParameters(cmdObject, args);
}
catch (JeqlException ex) {
throw ex;
}
catch (Exception e) {
Throwable ex = e;
if (e instanceof InvocationTargetException) {
ex = ((InvocationTargetException) e).getTargetException();
if (ex instanceof JeqlException)
throw (JeqlException) ex;
}
if (scope.getContext().isDebug()) {
System.out.println("------- originating exception -------");
ex.printStackTrace();
}
ExceptionHandler.handleExternal(ex, "Error in command " + cmdName);
}
checkAllArgsUsed(cmdObject, argUsed, args);
}
private static String paramDisplayName(String argName)
{
String paramDisplayString = "default";
if (argName != null) {
paramDisplayString = argName + ":";
}
return paramDisplayString;
}
private void checkAllArgsUsed(Command cmdObject, boolean[] argUsed, List args)
{
for (int i = 0; i < argUsed.length; i++) {
if (! argUsed[i]) {
CommandParameterNode cmdArg = (CommandParameterNode) args.get(i);
throw new ExecutionException(cmdArg, cmdName + " command does not accept the '"
+ paramDisplayName(cmdArg.getName()) + "' parameter");
}
}
}
private void setParameters(Object cmdObject, List args)
throws IllegalAccessException, InvocationTargetException
{
for (int i = 0; i < args.size(); i++ ) {
CommandParameterNode cmdArg = (CommandParameterNode) args.get(i);
setArg(i, cmdObject, cmdArg);
}
}
/**
* Calls a setter method with the value of a cmd argument,
* if such a method exists.
*
* @return true if arg was set
*/
private void setArg(int i, Object cmdObject, CommandParameterNode cmdParam)
throws IllegalAccessException, InvocationTargetException
{
Method setMethod = null;
if (cmdParam.isDefault()) {
setMethod = findSetMethod(DEFAULT_SETTER);
}
else {
String argName = cmdParam.getName();
setMethod = findSetMethod(getMethodName(SET_PREFIX, argName));
}
if (setMethod == null)
return;
argUsed[i] = true;
Object argVal = cmdParam.eval(scope);
setMethod.invoke(cmdObject, new Object[] { argVal });
}
private void getParameters(Object cmdObject, List args)
throws IllegalAccessException, InvocationTargetException
{
for (int i = 0; i < args.size(); i++ ) {
CommandParameterNode procArg = (CommandParameterNode) args.get(i);
getArg(i, cmdObject, procArg);
}
}
/**
* Calls a getter method to get a value,
* and sets the value of the parameter (which must be a variable name)
*/
private void getArg(int i, Object cmdObject, CommandParameterNode cmdParam)
throws IllegalAccessException, InvocationTargetException
{
if (! cmdParam.isAssignable()) {
return;
/*
throw new ExecutionException(cmdName + " command parameter '"
+ paramDisplayName(cmdArg.getName()) + "' has argument which is not assignable");
*/
}
Method getMethod = null;
if (cmdParam.isDefault()) {
getMethod = findGetMethod(DEFAULT_GETTER);
}
else {
String argName = cmdParam.getName();
getMethod = findGetMethod(getMethodName(GET_PREFIX, argName));
}
if (getMethod == null)
return;
argUsed[i] = true;
Object result = getMethod.invoke(cmdObject, new Object[0]);
String tag = cmdName + " " + cmdParam.getArgName();
result = Monitor.wrap(cmdParam.getLine(), tag, tag, result);
((BasicScope) scope).setVariable(cmdParam.getArgName(), result);
}
private static String getMethodName(String prefix, String name)
{
return prefix + name;
}
private Method findSetMethod(String name)
{
for (int i = 0; i < methods.length; i++) {
Method m = methods[i];
if (! m.getName().equalsIgnoreCase(name))
continue;
Class[] paramType = m.getParameterTypes();
if (paramType.length > 1)
throw new ConfigurationException("Command " + cmdName + " set method " + name + " takes more than 1 argument");
// assert: m param is assignable
return m;
}
return null;
}
private Method findGetMethod(String name)
{
for (int i = 0; i < methods.length; i++) {
Method m = methods[i];
if (! m.getName().equalsIgnoreCase(name))
continue;
Class[] paramType = m.getParameterTypes();
if (paramType.length > 0)
throw new ConfigurationException("Command " + cmdName + " get method " + name + " requires arguments");
return m;
}
return null;
}
private boolean isAssignable(Class dest, Class src)
{
if (dest.isAssignableFrom(src)) return true;
// also need to handle Double->double, Int->double etc
return false;
}
public void checkParametersExist(List args) {
for (int i = 0; i < args.size(); i++) {
CommandParameterNode cmdArg = (CommandParameterNode) args.get(i);
checkParameterExists(cmdArg);
}
}
private void checkParameterExists(CommandParameterNode cmdParam)
{
Method method = null;
if (cmdParam.isDefault()) {
method = findSetMethod(DEFAULT_SETTER);
if (method != null) return;
method = findSetMethod(DEFAULT_GETTER);
if (method != null) return;
}
String argName = cmdParam.getName();
method = findSetMethod(getMethodName(SET_PREFIX, argName));
if (method != null) return;
method = findGetMethod(getMethodName(GET_PREFIX, argName));
if (method != null) return;
// can't find method - error
throw new CompilationException(cmdParam, "Command " + cmdName + " does not accept parameter '" + argName + "'");
}
}