/*
* Copyright 1999-2002 Carnegie Mellon University.
* Portions Copyright 2002 Sun Microsystems, Inc.
* Portions Copyright 2002 Mitsubishi Electric Research Laboratories.
* All Rights Reserved. Use is subject to license terms.
*
* See the file "license.terms" for information on usage and
* redistribution of this file, and for a DISCLAIMER OF ALL
* WARRANTIES.
*
*/
package edu.cmu.sphinx.util;
import java.io.*;
import java.net.Socket;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
/**
* This class is a command interpreter. It reads strings from an input stream, parses them into commands and executes
* them, results are sent back on the output stream.
*
* @see CommandInterpreter
*/
public class CommandInterpreter extends Thread {
private Map<String, CommandInterface> commandList;
private int totalCommands;
private BufferedReader in;
private PrintWriter out;
private String prompt;
private boolean done;
private boolean trace;
private final CommandHistory history = new CommandHistory();
private Socket socket;
/**
* Creates a command interpreter that reads/writes on the given streams.
*
* @param in the input stream.
* @param out the output stream.
*/
public CommandInterpreter(BufferedReader in, PrintWriter out) {
init(in, out);
}
/**
* Sets the trace mode of the command interpreter.
*
* @param trace true if tracing.
*/
public void setTrace(boolean trace) {
this.trace = trace;
}
/** Creates a command interpreter that won't read a stream. */
public CommandInterpreter() {
BufferedReader bin =
new BufferedReader(new InputStreamReader(System.in));
PrintWriter pw = new PrintWriter(System.out);
init(bin, pw);
}
/** Initializes the CI */
private void init(BufferedReader in, PrintWriter out) {
commandList = new HashMap<String, CommandInterface>();
addStandardCommands();
setStreams(in, out);
}
/**
* Sets the I/O streams
*
* @param in the input stream.
* @param out the output stream.
*/
public void setStreams(BufferedReader in, PrintWriter out) {
this.in = in;
this.out = out;
}
/** @return the Socket this CommandInterpreter uses. */
public Socket getSocket() {
return socket;
}
/**
* Sets the Socket for this CommandInterpreter.
*
* @param skt the Socket this CommandInterpreter uses
*/
public void setSocket(Socket skt) {
socket = skt;
}
/** Adds the set of standard commands */
private void addStandardCommands() {
add("help", new CommandInterface() {
public String execute(CommandInterpreter ci, String[] args) {
dumpCommands();
return "";
}
public String getHelp() {
return "lists available commands";
}
});
add("history", new CommandInterface() {
public String execute(CommandInterpreter ci, String[] args) {
history.dump();
return "";
}
public String getHelp() {
return "shows command history";
}
});
add("status", new CommandInterface() {
public String execute(CommandInterpreter ci, String[] args) {
putResponse("Total number of commands: " + totalCommands);
return "";
}
public String getHelp() {
return "shows command status";
}
});
add("echo", new CommandInterface() {
public String execute(CommandInterpreter ci, String[] args) {
StringBuilder b = new StringBuilder(80);
for (int i = 1; i < args.length; i++) {
b.append(args[i]);
b.append(' ');
}
putResponse(b.toString());
return "";
}
public String getHelp() {
return "display a line of text";
}
});
add("quit", new CommandInterface() {
public String execute(CommandInterpreter ci, String[] args) {
done = true;
return "";
}
public String getHelp() {
return "exit the shell";
}
});
add("on_exit", new CommandInterface() {
public String execute(CommandInterpreter ci, String[] args) {
return "";
}
public String getHelp() {
return "command executed upon exit";
}
});
add("version", new CommandInterface() {
public String execute(CommandInterpreter ci, String[] args) {
putResponse("Command Interpreter - Version 1.1 ");
return "";
}
public String getHelp() {
return "displays version information";
}
});
add("gc", new CommandInterface() {
public String execute(CommandInterpreter ci, String[] args) {
Runtime.getRuntime().gc();
return "";
}
public String getHelp() {
return "performs garbage collection";
}
});
add("memory", new CommandInterface() {
public String execute(CommandInterpreter ci, String[] args) {
long totalMem = Runtime.getRuntime().totalMemory();
long freeMem = Runtime.getRuntime().freeMemory();
putResponse("Free Memory : "
+ freeMem / (1024.0 * 1024) + " mbytes");
putResponse("Total Memory : "
+ totalMem / (1024.0 * 1024) + " mbytes");
return "";
}
public String getHelp() {
return "shows memory statistics";
}
});
add("delay", new CommandInterface() {
public String execute(CommandInterpreter ci, String[] args) {
if (args.length == 2) {
try {
float seconds = Float.parseFloat(args[1]);
Thread.sleep((long) (seconds * 1000));
} catch (NumberFormatException nfe) {
putResponse("Usage: delay time-in-seconds");
} catch (InterruptedException ie) {
}
} else {
putResponse("Usage: delay time-in-seconds");
}
return "";
}
public String getHelp() {
return "pauses for a given number of seconds";
}
});
add("repeat", new CommandInterface() {
public String execute(CommandInterpreter ci, String[] args) {
if (args.length >= 3) {
try {
int count = Integer.parseInt(args[1]);
String[] subargs = Arrays.copyOfRange(args, 2, args.length);
for (int i = 0; i < count; i++) {
putResponse(CommandInterpreter.this.execute(subargs));
}
} catch (NumberFormatException nfe) {
putResponse("Usage: repeat count command args");
}
} else {
putResponse("Usage: repeat count command args");
}
return "";
}
public String getHelp() {
return "repeatedly execute a command";
}
});
add("load", new CommandInterface() {
public String execute(CommandInterpreter ci, String[] args) {
if (args.length == 2) {
if (!load(args[1])) {
putResponse("load: trouble loading " + args[1]);
}
} else {
putResponse("Usage: load filename");
}
return "";
}
public String getHelp() {
return "load and execute commands from a file";
}
});
add("chain", new CommandInterface() {
public String execute(CommandInterpreter ci, String[] args) {
if (args.length > 1) {
String[] subargs = new String[args.length - 1];
List<String[]> commands = new ArrayList<String[]>(5);
int count = 0;
for (int i = 1; i < args.length; i++) {
if (args[i].equals(";")) {
if (count > 0) {
commands.add(Arrays.copyOf(subargs, count));
count = 0;
}
} else {
subargs[count++] = args[i];
}
}
if (count > 0) {
commands.add(Arrays.copyOf(subargs, count));
}
for (String[] command : commands) {
putResponse(CommandInterpreter.this.execute(command));
}
} else {
putResponse("Usage: chain cmd1 ; cmd2 ; cmd3 ");
}
return "";
}
public String getHelp() {
return "execute multiple commands on a single line";
}
});
add("time", new CommandInterface() {
public String execute(CommandInterpreter ci, String[] args) {
if (args.length > 1) {
String[] subargs = Arrays.copyOfRange(args, 1, args.length);
long startTime = System.currentTimeMillis();
long endTime;
putResponse(CommandInterpreter.this.execute(subargs));
endTime = System.currentTimeMillis();
putResponse("Time: " + ((endTime - startTime) / 1000.0)
+ " seconds");
} else {
putResponse("Usage: time cmd [args]");
}
return "";
}
public String getHelp() {
return "report the time it takes to run a command";
}
});
}
/** Dumps the commands in the interpreter */
private void dumpCommands() {
for (Map.Entry<String, CommandInterface> entry : new TreeMap<String, CommandInterface>(commandList).entrySet())
putResponse(entry.getKey() + " - " + entry.getValue().getHelp());
}
/**
* Adds the given command to the command list.
*
* @param name the name of the command.
* @param command the command to be executed.
*/
public void add(String name, CommandInterface command) {
commandList.put(name, command);
}
/**
* Adds an alias to the command
*
* @param command the name of the command.
* @param alias the new aliase
*/
public void addAlias(String command, String alias) {
commandList.put(alias, commandList.get(command));
}
/**
* Add the given set of commands to the list of commands.
*
* @param newCommands the new commands to add to this interpreter.
*/
public void add(Map<String, CommandInterface> newCommands) {
commandList.putAll(newCommands);
}
/**
* Outputs a response to the sender.
*
* @param response the response to send.
*/
public synchronized void putResponse(String response) {
if (response != null && !response.isEmpty()) {
out.println(response);
out.flush();
if (trace) {
System.out.println("Out: " + response);
}
}
}
/** Called when the interpreter is exiting. Default behavior is to execute an "on_exit" command. */
protected void onExit() {
execute("on_exit");
System.out.println("----------\n");
}
/**
* Execute the given command.
*
* @param args command args, args[0] contains name of cmd.
* @return result string
*/
protected String execute(String[] args) {
String response = "";
CommandInterface ci;
if (args.length > 0) {
ci = commandList.get(args[0]);
if (ci != null) {
response = ci.execute(this, args);
} else {
response = "ERR CMD_NOT_FOUND";
}
totalCommands++;
}
return response;
}
/**
* Execute the given command string.
*
* @param cmdString the command string.
* @return result string
*/
public String execute(String cmdString) {
if (trace) {
System.out.println("Execute: " + cmdString);
}
return execute(parseMessage(cmdString));
}
/**
* Parses the given message into an array of strings.
*
* @param message the string to be parsed.
* @return the parsed message as an array of strings
*/
protected String[] parseMessage(String message) {
int tokenType;
List<String> words = new ArrayList<String>(20);
StreamTokenizer st = new StreamTokenizer(new StringReader(message));
st.resetSyntax();
st.whitespaceChars(0, ' ');
st.wordChars('!', 255);
st.quoteChar('"');
st.quoteChar('\"');
st.commentChar('#');
while (true) {
try {
tokenType = st.nextToken();
if (tokenType == StreamTokenizer.TT_WORD) {
words.add(st.sval);
} else if (tokenType == '\'' || tokenType == '"') {
words.add(st.sval);
} else if (tokenType == StreamTokenizer.TT_NUMBER) {
System.out.println("Unexpected numeric token!");
} else {
break;
}
} catch (IOException e) {
break;
}
}
return words.toArray(new String[words.size()]);
}
// inherited from thread.
@Override
public void run() {
while (!done) {
try {
printPrompt();
String message = getInputLine();
if (message == null) {
break;
} else {
if (trace) {
System.out.println("\n----------");
System.out.println("In : " + message);
}
message = message.trim();
if (!message.isEmpty()) {
putResponse(execute(message));
}
}
}
catch (IOException e) {
System.out.println("Exception: CommandInterpreter.run()");
break;
}
}
onExit();
}
// some history patterns used by getInputLine()
private static final Pattern historyPush = Pattern.compile("(.+):p");
private static final Pattern editPattern =
Pattern.compile("\\^(.+?)\\^(.*?)\\^?");
private static final Pattern bbPattern = Pattern.compile("(!!)");
/**
* Gets the input line. Deals with history. Currently we support simple csh-like history. !! - execute last command,
* !-3 execute 3 from last command, !2 execute second command in history list, !foo - find last command that started
* with foo and execute it. Also allows editing of the last command wich ^old^new^ type replacesments
*
* @return the next history line or null if done
*/
private String getInputLine() throws IOException {
String message = in.readLine();
if (message == null)
return null;
boolean justPush = false;
boolean echo = false;
boolean error = false;
Matcher m = historyPush.matcher(message);
if (m.matches()) {
justPush = true;
echo = true;
message = m.group(1);
}
if (message.startsWith("^")) { // line editing ^foo^fum^
m = editPattern.matcher(message);
if (m.matches()) {
String orig = m.group(1);
String sub = m.group(2);
try {
Pattern pat = Pattern.compile(orig);
Matcher subMatcher = pat.matcher(history.getLast(0));
if (subMatcher.find()) {
message = subMatcher.replaceFirst(sub);
echo = true;
} else {
error = true;
putResponse(message + ": substitution failed");
}
} catch (PatternSyntaxException pse) {
error = true;
putResponse("Bad regexp: " + pse.getDescription());
}
} else {
error = true;
putResponse("bad substitution sytax, use ^old^new^");
}
} else if ((m = bbPattern.matcher(message)).find()) {
message = m.replaceAll(history.getLast(0));
echo = true;
} else if (message.startsWith("!")) {
if (message.matches("!\\d+")) {
int which = Integer.parseInt(message.substring(1));
message = history.get(which);
} else if (message.matches("!-\\d+")) {
int which = Integer.parseInt(message.substring(2));
message = history.getLast(which - 1);
} else {
message = history.findLast(message.substring(1));
}
echo = true;
}
if (error) {
return "";
}
if (!message.isEmpty()) {
history.add(message);
}
if (echo) {
putResponse(message);
}
return justPush ? "" : message;
}
public void close() {
done = true;
}
/** Prints the prompt. */
private void printPrompt() {
if (prompt != null) {
out.print(prompt);
out.flush();
}
}
public boolean load(String filename) {
try {
FileReader fr = new FileReader(filename);
BufferedReader br = new BufferedReader(fr);
String inputLine;
while ((inputLine = br.readLine()) != null) {
String response = CommandInterpreter.this.execute(inputLine);
if (!response.equals("OK")) {
putResponse(response);
}
}
fr.close();
return true;
} catch (IOException ioe) {
return false;
}
}
/**
* Sets the prompt for the interpreter
*
* @param prompt the prompt.
*/
public void setPrompt(String prompt) {
this.prompt = prompt;
}
/**
* Gets the prompt for the interpreter
*
* @return the prompt.
*/
public String getPrompt() {
return prompt;
}
/**
* Returns the output stream of this CommandInterpreter.
*
* @return the output stream
*/
public PrintWriter getPrintWriter() {
return out;
}
public static void main(String[] args) {
CommandInterpreter ci = new CommandInterpreter();
try {
System.out.println("Welcome to the Command interpreter test program");
ci.setPrompt("CI> ");
ci.run();
System.out.println("Goodbye!");
} catch (Throwable t) {
System.out.println(t);
}
}
class CommandHistory {
private final List<String> history = new ArrayList<String>(100);
/**
* Adds a command to the history
*
* @param command the command to add
*/
public void add(String command) {
history.add(command);
}
/**
* Gets the most recent element in the history
*
* @param offset the offset from the most recent command
* @return the last command executed
*/
public String getLast(int offset) {
if (history.size() > offset) {
return history.get((history.size() - 1) - offset);
} else {
putResponse("command not found");
return "";
}
}
/**
* Gets the most recent element in the history
*
* @param which the offset from the most recent command
* @return the last command executed
*/
public String get(int which) {
if (history.size() > which) {
return history.get(which);
} else {
putResponse("command not found");
return "";
}
}
/**
* Finds the most recent message that starts with the given string
*
* @param match the string to match
* @return the last command executed that matches match
*/
public String findLast(String match) {
for (int i = history.size() - 1; i >= 0; i--) {
String cmd = get(i);
if (cmd.startsWith(match)) {
return cmd;
}
}
putResponse("command not found");
return "";
}
/** Dumps the current history */
public void dump() {
for (int i = 0; i < history.size(); i++) {
String cmd = get(i);
putResponse(i + " " + cmd);
}
}
}
}