/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*
* SimpleCLIPanel.java
* Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
*/
package weka.gui;
import weka.core.Capabilities;
import weka.core.CapabilitiesHandler;
import weka.core.ClassDiscovery;
import weka.core.OptionHandler;
import weka.core.Trie;
import weka.core.Utils;
import weka.gui.scripting.ScriptingPanel;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowEvent;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.HashSet;
import java.util.Properties;
import java.util.Vector;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTextPane;
/**
* Creates a very simple command line for invoking the main method of
* classes. System.out and System.err are redirected to an output area.
* Features a simple command history -- use up and down arrows to move
* through previous commmands. This gui uses only AWT (i.e. no Swing).
*
* @author Len Trigg (trigg@cs.waikato.ac.nz)
* @author FracPete (fracpete at waikato dot ac dot nz)
*/
public class SimpleCLIPanel
extends ScriptingPanel
implements ActionListener {
/** for serialization. */
private static final long serialVersionUID = 1089039734615114942L;
/** The filename of the properties file. */
protected static String FILENAME = "SimpleCLI.props";
/** The default location of the properties file. */
protected static String PROPERTY_FILE = "weka/gui/" + FILENAME;
/** Contains the SimpleCLI properties. */
protected static Properties PROPERTIES;
static {
// Allow a properties file in the current directory to override
try {
PROPERTIES = Utils.readProperties(PROPERTY_FILE);
java.util.Enumeration keys =
(java.util.Enumeration) PROPERTIES.propertyNames();
if (!keys.hasMoreElements()) {
throw new Exception(
"Failed to read a property file for the SimpleCLI");
}
}
catch (Exception ex) {
JOptionPane.showMessageDialog(
null,
"Could not read a configuration file for the SimpleCLI.\n"
+ "An example file is included with the Weka distribution.\n"
+ "This file should be named \"" + PROPERTY_FILE + "\" and\n"
+ "should be placed either in your user home (which is set\n"
+ "to \"" + System.getProperties().getProperty("user.home") + "\")\n"
+ "or the directory that java was started from\n",
"SimpleCLI",
JOptionPane.ERROR_MESSAGE);
}
}
/** The output area canvas added to the frame. */
protected JTextPane m_OutputArea;
/** The command input area. */
protected JTextField m_Input;
/** The history of commands entered interactively. */
protected Vector m_CommandHistory;
/** The current position in the command history. */
protected int m_HistoryPos;
/** The thread currently running a class main method. */
protected Thread m_RunThread;
/** The commandline completion. */
protected CommandlineCompletion m_Completion;
/**
* A class that handles running the main method of the class
* in a separate thread.
*
* @author Len Trigg (trigg@cs.waikato.ac.nz)
* @version $Revision: 7190 $
*/
class ClassRunner extends Thread {
/** Stores the main method to call. */
protected Method m_MainMethod;
/** Stores the command line arguments to pass to the main method. */
String[] m_CommandArgs;
/**
* Sets up the class runner thread.
*
* @param theClass the Class to call the main method of
* @param commandArgs an array of Strings to use as command line args
* @throws Exception if an error occurs
*/
public ClassRunner(Class theClass, String [] commandArgs)
throws Exception {
setDaemon(true);
Class[] argTemplate = {String[].class};
m_CommandArgs = commandArgs;
m_MainMethod = theClass.getMethod("main", argTemplate);
if (((m_MainMethod.getModifiers() & Modifier.STATIC) == 0)
|| (m_MainMethod.getModifiers() & Modifier.PUBLIC) == 0) {
throw new NoSuchMethodException("main(String[]) method of " +
theClass.getName() +
" is not public and static.");
}
}
/**
* Starts running the main method.
*/
public void run() {
PrintStream outOld = null;
PrintStream outNew = null;
String outFilename = null;
// is the output redirected?
if (m_CommandArgs.length > 2) {
String action = m_CommandArgs[m_CommandArgs.length - 2];
if (action.equals(">")) {
outOld = System.out;
try {
outFilename = m_CommandArgs[m_CommandArgs.length - 1];
// since file may not yet exist, command-line completion doesn't
// work, hence replace "~" manually with home directory
if (outFilename.startsWith("~"))
outFilename = outFilename.replaceFirst("~", System.getProperty("user.home"));
outNew = new PrintStream(new File(outFilename));
System.setOut(outNew);
m_CommandArgs[m_CommandArgs.length - 2] = "";
m_CommandArgs[m_CommandArgs.length - 1] = "";
// some main methods check the length of the "args" array
// -> removed the two empty elements at the end
String[] newArgs = new String[m_CommandArgs.length - 2];
System.arraycopy(m_CommandArgs, 0, newArgs, 0, m_CommandArgs.length - 2);
m_CommandArgs = newArgs;
}
catch (Exception e) {
System.setOut(outOld);
outOld = null;
}
}
}
try {
Object[] args = {m_CommandArgs};
m_MainMethod.invoke(null, args);
if (isInterrupted()) {
System.err.println("[...Interrupted]");
}
} catch (Exception ex) {
if (ex.getMessage() == null) {
System.err.println("[...Killed]");
} else {
System.err.println("[Run exception] " + ex.getMessage());
}
} finally {
m_RunThread = null;
}
// restore old System.out stream
if (outOld != null) {
outNew.flush();
outNew.close();
System.setOut(outOld);
System.out.println("Finished redirecting output to '" + outFilename + "'.");
}
}
}
/**
* A class for commandline completion of classnames.
*
* @author FracPete (fracpete at waikato dot ac dot nz)
* @version $Revision: 7190 $
*/
public static class CommandlineCompletion {
/** all the available packages. */
protected static Vector<String> m_Packages;
/** a trie for storing the packages. */
protected static Trie m_Trie;
/** debug mode on/off. */
protected boolean m_Debug = false;
/**
* default constructor.
*/
public CommandlineCompletion() {
super();
// build incremental list of packages
if (m_Packages == null) {
// get all packages
Vector list = ClassDiscovery.findPackages();
// create incremental list
HashSet<String> set = new HashSet<String>();
for (int i = 0; i < list.size(); i++) {
String[] parts = ((String) list.get(i)).split("\\.");
for (int n = 1; n < parts.length; n++) {
String pkg = "";
for (int m = 0; m <= n; m++) {
if (m > 0)
pkg += ".";
pkg += parts[m];
}
set.add(pkg);
}
}
// init packages
m_Packages = new Vector<String>();
m_Packages.addAll(set);
Collections.sort(m_Packages);
m_Trie = new Trie();
m_Trie.addAll(m_Packages);
}
}
/**
* returns whether debug mode is on.
*
* @return true if debug is on
*/
public boolean getDebug() {
return m_Debug;
}
/**
* sets debug mode on/off.
*
* @param value if true then debug mode is on
*/
public void setDebug(boolean value) {
m_Debug = value;
}
/**
* tests whether the given partial string is the name of a class with
* classpath - it basically tests, whether the string consists only
* of alphanumeric literals, underscores and dots.
*
* @param partial the string to test
* @return true if it looks like a classname
*/
public boolean isClassname(String partial) {
return (partial.replaceAll("[a-zA-Z0-9\\-\\.]*", "").length() == 0);
}
/**
* returns the packages part of the partial classname.
*
* @param partial the partial classname
* @return the package part of the partial classname
*/
public String getPackage(String partial) {
String result;
int i;
boolean wasDot;
char c;
result = "";
wasDot = false;
for (i = 0; i < partial.length(); i++) {
c = partial.charAt(i);
// start of classname?
if (wasDot && ((c >= 'A') && (c <= 'Z'))) {
break;
}
// package/class separator
else if (c == '.') {
wasDot = true;
result += "" + c;
}
// regular char
else {
wasDot = false;
result += "" + c;
}
}
// remove trailing "."
if (result.endsWith("."))
result = result.substring(0, result.length() - 1);
return result;
}
/**
* returns the classname part of the partial classname.
*
* @param partial the partial classname
* @return the class part of the classname
*/
public String getClassname(String partial) {
String result;
String pkg;
pkg = getPackage(partial);
if (pkg.length() + 1 < partial.length())
result = partial.substring(pkg.length() + 1);
else
result = "";
return result;
}
/**
* returns all the file/dir matches with the partial search string.
*
* @param partial the partial search string
* @return all the matches
*/
public Vector<String> getFileMatches(String partial) {
Vector<String> result;
File file;
File dir;
File[] files;
int i;
String prefix;
boolean caseSensitive;
String name;
boolean match;
result = new Vector<String>();
// is the OS case-sensitive?
caseSensitive = (File.separatorChar != '\\');
if (m_Debug)
System.out.println("case-sensitive=" + caseSensitive);
// is "~" used for home directory? -> replace with actual home directory
if (partial.startsWith("~"))
partial = System.getProperty("user.home") + partial.substring(1);
// determine dir and possible prefix
file = new File(partial);
dir = null;
prefix = null;
if (file.exists()) {
// determine dir to read
if (file.isDirectory()) {
dir = file;
prefix = null; // retrieve all
}
else {
dir = file.getParentFile();
prefix = file.getName();
}
}
else {
dir = file.getParentFile();
prefix = file.getName();
}
if (m_Debug)
System.out.println("search in dir=" + dir + ", prefix=" + prefix);
// list all files in dir
if (dir != null) {
files = dir.listFiles();
if (files != null) {
for (i = 0; i < files.length; i++) {
name = files[i].getName();
// does the name match?
if ((prefix != null) && caseSensitive)
match = name.startsWith(prefix);
else if ((prefix != null) && !caseSensitive)
match = name.toLowerCase().startsWith(prefix.toLowerCase());
else
match = true;
if (match) {
if (prefix != null) {
result.add(partial.substring(0, partial.length() - prefix.length()) + name);
}
else {
if (partial.endsWith("\\") || partial.endsWith("/"))
result.add(partial + name);
else
result.add(partial + File.separator + name);
}
}
}
}
else {
System.err.println("Invalid path: " + partial);
}
}
// sort the result
if (result.size() > 1)
Collections.sort(result);
// print results
if (m_Debug) {
System.out.println("file matches:");
for (i = 0; i < result.size(); i++)
System.out.println(result.get(i));
}
return result;
}
/**
* returns all the class/package matches with the partial search string.
*
* @param partial the partial search string
* @return all the matches
*/
public Vector<String> getClassMatches(String partial) {
String pkg;
String cls;
Vector<String> result;
Vector<String> list;
int i;
int index;
Trie tmpTrie;
HashSet set;
String tmpStr;
pkg = getPackage(partial);
cls = getClassname(partial);
if (getDebug())
System.out.println(
"\nsearch for: '" + partial + "' => package=" + pkg + ", class=" + cls);
result = new Vector<String>();
// find all packages that start with that string
if (cls.length() == 0) {
list = m_Trie.getWithPrefix(pkg);
set = new HashSet();
for (i = 0; i < list.size(); i++) {
tmpStr = list.get(i);
if (tmpStr.length() < partial.length())
continue;
if (tmpStr.equals(partial))
continue;
index = tmpStr.indexOf('.', partial.length() + 1);
if (index > -1)
set.add(tmpStr.substring(0, index));
else
set.add(tmpStr);
}
result.addAll(set);
if (result.size() > 1)
Collections.sort(result);
}
// find all classes that start with that string
list = ClassDiscovery.find(Object.class, pkg);
tmpTrie = new Trie();
tmpTrie.addAll(list);
list = tmpTrie.getWithPrefix(partial);
result.addAll(list);
// sort the result
if (result.size() > 1)
Collections.sort(result);
// print results
if (m_Debug) {
System.out.println("class/package matches:");
for (i = 0; i < result.size(); i++)
System.out.println(result.get(i));
}
return result;
}
/**
* returns all the matches with the partial search string, files or
* classes.
*
* @param partial the partial search string
* @return all the matches
*/
public Vector<String> getMatches(String partial) {
if (isClassname(partial))
return getClassMatches(partial);
else
return getFileMatches(partial);
}
/**
* returns the common prefix for all the items in the list.
*
* @param list the list to return the common prefix for
* @return the common prefix of all the items
*/
public String getCommonPrefix(Vector<String> list) {
String result;
Trie trie;
trie = new Trie();
trie.addAll(list);
result = trie.getCommonPrefix();
if (m_Debug)
System.out.println(list + "\n --> common prefix: '" + result + "'");
return result;
}
}
/**
* For initializing member variables.
*/
protected void initialize() {
super.initialize();
m_CommandHistory = new Vector();
m_HistoryPos = 0;
m_Completion = new CommandlineCompletion();
}
/**
* Sets up the GUI after initializing the members.
*/
protected void initGUI() {
super.initGUI();
setLayout(new BorderLayout());
m_OutputArea = new JTextPane();
m_OutputArea.setEditable(false);
m_OutputArea.setFont(new Font("Monospaced", Font.PLAIN, 12));
add(new JScrollPane(m_OutputArea), "Center");
m_Input = new JTextField();
m_Input.setFont(new Font("Monospaced", Font.PLAIN, 12));
m_Input.addActionListener(this);
m_Input.setFocusTraversalKeysEnabled(false);
m_Input.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
doHistory(e);
doCommandlineCompletion(e);
}
});
add(m_Input, "South");
}
/**
* Finishes up after initializing members and setting up the GUI.
*/
protected void initFinish() {
super.initFinish();
System.out.println(
"\nWelcome to the WEKA SimpleCLI\n\n"
+ "Enter commands in the textfield at the bottom of \n"
+ "the window. Use the up and down arrows to move \n"
+ "through previous commands.\n"
+ "Command completion for classnames and files is \n"
+ "initiated with <Tab>. In order to distinguish \n"
+ "between files and classnames, file names must \n"
+ "be either absolute or start with '." + File.separator + "' or '~/'\n"
+ "(the latter is a shortcut for the home directory).\n"
+ "<Alt+BackSpace> is used for deleting the text\n"
+ "in the commandline in chunks.\n");
try {
runCommand("help");
}
catch (Exception e) {
// ignored
}
loadHistory();
}
/**
* Returns an icon to be used in a frame.
*
* @return the icon
*/
public ImageIcon getIcon() {
return ComponentHelper.getImageIcon("weka_icon_new_48.png");
}
/**
* Returns the current title for the frame/dialog.
*
* @return the title
*/
public String getTitle() {
return "SimpleCLI";
}
/**
* Returns the text area that is used for displaying output on stdout
* and stderr.
*
* @return the JTextArea
*/
public JTextPane getOutput() {
return m_OutputArea;
}
/**
* Not supported.
*
* @return always null
*/
public JMenuBar getMenuBar() {
return null;
}
/**
* Executes a simple cli command.
*
* @param commands the command string
* @throws Exception if an error occurs
*/
public void runCommand(String commands) throws Exception {
System.out.println("> " + commands + '\n');
System.out.flush();
String [] commandArgs = Utils.splitOptions(commands);
if (commandArgs.length == 0) {
return;
}
if (commandArgs[0].equals("java")) {
// Execute the main method of a class
commandArgs[0] = "";
try {
if (commandArgs.length == 1) {
throw new Exception("No class name given");
}
String className = commandArgs[1];
commandArgs[1] = "";
if (m_RunThread != null) {
throw new Exception("An object is already running, use \"break\""
+ " to interrupt it.");
}
Class theClass = Class.forName(className);
// some classes expect a fixed order of the args, i.e., they don't
// use Utils.getOption(...) => create new array without first two
// empty strings (former "java" and "<classname>")
Vector argv = new Vector();
for (int i = 2; i < commandArgs.length; i++)
argv.add(commandArgs[i]);
m_RunThread = new ClassRunner(theClass, (String[]) argv.toArray(new String[argv.size()]));
m_RunThread.setPriority(Thread.MIN_PRIORITY); // UI has most priority
m_RunThread.start();
} catch (Exception ex) {
System.err.println(ex.getMessage());
}
} else if (commandArgs[0].equals("capabilities")) {
try {
Object obj = Class.forName(commandArgs[1]).newInstance();
if (obj instanceof CapabilitiesHandler) {
if (obj instanceof OptionHandler) {
Vector<String> args = new Vector<String>();
for (int i = 2; i < commandArgs.length; i++)
args.add(commandArgs[i]);
((OptionHandler) obj).setOptions(args.toArray(new String[args.size()]));
}
Capabilities caps = ((CapabilitiesHandler) obj).getCapabilities();
System.out.println(caps.toString().replace("[", "\n").replace("]", "\n"));
}
else {
System.out.println("'" + commandArgs[1] + "' is not a " + CapabilitiesHandler.class.getName() + "!");
}
}
catch (Exception e) {
System.err.println(e.getMessage());
}
} else if (commandArgs[0].equals("cls")) {
// Clear the text area
m_OutputArea.setText("");
} else if (commandArgs[0].equals("history")) {
System.out.println("Command history:");
for (int i = 0; i < m_CommandHistory.size(); i++)
System.out.println(m_CommandHistory.get(i));
System.out.println();
} else if (commandArgs[0].equals("break")) {
if (m_RunThread == null) {
System.err.println("Nothing is currently running.");
} else {
System.out.println("[Interrupt...]");
m_RunThread.interrupt();
}
} else if (commandArgs[0].equals("kill")) {
if (m_RunThread == null) {
System.err.println("Nothing is currently running.");
} else {
System.out.println("[Kill...]");
m_RunThread.stop();
m_RunThread = null;
}
} else if (commandArgs[0].equals("exit")) {
// Shut down
// determine parent
Container parent = getParent();
Container frame = null;
boolean finished = false;
while (!finished) {
if ( (parent instanceof JFrame)
|| (parent instanceof Frame)
|| (parent instanceof JInternalFrame) ) {
frame = parent;
finished = true;
}
if (!finished) {
parent = parent.getParent();
finished = (parent == null);
}
}
// fire the frame close event
if (frame != null) {
if (frame instanceof JInternalFrame)
((JInternalFrame) frame).doDefaultCloseAction();
else
((Window) frame).dispatchEvent(
new WindowEvent(
(Window) frame, WindowEvent.WINDOW_CLOSING));
}
} else {
boolean help = ((commandArgs.length > 1)
&& commandArgs[0].equals("help"));
if (help && commandArgs[1].equals("java")) {
System.out.println(
"java <classname> <args>\n\n"
+ "Starts the main method of <classname> with "
+ "the supplied command line arguments (if any).\n"
+ "The command is started in a separate thread, "
+ "and may be interrupted with the \"break\"\n"
+ "command (friendly), or killed with the \"kill\" "
+ "command (unfriendly).\n"
+ "Redirecting can be done with '>' followed by the "
+ "file to write to, e.g.:\n"
+ " java some.Class > ." + File.separator + "some.txt");
} else if (help && commandArgs[1].equals("break")) {
System.out.println(
"break\n\n"
+ "Attempts to nicely interrupt the running job, "
+ "if any. If this doesn't respond in an\n"
+ "acceptable time, use \"kill\".\n");
} else if (help && commandArgs[1].equals("kill")) {
System.out.println(
"kill\n\n"
+ "Kills the running job, if any. You should only "
+ "use this if the job doesn't respond to\n"
+ "\"break\".\n");
} else if (help && commandArgs[1].equals("capabilities")) {
System.out.println(
"capabilities <classname> <args>\n\n"
+ "Lists the capabilities of the specified class.\n"
+ "If the class is a " + OptionHandler.class.getName() + " then\n"
+ "trailing options after the classname will be\n"
+ "set as well.\n");
} else if (help && commandArgs[1].equals("cls")) {
System.out.println(
"cls\n\n"
+ "Clears the output area.\n");
} else if (help && commandArgs[1].equals("history")) {
System.out.println(
"history\n\n"
+ "Prints all issued commands.\n");
} else if (help && commandArgs[1].equals("exit")) {
System.out.println(
"exit\n\n"
+ "Exits the SimpleCLI program.\n");
} else {
// Print a help message
System.out.println(
"Command must be one of:\n"
+ "\tjava <classname> <args> [ > file]\n"
+ "\tbreak\n"
+ "\tkill\n"
+ "\tcapabilities <classname> <args>\n"
+ "\tcls\n"
+ "\thistory\n"
+ "\texit\n"
+ "\thelp <command>\n");
}
}
}
/**
* Changes the currently displayed command line when certain keys
* are pressed. The up arrow moves back through history entries
* and the down arrow moves forward through history entries.
*
* @param e a value of type 'KeyEvent'
*/
public void doHistory(KeyEvent e) {
if (e.getSource() == m_Input) {
switch (e.getKeyCode()) {
case KeyEvent.VK_UP:
if (m_HistoryPos > 0) {
m_HistoryPos--;
String command = (String) m_CommandHistory.elementAt(m_HistoryPos);
m_Input.setText(command);
}
break;
case KeyEvent.VK_DOWN:
if (m_HistoryPos < m_CommandHistory.size()) {
m_HistoryPos++;
String command = "";
if (m_HistoryPos < m_CommandHistory.size()) {
command = (String) m_CommandHistory.elementAt(m_HistoryPos);
}
m_Input.setText(command);
}
break;
default:
break;
}
}
}
/**
* performs commandline completion on packages and classnames.
*
* @param e a value of type 'KeyEvent'
*/
public void doCommandlineCompletion(KeyEvent e) {
if (e.getSource() == m_Input) {
switch (e.getKeyCode()) {
// completion
case KeyEvent.VK_TAB:
if (e.getModifiers() == 0) {
// it might take a while before we determined all of the possible
// matches (Java doesn't have an application wide cursor handling??)
m_Input.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
m_OutputArea.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try {
String txt = m_Input.getText();
// java call?
if (txt.trim().startsWith("java ")) {
int pos = m_Input.getCaretPosition();
int nonNameCharPos = -1;
// find first character not part of a name, back from current position
// i.e., " or blank
for (int i = pos - 1; i >= 0; i--) {
if ( (txt.charAt(i) == '"')
|| (txt.charAt(i) == ' ') ) {
nonNameCharPos = i;
break;
}
}
if (nonNameCharPos > -1) {
String search = txt.substring(nonNameCharPos + 1, pos);
// find matches and common prefix
Vector<String> list = m_Completion.getMatches(search);
String common = m_Completion.getCommonPrefix(list);
// just extending by separator is not a real extension
if ((search.toLowerCase() + File.separator).equals(common.toLowerCase()))
common = search;
// can we complete the string?
if (common.length() > search.length()) {
try {
m_Input.getDocument().remove(nonNameCharPos + 1, search.length());
m_Input.getDocument().insertString(nonNameCharPos + 1, common, null);
}
catch (Exception ex) {
ex.printStackTrace();
}
}
// ambigiuous? -> print matches
else if (list.size() > 1) {
System.out.println("\nPossible matches:");
for (int i = 0; i < list.size(); i++)
System.out.println(" " + list.get(i));
}
else {
// nothing found, don't do anything
}
}
}
}
finally {
// set cursor back to default
m_Input.setCursor(null);
m_OutputArea.setCursor(null);
}
}
break;
// delete last part up to next blank or dot
case KeyEvent.VK_BACK_SPACE:
if (e.getModifiers() == KeyEvent.ALT_MASK) {
String txt = m_Input.getText();
int pos = m_Input.getCaretPosition();
// skip whitespaces
int start = pos;
start--;
while (start >= 0) {
if ( (txt.charAt(start) == '.')
|| (txt.charAt(start) == ' ')
|| (txt.charAt(start) == '\\')
|| (txt.charAt(start) == '/') )
start--;
else
break;
}
// find first blank or dot back from position
int newPos = -1;
for (int i = start; i >= 0; i--) {
if ( (txt.charAt(i) == '.')
|| (txt.charAt(i) == ' ')
|| (txt.charAt(i) == '\\')
|| (txt.charAt(i) == '/') ) {
newPos = i;
break;
}
}
// remove string
try {
m_Input.getDocument().remove(newPos + 1, pos - newPos - 1);
}
catch (Exception ex) {
ex.printStackTrace();
}
}
break;
}
}
}
/**
* Only gets called when return is pressed in the input area, which
* starts the command running.
*
* @param e a value of type 'ActionEvent'
*/
public void actionPerformed(ActionEvent e) {
try {
if (e.getSource() == m_Input) {
String command = m_Input.getText();
int last = m_CommandHistory.size() - 1;
if ((last < 0)
|| !command.equals((String)m_CommandHistory.elementAt(last))) {
m_CommandHistory.addElement(command);
saveHistory();
}
m_HistoryPos = m_CommandHistory.size();
runCommand(command);
m_Input.setText("");
}
} catch (Exception ex) {
System.err.println(ex.getMessage());
}
}
/**
* loads the command history from the user's properties file.
*/
protected void loadHistory() {
int size;
int i;
String cmd;
size = Integer.parseInt(PROPERTIES.getProperty("HistorySize", "50"));
m_CommandHistory.clear();
for (i = 0; i < size; i++) {
cmd = PROPERTIES.getProperty("Command" + i, "");
if (cmd.length() != 0)
m_CommandHistory.add(cmd);
else
break;
}
m_HistoryPos = m_CommandHistory.size();
}
/**
* saves the current command history in the user's home directory.
*/
protected void saveHistory() {
int size;
int from;
int i;
String filename;
BufferedOutputStream stream;
size = Integer.parseInt(PROPERTIES.getProperty("HistorySize", "50"));
// determine first command to save
from = m_CommandHistory.size() - size;
if (from < 0)
from = 0;
// fill properties
PROPERTIES.setProperty("HistorySize", "" + size);
for (i = from; i < m_CommandHistory.size(); i++)
PROPERTIES.setProperty("Command" + (i-from), (String) m_CommandHistory.get(i));
try {
filename = System.getProperties().getProperty("user.home") + File.separatorChar + FILENAME;
stream = new BufferedOutputStream(new FileOutputStream(filename));
PROPERTIES.store(stream, "SimpleCLI");
stream.close();
}
catch (Exception e) {
e.printStackTrace();
}
}
/**
* Displays the panel in a frame.
*
* @param args ignored
*/
public static void main(String[] args) {
showPanel(new SimpleCLIPanel(), args);
}
}