/* * 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. */ /* * SimpleCLI.java * Copyright (C) 1999 Len Trigg * */ package weka.gui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Frame; import java.awt.Font; import java.awt.TextArea; import java.awt.TextField; import java.awt.event.KeyListener; import java.awt.event.ActionListener; import java.awt.event.WindowListener; import java.awt.event.KeyEvent; import java.awt.event.ActionEvent; import java.awt.event.WindowEvent; import java.awt.event.KeyAdapter; import java.awt.event.WindowAdapter; import java.io.PipedOutputStream; import java.io.LineNumberReader; import java.io.Reader; import java.io.PipedInputStream; import java.io.PrintStream; import java.io.InputStreamReader; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.StringTokenizer; import java.util.Vector; /** * 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) * @version $Revision: 1.1.1.1 $ */ public class SimpleCLI extends Frame implements ActionListener { /** The output area canvas added to the frame */ protected TextArea m_OutputArea = new TextArea(); /** The command input area */ protected TextField m_Input = new TextField(); /** The history of commands entered interactively */ protected Vector m_CommandHistory = new Vector(); /** The current position in the command history */ protected int m_HistoryPos = 0; /** The new output stream for System.out */ protected PipedOutputStream m_POO = new PipedOutputStream(); /** The new output stream for System.err */ protected PipedOutputStream m_POE = new PipedOutputStream(); /** The thread that sends output from m_POO to the output box */ protected Thread m_OutRedirector; /** The thread that sends output from m_POE to the output box */ protected Thread m_ErrRedirector; /** The thread currently running a class main method */ protected Thread m_RunThread; /* * A class that sends all lines from a reader to a TextArea component */ class ReaderToTextArea extends Thread { /** The reader being monitored */ protected LineNumberReader m_Input; /** The output text component */ protected TextArea m_Output; /** * Sets up the ReaderToTextArea * * @param input the Reader to monitor * @param output the TextArea to send output to */ public ReaderToTextArea(Reader input, TextArea output) { setDaemon(true); m_Input = new LineNumberReader(input); m_Output = output; } /** * Sit here listening for lines of input and appending them straight * to the text component. */ public void run() { while (true) { try { m_Output.append(m_Input.readLine() + '\n'); } catch (Exception ex) { try { sleep(100); } catch (Exception e) { } } } } } /* * A class that handles running the main method of the class * in a separate thread */ 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 * @exception 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() { 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; } } } /** * Constructor * * @exception Exception if an error occurs */ public SimpleCLI() throws Exception { setLayout(new BorderLayout()); add(m_OutputArea, "Center"); add(m_Input, "South"); m_Input.addActionListener(this); m_Input.addKeyListener(new KeyAdapter() { public void keyPressed(KeyEvent e) { doHistory(e); } }); m_OutputArea.setEditable(false); m_OutputArea.setFont(new Font("Monospaced", Font.PLAIN, 12)); // Redirect System.out to the text area // System.out.println("Redirecting System.out"); PipedInputStream pio = new PipedInputStream(m_POO); System.setOut(new PrintStream(m_POO)); Reader r = new InputStreamReader(pio); m_OutRedirector = new ReaderToTextArea(r, m_OutputArea); m_OutRedirector.start(); // Redirect System.err to the text area // System.err.println("Redirecting System.err"); PipedInputStream pie = new PipedInputStream(m_POE); System.setErr(new PrintStream(m_POE)); r = new InputStreamReader(pie); m_ErrRedirector = new ReaderToTextArea(r, m_OutputArea); m_ErrRedirector.start(); // Finish setting up the frame and show it pack(); setSize(600,500); 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\n"); runCommand("help"); } /** * Executes a simple cli command. * * @param commands the command string * @exception Exception if an error occurs */ public void runCommand(String commands) throws Exception { System.out.println("> " + commands + '\n'); System.out.flush(); String [] commandArgs = 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); m_RunThread = new ClassRunner(theClass, commandArgs); 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("cls")) { // Clear the text area m_OutputArea.setText(""); } 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 // fire the frame close event processEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING)); } else { boolean help = ((commandArgs.length > 1) && commandArgs[0].equals("help")); if (help && commandArgs[1].equals("java")) { System.err.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"); } else if (help && commandArgs[1].equals("break")) { System.err.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.err.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("cls")) { System.err.println("cls\n\n" + "Clears the output area.\n"); } else if (help && commandArgs[1].equals("exit")) { System.err.println("exit\n\n" + "Exits the SimpleCLI program.\n"); } else { // Print a help message System.err.println("Command must be one of:\n" + "\tjava <classname> <args>\n" + "\tbreak\n" + "\tkill\n" + "\tcls\n" + "\texit\n" + "\thelp <command>\n"); } } } /** * Split up a string containing options into an array of strings, * one for each option. * * @param optionString the string containing the options * @return the array of options */ protected static String [] splitOptions(String optionString) { Vector optionsVec = new Vector(); StringTokenizer st = new StringTokenizer(optionString); while (st.hasMoreTokens()) optionsVec.addElement(st.nextToken()); String [] options = new String[optionsVec.size()]; for (int i = 0; i < optionsVec.size(); i++) { options[i] = (String)optionsVec.elementAt(i); } return options; } /** * 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; } } } /** * 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); m_HistoryPos = m_CommandHistory.size(); } runCommand(command); m_Input.setText(""); } } catch (Exception ex) { System.err.println(ex.getMessage()); } } /** * Method to start up the simple cli * * @param args array of command line arguments. Not used. */ public static void main(String[] args) { try { final SimpleCLI frame = new SimpleCLI(); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent param1) { System.err.println("window closed"); frame.dispose(); } }); frame.setVisible(true); } catch (Exception e) { System.out.println(e.getMessage()); System.exit(0); } } }