package net.sourceforge.javautil.ui.command;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import net.sourceforge.javautil.common.ReflectionUtil;
import net.sourceforge.javautil.common.exception.ThrowableManagerRegistry;
import net.sourceforge.javautil.common.reflection.cache.ClassMethod;
import net.sourceforge.javautil.common.reflection.cache.ClassProperty;
import net.sourceforge.javautil.ui.UserInterfaceException;
import net.sourceforge.javautil.ui.command.UICommandArguments.PassedArgument;
import net.sourceforge.javautil.ui.command.UICommandArguments.PassedOption;
import net.sourceforge.javautil.ui.command.annotation.Argument;
import net.sourceforge.javautil.ui.command.annotation.Command;
import net.sourceforge.javautil.ui.command.annotation.Flag;
import net.sourceforge.javautil.ui.command.annotation.Option;
/**
* This will use a {@link ClassMethod} as the target for the command. The
* parameters can be defined as {@link Argument}'s, {@link Option}'s or
* {@link Flag}'s. This will also inject the context into the instance
* if a getter/setter exists on the class for the type {@link UICommandContext}
* or one of it's sub classes.
*
* @author elponderador
* @author $Author: ponderator $
* @version $Id: UICommandMethod.java 1527 2009-11-30 23:40:46Z ponderator $
*/
public class UICommandMethod<CTX extends UICommandContext, ARG extends UICommandArguments> extends UICommandAbstract<CTX, ARG> {
protected final Object instance;
protected final ClassMethod method;
protected final UICommandSetClass set;
protected List<String> defs = new ArrayList<String>();
protected ClassProperty contextProperty;
protected Command command;
public UICommandMethod(UICommandSetClass set, Object instance, ClassMethod method, String name, String description) {
super(name, description);
this.set = set;
this.method = method;
this.instance = instance;
this.getArgumentDefs(this.command = method.getAnnotation(Command.class));
this.contextProperty = method.getDescriptor().getProperty(net.sourceforge.javautil.ui.command.annotation.UICommandContext.class);
if (this.contextProperty != null && !UICommandContext.class.isAssignableFrom( this.contextProperty.getType() ))
throw new IllegalArgumentException("Context properties must be a sub class of UICommandContext");
}
public Object execute(CTX ctx, ARG arguments) {
try {
if (this.contextProperty != null) this.contextProperty.setValue(instance, ctx);
return method.invoke(instance, arguments == null ? new Object[method.getParameterTypes().length] : this.mapArgumentDefs(arguments));
} catch (UICommandException e) {
throw ThrowableManagerRegistry.caught(e);
} catch (UserInterfaceException e) {
throw ThrowableManagerRegistry.caught(e);
} catch (Exception e) {
throw ThrowableManagerRegistry.caught(new UICommandException(this, e));
}
}
/**
* @param arguments The passed command line arguments
* @return The array of values for the arguments
*/
protected Object[] mapArgumentDefs (ARG arguments) {
Object[] newargs = new Object[defs.size()];
Class[] types = method.getParameterTypes();
Argument[] args = command.arguments();
Option[] options = command.options();
Flag[] flags = command.flags();
for (int a=0; a<newargs.length; a++) {
String def = defs.get(a);
Class type = types[a];
Object value = null;
boolean required = false;
if (a >= args.length) {
int offset = a - args.length;
if (offset >= options.length) {
offset = offset - options.length;
if (offset < flags.length) {
value = arguments.isEnabled(def);
}
} else {
Option opt = options[offset];
PassedOption option = arguments.getOption(def);
value = option == null ? null : option.getValue();
}
} else {
PassedArgument pa = arguments.getArgument(a);
if (pa == null) throw new IllegalArgumentException("Missing argument: " + a);
required = true;
value = pa.getValue();
}
if (value == null && type.isPrimitive()) newargs[a] = ReflectionUtil.getPrimitiveDefault(type);
else newargs[a] = value == null ? null : set.convert(value, type);
if (required && newargs[a] == null) throw new UICommandException(this, "Argument missing or invalid: " + def);
}
return newargs;
}
/**
* Preprocess what arguments are expected
*/
protected void getArgumentDefs (Command command) {
Class[] types = method.getParameterTypes();
Argument[] args = command.arguments();
Option[] options = command.options();
Flag[] flags = command.flags();
for (int t=0; t<types.length; t++) {
Class type = types[t];
if (t >= args.length) {
int offset = t - args.length;
if (offset >= options.length) {
offset = offset - options.length;
if (offset >= flags.length) break;
Flag fla = flags[offset];
if (fla != null) {
if (type != boolean.class && type != Boolean.class)
throw new IllegalArgumentException("Invalid flag definition, can only be used on boolean types");
defs.add(fla.name());
this.addFlag(fla.name(), fla.value(), fla.defaultValue());
}
} else {
Option opt = options[offset];
defs.add(opt.name());
this.addOption(opt.name(), type, opt.desc(), opt.defaultDesc());
}
} else {
Argument arg = args[t];
defs.add(arg.name());
this.addArgument(arg.name(), type, arg.value());
}
}
}
}