/* * Copyright (c) 2003-2006, KNOPFLERFISH project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following * conditions are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials * provided with the distribution. * * - Neither the name of the KNOPFLERFISH project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.knopflerfish.service.console; import java.io.PrintWriter; import java.io.Reader; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Dictionary; import java.util.Hashtable; /** * Adapter class for CommandGroup. Simplifies the creations of command groups. * It takes away the work of parsing command lines and generates help text. It * uses reflection to check which commands you have created. *
* To create your own command group you extend this class and add two variables * and a method. *
USAGE_{NAME}* usage : [ flags ] [ args [ '...' ]] * flags : flag [ flags ] * flags : oflag [ flags ] * flag : flag '|' flag * flag : '-'FLAGNAME [ '#'[TEXT['#']] ] * oflag : '[' flag ']' * args : '<' ARGNAME '>' * args : '[' args ']' ** * Note: The flag "-help" is automatically added and handled by * CommandGroupAdapter.
HELP_{NAME}int cmd{Name}(Dictionary, Reader, PrintWriter, Session)
* The object must then be registered under the class name
* org.knopflerfish.service.console.CommandGroup with the
* property "groupName" set to the command group name.
*
* Example: * *
* package com.apa;
*
* import java.io.*;
* import java.util.*;
*
* import org.knopflerfish.service.console.*;
*
* public class MyCommandGroup extends CommandGroupAdapter {
*
* MyCommandGroup() {
* super("echocommands", "Echo commands");
* }
*
* public final static String USAGE_ECHO = "[-n] <text> ...";
*
* public final static String[] HELP_ECHO = new String[] {
* "Echo command arguments", "-n Don't add newline at end",
* "<text> Text to echo" };
*
* public int cmdEcho(Dictionary opts, Reader in, PrintWriter out,
* Session session) {
* String[] t = (String[]) opts.get("text");
* for (int i = 0; i < t.length; i++) {
* out.print(t[i]);
* }
* if (opts.get("-n") == null) {
* out.println();
* }
* return 0;
* }
* }
*
*
* @author Gatespace AB
*/
public abstract class CommandGroupAdapter implements CommandGroup {
/**
* Full class name of CommandGroup interface.
*/
public final static String COMMAND_GROUP = org.knopflerfish.service.console.CommandGroup.class
.getName();
String groupName;
String shortHelp;
/**
* Constructs a command group. This should be called via super() if you
* extend this class.
*
* @param groupName
* the name for this command group
* @param shortHelp
* one line description of this command group
*/
public CommandGroupAdapter(String groupName, String shortHelp) {
this.groupName = groupName;
this.shortHelp = shortHelp;
}
/**
* Returns the command group name. This methods returns the group name
* registered via the constructor.
*
* @return Group name.
*/
public String getGroupName() {
return groupName;
}
/**
* Returns short command group help. Returns the short help message
* registered via the constructor.
*
* @return short command group help.
*/
public String getShortHelp() {
return shortHelp;
}
/**
* Returns long command group help. This is built using the
* HELP_{CMD} and USAGE_{CMD} variables of the
* sub-class.
*
* @return long command group help.
*/
public String getLongHelp() {
StringBuffer res = new StringBuffer();
res.append("Available " + groupName + " commands:\n");
Field[] f = getClass().getFields();
for (int i = 0; i < f.length; i++) {
String name = f[i].getName();
if (name.startsWith("HELP_")) {
try {
name = name.substring(5).toLowerCase();
DynamicCmd cmd = new DynamicCmd(this, name);
res.append(" " + name + " [-help] " + cmd.usage + " - "
+ cmd.help[0] + "\n");
} catch (Exception ignore) {
}
}
}
return res.toString();
}
/**
* Executes a command in the command group. This parses the command line,
* matches it with commands from the sub-class, check the
* USAGE_{CMD} string of that command and calls its
* cmd{Cmd} method of the sub-class.
*
* @param args
* argument list passed to the command
* @param out
* output device to print result
* @param in
* input for command
* @param session
* a handle to command session or null if single command
* @return status from execution, 0 means okey
*/
public int execute(String[] args, Reader in, PrintWriter out,
Session session) {
if (args.length == 0 || args[0] == null || args[0].length() == 0) {
return -1;
}
DynamicCmd cmd;
try {
cmd = new DynamicCmd(this, args[0]);
} catch (Exception e) {
out.println(e.getMessage());
return -2;
}
for (int i = 0; i < args.length; i++) {
if ("-help".equals(args[i])) {
out.println("Usage: " + args[0] + " [-help] " + cmd.usage);
for (int j = 0; j < cmd.help.length; j++) {
out.println(" " + cmd.help[j]);
}
return 0;
}
}
try {
Integer res = (Integer) cmd.cmd.invoke(this, new Object[] {
getOpt(args, cmd.usage), in, out, session });
return res.intValue();
} catch (IllegalAccessException e) {
out.println("Command failed: " + e.getMessage());
return -1;
} catch (InvocationTargetException e) {
out.println("Command execution failed, stack trace follows:");
e.getTargetException().printStackTrace(out);
return -1;
} catch (Exception e) {
out.println(e.getMessage());
return -1;
}
}
/**
* Method to do argument parsing. See parsed syntax in class
* CommandGroupAdapter description.
*
* @param args
* argument list passed to the command
* @param usage
* usage string
* @return Dictionary with parsed arguments
* @exception Exception
* Thrown if it fails to parse args or usage
*/
public Dictionary getOpt(String[] args, String usage) throws Exception {
Hashtable res = new Hashtable();
res.put("command", args[0]);
args[0] = null;
parseUsage(usage.trim(), 0, args, res, 0);
for (int i = 0; i < args.length; i++) {
if (args[i] != null) {
throw new Exception("Unknown argument: " + args[i]);
}
}
return res;
}
//
// Internal methods
//
private final static String DOING_ARGS = "_D_";
private final static String LAST = "_L_";
private int parseUsage(String usage, int pos, String[] args, Hashtable res,
int level) throws Exception {
int ulen = usage.length();
while (pos < ulen) {
switch (usage.charAt(pos)) {
case '-':
if (res.containsKey(DOING_ARGS)) {
throw new Exception("Can not mix flags and args: "
+ usage.substring(pos));
}
boolean found = false;
String flag = "";
boolean alt;
do {
alt = false;
int start = pos++;
while (pos < ulen && usage.charAt(pos) != ' '
&& usage.charAt(pos) != ']')
pos++;
flag = usage.substring(start, pos);
Object val = new Integer(1);
int fpos = -1;
for (int i = 0; i < args.length; i++) {
if (flag.equals(args[i])) {
fpos = i;
break;
}
}
while (pos < ulen && usage.charAt(pos) == ' ')
pos++;
if (pos < ulen) {
if (usage.charAt(pos) == '#') {
while (pos < ulen && usage.charAt(pos) != ' '
&& usage.charAt(pos) != ']')
pos++;
if (fpos >= 0) {
if (fpos + 1 < args.length) {
val = args[fpos + 1];
args[fpos + 1] = null;
} else {
throw new Exception("No value for: " + flag);
}
}
while (pos < ulen && usage.charAt(pos) == ' ')
pos++;
}
if (pos < ulen) {
if (usage.charAt(pos) == '|') {
pos++;
while (pos < ulen && usage.charAt(pos) == ' ')
pos++;
if (usage.charAt(pos) == '-') {
alt = true;
} else {
throw new Exception(
"Missing flag in OR-expression: "
+ usage.substring(pos));
}
}
}
}
if (fpos >= 0) {
Object old = res.put(args[fpos], val);
if (old != null) {
if (old instanceof Integer) {
res.put(args[fpos], new Integer(((Integer) old)
.intValue() + 1));
} else {
throw new Exception(
"Duplicate flagname with value in usage: "
+ args[fpos]);
}
}
args[fpos] = null;
found = true;
}
} while (alt);
if (!found && level == 0) {
throw new Exception("Mandatory flag not set, flags: "
+ usage);
}
break;
case '<':
res.put(DOING_ARGS, "");
int wstart = ++pos;
pos = usage.indexOf('>', ++pos);
if (pos == -1) {
throw new Exception("Unmatched: "
+ usage.substring(wstart - 1));
}
String key = usage.substring(wstart, pos++);
int i;
for (i = 0; i < args.length; i++) {
if (args[i] != null) {
if (args[i].startsWith("-")) {
// '--' means '-' at begining of args
if (args[i].startsWith("--")) {
args[i] = args[i].substring(1);
} else {
throw new Exception("Unknown flag: " + args[i]);
}
}
if (res.put(key, args[i]) != null) {
throw new Exception("Duplicate argname in usage: "
+ key);
}
args[i] = null;
break;
}
}
if (i == args.length && level == 0) {
throw new Exception("Mandatory argument not set: " + key);
}
res.put(LAST, key);
break;
case '[':
pos = parseUsage(usage, pos + 1, args, res, level + 1);
break;
case ']':
if (level == 0) {
throw new Exception("Unmatched: " + usage.substring(pos));
}
return pos + 1;
case '.':
if (usage.substring(pos).equals("...")) {
String repeat = (String) res.get(LAST);
if (repeat != null && res.containsKey(repeat)) {
ArrayList v = new ArrayList();
do {
v.add(res.remove(repeat));
parseUsage("<" + repeat + ">]", 0, args, res, 1);
} while (res.containsKey(repeat));
String[] vres = new String[v.size()];
res.put(repeat, v.toArray(vres));
}
pos = ulen;
} else {
throw new Exception("Unexpected usage end: "
+ usage.substring(pos));
}
break;
case ' ':
pos++;
break;
default:
throw new Exception("Unexpected character: "
+ usage.charAt(pos));
}
}
if (level > 0) {
throw new Exception("Missing " + level + " closing ']'");
}
res.remove(DOING_ARGS);
res.remove(LAST);
return pos;
}
}
class DynamicCmd {
Method cmd;
String usage;
String[] help;
DynamicCmd(CommandGroup cg, String name) throws Exception {
try {
Class cls = cg.getClass();
String hname = "HELP_" + name.toUpperCase();
Field[] f = cls.getFields();
int match = -1;
boolean multiple = false;
for (int i = 0; i < f.length; i++) {
String fname = f[i].getName();
if (fname.equals(hname)) {
match = i;
name = fname.substring(5);
multiple = false;
break;
} else if (fname.startsWith(hname)) {
if (match != -1) {
multiple = true;
continue;
}
match = i;
name = fname.substring(5);
}
}
if (match == -1) {
throw new Exception("No such command: " + name);
}
if (multiple) {
throw new Exception("Multiple matching commands for: "
+ hname.substring(5));
}
help = (String[]) f[match].get(cg);
usage = (String) cls.getField("USAGE_" + name.toUpperCase())
.get(cg);
cmd = cls.getMethod("cmd" + name.substring(0, 1).toUpperCase()
+ name.substring(1).toLowerCase(), new Class[] {
java.util.Dictionary.class, java.io.Reader.class,
java.io.PrintWriter.class, Session.class });
if (!cmd.getReturnType().getName().equals("int")) {
throw new Exception("No such command: " + name);
}
} catch (ClassNotFoundException e) {
throw new Exception("Internal error");
} catch (NoSuchFieldException e) {
throw new Exception(
"Command implementation incomplete (USAGE string missing): "
+ name);
} catch (NoSuchMethodException e) {
throw new Exception(
"Command implementation incomplete (cmd method missing): "
+ name);
}
}
}