source: src/main/java/weka/gui/SimpleCLIPanel.java @ 19

Last change on this file since 19 was 4, checked in by gnappo, 14 years ago

Import di weka.

File size: 29.8 KB
RevLine 
[4]1/*
2 *    This program is free software; you can redistribute it and/or modify
3 *    it under the terms of the GNU General Public License as published by
4 *    the Free Software Foundation; either version 2 of the License, or
5 *    (at your option) any later version.
6 *
7 *    This program is distributed in the hope that it will be useful,
8 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
9 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10 *    GNU General Public License for more details.
11 *
12 *    You should have received a copy of the GNU General Public License
13 *    along with this program; if not, write to the Free Software
14 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
15 */
16
17/*
18 * SimpleCLIPanel.java
19 * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
20 */
21
22package weka.gui;
23
24import weka.core.Capabilities;
25import weka.core.CapabilitiesHandler;
26import weka.core.ClassDiscovery;
27import weka.core.OptionHandler;
28import weka.core.Trie;
29import weka.core.Utils;
30import weka.gui.scripting.ScriptingPanel;
31
32import java.awt.BorderLayout;
33import java.awt.Container;
34import java.awt.Cursor;
35import java.awt.Font;
36import java.awt.Frame;
37import java.awt.Window;
38import java.awt.event.ActionEvent;
39import java.awt.event.ActionListener;
40import java.awt.event.KeyAdapter;
41import java.awt.event.KeyEvent;
42import java.awt.event.WindowEvent;
43import java.io.BufferedOutputStream;
44import java.io.File;
45import java.io.FileOutputStream;
46import java.io.PrintStream;
47import java.lang.reflect.Method;
48import java.lang.reflect.Modifier;
49import java.util.Collections;
50import java.util.HashSet;
51import java.util.Properties;
52import java.util.Vector;
53
54import javax.swing.ImageIcon;
55import javax.swing.JFrame;
56import javax.swing.JInternalFrame;
57import javax.swing.JMenuBar;
58import javax.swing.JOptionPane;
59import javax.swing.JScrollPane;
60import javax.swing.JTextField;
61import javax.swing.JTextPane;
62
63/**
64 * Creates a very simple command line for invoking the main method of
65 * classes. System.out and System.err are redirected to an output area.
66 * Features a simple command history -- use up and down arrows to move
67 * through previous commmands. This gui uses only AWT (i.e. no Swing).
68 *
69 * @author Len Trigg (trigg@cs.waikato.ac.nz)
70 * @author FracPete (fracpete at waikato dot ac dot nz)
71 */
72public class SimpleCLIPanel
73  extends ScriptingPanel
74  implements ActionListener {
75 
76  /** for serialization. */
77  private static final long serialVersionUID = 1089039734615114942L;
78
79  /** The filename of the properties file. */
80  protected static String FILENAME = "SimpleCLI.props";
81 
82  /** The default location of the properties file. */
83  protected static String PROPERTY_FILE = "weka/gui/" + FILENAME;
84 
85  /** Contains the SimpleCLI properties. */
86  protected static Properties PROPERTIES;
87
88  static {
89    // Allow a properties file in the current directory to override
90    try {
91      PROPERTIES = Utils.readProperties(PROPERTY_FILE);
92      java.util.Enumeration keys = 
93        (java.util.Enumeration) PROPERTIES.propertyNames();
94      if (!keys.hasMoreElements()) {
95        throw new Exception(
96            "Failed to read a property file for the SimpleCLI");
97      }
98    }
99    catch (Exception ex) {
100      JOptionPane.showMessageDialog(
101          null,
102          "Could not read a configuration file for the SimpleCLI.\n"
103          + "An example file is included with the Weka distribution.\n"
104          + "This file should be named \"" + PROPERTY_FILE + "\" and\n"
105          + "should be placed either in your user home (which is set\n"
106          + "to \"" + System.getProperties().getProperty("user.home") + "\")\n"
107          + "or the directory that java was started from\n",
108          "SimpleCLI",
109          JOptionPane.ERROR_MESSAGE);
110    }
111  }
112 
113  /** The output area canvas added to the frame. */
114  protected JTextPane m_OutputArea;
115
116  /** The command input area. */
117  protected JTextField m_Input;
118
119  /** The history of commands entered interactively. */
120  protected Vector m_CommandHistory;
121
122  /** The current position in the command history. */
123  protected int m_HistoryPos;
124
125  /** The thread currently running a class main method. */
126  protected Thread m_RunThread;
127
128  /** The commandline completion. */
129  protected CommandlineCompletion m_Completion;
130
131  /**
132   * A class that handles running the main method of the class
133   * in a separate thread.
134   *
135   * @author Len Trigg (trigg@cs.waikato.ac.nz)
136   * @version $Revision: 5855 $
137   */
138  class ClassRunner extends Thread {
139
140    /** Stores the main method to call. */
141    protected Method m_MainMethod;
142
143    /** Stores the command line arguments to pass to the main method. */
144    String[] m_CommandArgs;
145   
146    /**
147     * Sets up the class runner thread.
148     *
149     * @param theClass the Class to call the main method of
150     * @param commandArgs an array of Strings to use as command line args
151     * @throws Exception if an error occurs
152     */
153    public ClassRunner(Class theClass, String [] commandArgs)
154      throws Exception {
155     
156      setDaemon(true);
157      Class[] argTemplate = {String[].class};
158      m_CommandArgs = commandArgs;
159      m_MainMethod = theClass.getMethod("main", argTemplate);
160      if (((m_MainMethod.getModifiers() & Modifier.STATIC) == 0)
161          || (m_MainMethod.getModifiers() & Modifier.PUBLIC) == 0) {
162        throw new NoSuchMethodException("main(String[]) method of " +
163                                        theClass.getName() +
164                                        " is not public and static.");
165      }
166    }
167
168    /**
169     * Starts running the main method.
170     */
171    public void run() {
172      PrintStream outOld = null;
173      PrintStream outNew = null;
174      String outFilename = null;
175     
176      // is the output redirected?
177      if (m_CommandArgs.length > 2) {
178        String action = m_CommandArgs[m_CommandArgs.length - 2];
179        if (action.equals(">")) {
180          outOld = System.out;
181          try {
182            outFilename = m_CommandArgs[m_CommandArgs.length - 1];
183            // since file may not yet exist, command-line completion doesn't
184            // work, hence replace "~" manually with home directory
185            if (outFilename.startsWith("~"))
186              outFilename = outFilename.replaceFirst("~", System.getProperty("user.home"));
187            outNew = new PrintStream(new File(outFilename));
188            System.setOut(outNew);
189            m_CommandArgs[m_CommandArgs.length - 2] = "";
190            m_CommandArgs[m_CommandArgs.length - 1] = "";
191            // some main methods check the length of the "args" array
192            // -> removed the two empty elements at the end
193            String[] newArgs = new String[m_CommandArgs.length - 2];
194            System.arraycopy(m_CommandArgs, 0, newArgs, 0, m_CommandArgs.length - 2);
195            m_CommandArgs = newArgs;
196          }
197          catch (Exception e) {
198            System.setOut(outOld);
199            outOld = null;
200          }
201        }
202      }
203     
204      try {
205        Object[] args = {m_CommandArgs};
206        m_MainMethod.invoke(null, args);
207        if (isInterrupted()) {
208          System.err.println("[...Interrupted]");
209        }
210      } catch (Exception ex) {
211        if (ex.getMessage() == null) {
212          System.err.println("[...Killed]");
213        } else {
214          System.err.println("[Run exception] " + ex.getMessage());
215        }
216      } finally {
217        m_RunThread = null;
218      }
219     
220      // restore old System.out stream
221      if (outOld != null) {
222        outNew.flush();
223        outNew.close();
224        System.setOut(outOld);
225        System.out.println("Finished redirecting output to '" + outFilename + "'.");
226      }
227    }
228  }
229
230  /**
231   * A class for commandline completion of classnames.
232   *
233   * @author  FracPete (fracpete at waikato dot ac dot nz)
234   * @version $Revision: 5855 $
235   */
236  public static class CommandlineCompletion {
237   
238    /** all the available packages. */
239    protected static Vector<String> m_Packages;
240
241    /** a trie for storing the packages. */
242    protected static Trie m_Trie;
243   
244    /** debug mode on/off. */
245    protected boolean m_Debug = false;
246   
247    /**
248     * default constructor.
249     */
250    public CommandlineCompletion() {
251      super();
252     
253      // build incremental list of packages
254      if (m_Packages == null) {
255        // get all packages
256        Vector list = ClassDiscovery.findPackages();
257       
258        // create incremental list
259        HashSet<String> set = new HashSet<String>();
260        for (int i = 0; i < list.size(); i++) {
261          String[] parts = ((String) list.get(i)).split("\\.");
262          for (int n = 1; n < parts.length; n++) {
263            String pkg = "";
264            for (int m = 0; m <= n; m++) {
265              if (m > 0)
266                pkg += ".";
267              pkg += parts[m];
268            }
269            set.add(pkg);
270          }
271        }
272       
273        // init packages
274        m_Packages = new Vector<String>();
275        m_Packages.addAll(set);
276        Collections.sort(m_Packages);
277       
278        m_Trie = new Trie();
279        m_Trie.addAll(m_Packages);
280      }
281    }
282   
283    /**
284     * returns whether debug mode is on.
285     *
286     * @return          true if debug is on
287     */
288    public boolean getDebug() {
289      return m_Debug;
290    }
291   
292    /**
293     * sets debug mode on/off.
294     *
295     * @param value     if true then debug mode is on
296     */
297    public void setDebug(boolean value) {
298      m_Debug = value;
299    }
300   
301    /**
302     * tests whether the given partial string is the name of a class with
303     * classpath - it basically tests, whether the string consists only
304     * of alphanumeric literals, underscores and dots.
305     *
306     * @param partial   the string to test
307     * @return          true if it looks like a classname
308     */
309    public boolean isClassname(String partial) {
310      return (partial.replaceAll("[a-zA-Z0-9\\-\\.]*", "").length() == 0);
311    }
312   
313    /**
314     * returns the packages part of the partial classname.
315     *
316     * @param partial   the partial classname
317     * @return          the package part of the partial classname
318     */
319    public String getPackage(String partial) {
320      String    result;
321      int       i;
322      boolean   wasDot;
323      char      c;
324     
325      result = "";
326      wasDot = false;
327      for (i = 0; i < partial.length(); i++) {
328        c = partial.charAt(i);
329       
330        // start of classname?
331        if (wasDot && ((c >= 'A') && (c <= 'Z'))) {
332          break;
333        }
334        // package/class separator
335        else if (c == '.') {
336          wasDot = true;
337          result += "" + c;
338        }
339        // regular char
340        else {
341          wasDot = false;
342          result += "" + c;
343        }
344      }
345
346      // remove trailing "."
347      if (result.endsWith("."))
348        result = result.substring(0, result.length() - 1);
349     
350      return result;
351    }
352   
353    /**
354     * returns the classname part of the partial classname.
355     *
356     * @param partial   the partial classname
357     * @return          the class part of the classname
358     */
359    public String getClassname(String partial) {
360      String    result;
361      String    pkg;
362     
363      pkg = getPackage(partial);
364      if (pkg.length() + 1 < partial.length())
365        result = partial.substring(pkg.length() + 1);
366      else
367        result = "";
368     
369      return result;
370    }
371   
372    /**
373     * returns all the file/dir matches with the partial search string.
374     *
375     * @param partial   the partial search string
376     * @return          all the matches
377     */
378    public Vector<String> getFileMatches(String partial) {
379      Vector<String>    result;
380      File              file;
381      File              dir;
382      File[]            files;
383      int               i;
384      String            prefix;
385      boolean           caseSensitive;
386      String            name;
387      boolean           match;
388     
389      result = new Vector<String>();
390
391      // is the OS case-sensitive?
392      caseSensitive = (File.separatorChar != '\\');
393      if (m_Debug)
394        System.out.println("case-sensitive=" + caseSensitive);
395     
396      // is "~" used for home directory? -> replace with actual home directory
397      if (partial.startsWith("~"))
398        partial = System.getProperty("user.home") + partial.substring(1);
399     
400      // determine dir and possible prefix
401      file   = new File(partial);
402      dir    = null;
403      prefix = null;
404      if (file.exists()) {
405        // determine dir to read
406        if (file.isDirectory()) {
407          dir    = file;
408          prefix = null;  // retrieve all
409        }
410        else {
411          dir    = file.getParentFile();
412          prefix = file.getName();
413        }
414      }
415      else {
416        dir    = file.getParentFile();
417        prefix = file.getName();
418      }
419
420      if (m_Debug)
421        System.out.println("search in dir=" + dir + ", prefix=" + prefix);
422     
423      // list all files in dir
424      if (dir != null) {
425        files = dir.listFiles();
426        if (files != null) {
427          for (i = 0; i < files.length; i++) {
428            name = files[i].getName();
429
430            // does the name match?
431            if ((prefix != null) && caseSensitive)
432              match = name.startsWith(prefix);
433            else if ((prefix != null) && !caseSensitive)
434              match = name.toLowerCase().startsWith(prefix.toLowerCase());
435            else
436              match = true;
437
438            if (match) {
439              if (prefix != null) {
440                result.add(partial.substring(0, partial.length() - prefix.length()) + name);
441              }
442              else {
443                if (partial.endsWith("\\") || partial.endsWith("/"))
444                  result.add(partial + name);
445                else
446                  result.add(partial + File.separator + name);
447              }
448            }
449          }
450        }
451        else {
452          System.err.println("Invalid path: " + partial);
453        }
454      }
455     
456      // sort the result
457      if (result.size() > 1)
458        Collections.sort(result);
459     
460      // print results
461      if (m_Debug) {
462        System.out.println("file matches:");
463        for (i = 0; i < result.size(); i++)
464          System.out.println(result.get(i));
465      }
466     
467      return result;
468    }
469   
470    /**
471     * returns all the class/package matches with the partial search string.
472     *
473     * @param partial   the partial search string
474     * @return          all the matches
475     */
476    public Vector<String> getClassMatches(String partial) {
477      String            pkg;
478      String            cls;
479      Vector<String>    result;
480      Vector<String>    list;
481      int               i;
482      int               index;
483      Trie              tmpTrie;
484      HashSet           set;
485      String            tmpStr;
486     
487      pkg = getPackage(partial);
488      cls = getClassname(partial);
489     
490      if (getDebug())
491        System.out.println(
492            "\nsearch for: '" + partial + "' => package=" + pkg + ", class=" + cls);
493
494      result = new Vector<String>();
495
496      // find all packages that start with that string
497      if (cls.length() == 0) {
498        list = m_Trie.getWithPrefix(pkg);
499        set  = new HashSet();
500        for (i = 0; i < list.size(); i++) {
501          tmpStr = list.get(i);
502          if (tmpStr.length() < partial.length())
503            continue;
504          if (tmpStr.equals(partial))
505            continue;
506         
507          index  = tmpStr.indexOf('.', partial.length() + 1);
508          if (index > -1)
509            set.add(tmpStr.substring(0, index));
510          else
511            set.add(tmpStr);
512        }
513
514        result.addAll(set);
515        if (result.size() > 1)
516          Collections.sort(result);
517      }
518
519      // find all classes that start with that string
520      list = ClassDiscovery.find(Object.class, pkg);
521      tmpTrie = new Trie();
522      tmpTrie.addAll(list);
523      list = tmpTrie.getWithPrefix(partial);
524      result.addAll(list);
525     
526      // sort the result
527      if (result.size() > 1)
528        Collections.sort(result);
529
530      // print results
531      if (m_Debug) {
532        System.out.println("class/package matches:");
533        for (i = 0; i < result.size(); i++)
534          System.out.println(result.get(i));
535      }
536     
537      return result;
538    }
539   
540    /**
541     * returns all the matches with the partial search string, files or
542     * classes.
543     *
544     * @param partial   the partial search string
545     * @return          all the matches
546     */
547    public Vector<String> getMatches(String partial) {
548      if (isClassname(partial))
549        return getClassMatches(partial);
550      else
551        return getFileMatches(partial);
552    }
553   
554    /**
555     * returns the common prefix for all the items in the list.
556     *
557     * @param list      the list to return the common prefix for
558     * @return          the common prefix of all the items
559     */
560    public String getCommonPrefix(Vector<String> list) {
561      String    result;
562      Trie      trie;
563     
564      trie = new Trie();
565      trie.addAll(list);
566      result = trie.getCommonPrefix();
567     
568      if (m_Debug)
569        System.out.println(list + "\n  --> common prefix: '" + result + "'");
570     
571      return result;
572    }
573  }
574 
575  /**
576   * For initializing member variables.
577   */
578  protected void initialize() {
579    super.initialize();
580
581    m_CommandHistory = new Vector();
582    m_HistoryPos     = 0;
583    m_Completion     = new CommandlineCompletion();
584  }
585 
586  /**
587   * Sets up the GUI after initializing the members.
588   */
589  protected void initGUI() {
590    super.initGUI();
591
592    setLayout(new BorderLayout());
593
594    m_OutputArea = new JTextPane();
595    m_OutputArea.setEditable(false);
596    m_OutputArea.setFont(new Font("Monospaced", Font.PLAIN, 12));
597    add(new JScrollPane(m_OutputArea), "Center");
598
599    m_Input = new JTextField();
600    m_Input.setFont(new Font("Monospaced", Font.PLAIN, 12));
601    m_Input.addActionListener(this);
602    m_Input.setFocusTraversalKeysEnabled(false);
603    m_Input.addKeyListener(new KeyAdapter() {
604      public void keyPressed(KeyEvent e) {
605        doHistory(e);
606        doCommandlineCompletion(e);
607      }
608    });
609    add(m_Input, "South");
610  }
611 
612  /**
613   * Finishes up after initializing members and setting up the GUI.
614   */
615  protected void initFinish() {
616    super.initFinish();
617
618    System.out.println(
619          "\nWelcome to the WEKA SimpleCLI\n\n"
620        + "Enter commands in the textfield at the bottom of \n"
621        + "the window. Use the up and down arrows to move \n"
622        + "through previous commands.\n"
623        + "Command completion for classnames and files is \n"
624        + "initiated with <Tab>. In order to distinguish \n"
625        + "between files and classnames, file names must \n"
626        + "be either absolute or start with '." + File.separator + "' or '~/'\n"
627        + "(the latter is a shortcut for the home directory).\n"
628        + "<Alt+BackSpace> is used for deleting the text\n"
629        + "in the commandline in chunks.\n");
630    try {
631      runCommand("help");
632    }
633    catch (Exception e) {
634      // ignored
635    }
636   
637    loadHistory();
638  }
639
640  /**
641   * Returns an icon to be used in a frame.
642   *
643   * @return            the icon
644   */
645  public ImageIcon getIcon() {
646    return ComponentHelper.getImageIcon("weka_icon.gif");
647  }
648
649  /**
650   * Returns the current title for the frame/dialog.
651   *
652   * @return            the title
653   */
654  public String getTitle() {
655    return "SimpleCLI";
656  }
657
658  /**
659   * Returns the text area that is used for displaying output on stdout
660   * and stderr.
661   *
662   * @return            the JTextArea
663   */
664  public JTextPane getOutput() {
665    return m_OutputArea;
666  }
667
668  /**
669   * Not supported.
670   *
671   * @return            always null
672   */
673  public JMenuBar getMenuBar() {
674    return null;
675  }
676
677  /**
678   * Executes a simple cli command.
679   *
680   * @param commands the command string
681   * @throws Exception if an error occurs
682   */
683  public void runCommand(String commands) throws Exception {
684
685    System.out.println("> " + commands + '\n');
686    System.out.flush();
687    String [] commandArgs = Utils.splitOptions(commands);
688    if (commandArgs.length == 0) {
689      return;
690    }
691    if (commandArgs[0].equals("java")) {
692      // Execute the main method of a class
693      commandArgs[0] = "";
694      try {
695        if (commandArgs.length == 1) {
696          throw new Exception("No class name given");
697        }
698        String className = commandArgs[1];
699        commandArgs[1] = "";
700        if (m_RunThread != null) {
701          throw new Exception("An object is already running, use \"break\""
702                              + " to interrupt it.");
703        }
704        Class theClass = Class.forName(className);
705
706        // some classes expect a fixed order of the args, i.e., they don't
707        // use Utils.getOption(...) => create new array without first two
708        // empty strings (former "java" and "<classname>")
709        Vector argv = new Vector();
710        for (int i = 2; i < commandArgs.length; i++)
711          argv.add(commandArgs[i]);
712 
713        m_RunThread = new ClassRunner(theClass, (String[]) argv.toArray(new String[argv.size()]));
714        m_RunThread.setPriority(Thread.MIN_PRIORITY); // UI has most priority
715        m_RunThread.start();   
716      } catch (Exception ex) {
717        System.err.println(ex.getMessage());
718      }
719
720    } else if (commandArgs[0].equals("capabilities")) {
721      try {
722        Object obj = Class.forName(commandArgs[1]).newInstance();
723        if (obj instanceof CapabilitiesHandler) {
724          if (obj instanceof OptionHandler) {
725            Vector<String> args = new Vector<String>();
726            for (int i = 2; i < commandArgs.length; i++)
727              args.add(commandArgs[i]);
728            ((OptionHandler) obj).setOptions(args.toArray(new String[args.size()]));
729          }
730          Capabilities caps = ((CapabilitiesHandler) obj).getCapabilities();
731          System.out.println(caps.toString().replace("[", "\n").replace("]", "\n"));
732        }
733        else {
734          System.out.println("'" + commandArgs[1] + "' is not a " + CapabilitiesHandler.class.getName() + "!");
735        }
736      }
737      catch (Exception e) {
738        System.err.println(e.getMessage());
739      }
740    } else if (commandArgs[0].equals("cls")) {
741      // Clear the text area
742      m_OutputArea.setText("");
743    } else if (commandArgs[0].equals("history")) {
744      System.out.println("Command history:");
745      for (int i = 0; i < m_CommandHistory.size(); i++)
746        System.out.println(m_CommandHistory.get(i));
747      System.out.println();
748    } else if (commandArgs[0].equals("break")) {
749      if (m_RunThread == null) {
750        System.err.println("Nothing is currently running.");
751      } else {
752        System.out.println("[Interrupt...]");
753        m_RunThread.interrupt();
754      }
755    } else if (commandArgs[0].equals("kill")) {
756      if (m_RunThread == null) {
757        System.err.println("Nothing is currently running.");
758      } else {
759        System.out.println("[Kill...]");
760        m_RunThread.stop();
761        m_RunThread = null;
762      }
763    } else if (commandArgs[0].equals("exit")) {
764      // Shut down
765      // determine parent
766      Container parent = getParent();
767      Container frame = null;
768      boolean finished = false;
769      while (!finished) {
770        if (    (parent instanceof JFrame) 
771             || (parent instanceof Frame) 
772             || (parent instanceof JInternalFrame) ) {
773          frame = parent;
774          finished = true;
775        }
776       
777        if (!finished) {
778          parent = parent.getParent();
779          finished = (parent == null);
780        }
781      }
782      // fire the frame close event
783      if (frame != null) {
784        if (frame instanceof JInternalFrame)
785          ((JInternalFrame) frame).doDefaultCloseAction();
786        else
787          ((Window) frame).dispatchEvent(
788              new WindowEvent(
789                  (Window) frame, WindowEvent.WINDOW_CLOSING));
790      }
791     
792    } else {
793      boolean help = ((commandArgs.length > 1)
794                      && commandArgs[0].equals("help"));
795      if (help && commandArgs[1].equals("java")) {
796        System.out.println(
797            "java <classname> <args>\n\n"
798            + "Starts the main method of <classname> with "
799            + "the supplied command line arguments (if any).\n"
800            + "The command is started in a separate thread, "
801            + "and may be interrupted with the \"break\"\n"
802            + "command (friendly), or killed with the \"kill\" "
803            + "command (unfriendly).\n"
804            + "Redirecting can be done with '>' followed by the "
805            + "file to write to, e.g.:\n"
806            + "  java some.Class > ." + File.separator + "some.txt");
807      } else if (help && commandArgs[1].equals("break")) {
808        System.out.println(
809            "break\n\n"
810            + "Attempts to nicely interrupt the running job, "
811            + "if any. If this doesn't respond in an\n"
812            + "acceptable time, use \"kill\".\n");
813      } else if (help && commandArgs[1].equals("kill")) {
814        System.out.println(
815            "kill\n\n"
816            + "Kills the running job, if any. You should only "
817            + "use this if the job doesn't respond to\n"
818            + "\"break\".\n");
819      } else if (help && commandArgs[1].equals("capabilities")) {
820        System.out.println(
821            "capabilities <classname> <args>\n\n"
822            + "Lists the capabilities of the specified class.\n"
823            + "If the class is a " + OptionHandler.class.getName() + " then\n"
824            + "trailing options after the classname will be\n"
825            + "set as well.\n");
826      } else if (help && commandArgs[1].equals("cls")) {
827        System.out.println(
828            "cls\n\n"
829            + "Clears the output area.\n");
830      } else if (help && commandArgs[1].equals("history")) {
831        System.out.println(
832            "history\n\n"
833            + "Prints all issued commands.\n");
834      } else if (help && commandArgs[1].equals("exit")) {
835        System.out.println(
836            "exit\n\n"
837            + "Exits the SimpleCLI program.\n");
838      } else {
839        // Print a help message
840        System.out.println(
841            "Command must be one of:\n"
842            + "\tjava <classname> <args> [ > file]\n"
843            + "\tbreak\n"
844            + "\tkill\n"
845            + "\tcapabilities <classname> <args>\n"
846            + "\tcls\n"
847            + "\thistory\n"
848            + "\texit\n"
849            + "\thelp <command>\n");
850      }
851    }
852  }
853
854  /**
855   * Changes the currently displayed command line when certain keys
856   * are pressed. The up arrow moves back through history entries
857   * and the down arrow moves forward through history entries.
858   *
859   * @param e a value of type 'KeyEvent'
860   */
861  public void doHistory(KeyEvent e) {
862   
863    if (e.getSource() == m_Input) {
864      switch (e.getKeyCode()) {
865      case KeyEvent.VK_UP:
866        if (m_HistoryPos > 0) {
867          m_HistoryPos--;
868          String command = (String) m_CommandHistory.elementAt(m_HistoryPos);
869          m_Input.setText(command);
870        }
871        break;
872      case KeyEvent.VK_DOWN:
873        if (m_HistoryPos < m_CommandHistory.size()) {
874          m_HistoryPos++;
875          String command = "";
876          if (m_HistoryPos < m_CommandHistory.size()) {
877            command = (String) m_CommandHistory.elementAt(m_HistoryPos);
878          }
879          m_Input.setText(command);
880        }
881        break;
882      default:
883        break;
884      }
885    }
886  }
887
888  /**
889   * performs commandline completion on packages and classnames.
890   *
891   * @param e a value of type 'KeyEvent'
892   */
893  public void doCommandlineCompletion(KeyEvent e) {
894    if (e.getSource() == m_Input) {
895      switch (e.getKeyCode()) {
896        // completion
897        case KeyEvent.VK_TAB:
898          if (e.getModifiers() == 0) {
899            // it might take a while before we determined all of the possible
900            // matches (Java doesn't have an application wide cursor handling??)
901            m_Input.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
902            m_OutputArea.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
903
904            try {
905              String txt = m_Input.getText();
906
907              // java call?
908              if (txt.trim().startsWith("java ")) {
909                int pos = m_Input.getCaretPosition();
910                int nonNameCharPos = -1;
911                // find first character not part of a name, back from current position
912                // i.e., " or blank
913                for (int i = pos - 1; i >= 0; i--) {
914                  if (    (txt.charAt(i) == '"') 
915                      || (txt.charAt(i) == ' ') ) {
916                    nonNameCharPos = i;
917                    break;
918                  }
919                }
920
921                if (nonNameCharPos > -1) {
922                  String search = txt.substring(nonNameCharPos + 1, pos);
923
924                  // find matches and common prefix
925                  Vector<String> list = m_Completion.getMatches(search);
926                  String common = m_Completion.getCommonPrefix(list);
927
928                  // just extending by separator is not a real extension
929                  if ((search.toLowerCase() + File.separator).equals(common.toLowerCase()))
930                    common = search;
931
932                  // can we complete the string?
933                  if (common.length() > search.length()) {
934                    try {
935                      m_Input.getDocument().remove(nonNameCharPos + 1, search.length());
936                      m_Input.getDocument().insertString(nonNameCharPos + 1, common, null);
937                    }
938                    catch (Exception ex) {
939                      ex.printStackTrace();
940                    }
941                  }
942                  // ambigiuous? -> print matches
943                  else if (list.size() > 1) {
944                    System.out.println("\nPossible matches:");
945                    for (int i = 0; i < list.size(); i++)
946                      System.out.println("  " + list.get(i));
947                  }
948                  else {
949                    // nothing found, don't do anything
950                  }
951                }
952              }
953            }
954            finally {
955              // set cursor back to default
956              m_Input.setCursor(null);
957              m_OutputArea.setCursor(null);
958            }
959          }
960          break;
961
962        // delete last part up to next blank or dot
963        case KeyEvent.VK_BACK_SPACE:
964          if (e.getModifiers() == KeyEvent.ALT_MASK) {
965            String txt = m_Input.getText();
966            int pos    = m_Input.getCaretPosition();
967           
968            // skip whitespaces
969            int start = pos;
970            start--;
971            while (start >= 0) {
972              if (    (txt.charAt(start) == '.') 
973                   || (txt.charAt(start) == ' ') 
974                   || (txt.charAt(start) == '\\')
975                   || (txt.charAt(start) == '/') )
976                start--;
977              else
978                break;
979            }
980           
981            // find first blank or dot back from position
982            int newPos = -1;
983            for (int i = start; i >= 0; i--) {
984              if (    (txt.charAt(i) == '.') 
985                   || (txt.charAt(i) == ' ')
986                   || (txt.charAt(i) == '\\') 
987                   || (txt.charAt(i) == '/') ) {
988                newPos = i;
989                break;
990              }
991            }
992
993            // remove string
994            try {
995              m_Input.getDocument().remove(newPos + 1, pos - newPos - 1);
996            }
997            catch (Exception ex) {
998              ex.printStackTrace();
999            }
1000          }
1001          break;
1002      }
1003    }
1004  }
1005 
1006  /**
1007   * Only gets called when return is pressed in the input area, which
1008   * starts the command running.
1009   *
1010   * @param e a value of type 'ActionEvent'
1011   */
1012  public void actionPerformed(ActionEvent e) {
1013
1014    try {
1015      if (e.getSource() == m_Input) {
1016        String command = m_Input.getText();
1017        int last = m_CommandHistory.size() - 1;
1018        if ((last < 0)
1019            || !command.equals((String)m_CommandHistory.elementAt(last))) {
1020          m_CommandHistory.addElement(command);
1021          saveHistory();
1022        }
1023        m_HistoryPos = m_CommandHistory.size();
1024        runCommand(command);
1025       
1026        m_Input.setText("");
1027      }
1028    } catch (Exception ex) {
1029      System.err.println(ex.getMessage());
1030    }
1031  }
1032
1033  /**
1034   * loads the command history from the user's properties file.
1035   */
1036  protected void loadHistory() {
1037    int         size;
1038    int         i;
1039    String      cmd;
1040   
1041    size = Integer.parseInt(PROPERTIES.getProperty("HistorySize", "50"));
1042
1043    m_CommandHistory.clear();
1044    for (i = 0; i < size; i++) {
1045      cmd = PROPERTIES.getProperty("Command" + i, "");
1046      if (cmd.length() != 0)
1047        m_CommandHistory.add(cmd);
1048      else 
1049        break;
1050    }
1051   
1052    m_HistoryPos = m_CommandHistory.size();
1053  }
1054 
1055  /**
1056   * saves the current command history in the user's home directory.
1057   */
1058  protected void saveHistory() {
1059    int                         size;
1060    int                         from;
1061    int                         i;
1062    String                      filename;
1063    BufferedOutputStream        stream;
1064   
1065    size = Integer.parseInt(PROPERTIES.getProperty("HistorySize", "50"));
1066   
1067    // determine first command to save
1068    from = m_CommandHistory.size() - size;
1069    if (from < 0)
1070      from = 0;
1071
1072    // fill properties
1073    PROPERTIES.setProperty("HistorySize", "" + size);
1074    for (i = from; i < m_CommandHistory.size(); i++)
1075      PROPERTIES.setProperty("Command" + (i-from), (String) m_CommandHistory.get(i));
1076   
1077    try {
1078      filename = System.getProperties().getProperty("user.home") + File.separatorChar + FILENAME;
1079      stream = new BufferedOutputStream(new FileOutputStream(filename));
1080      PROPERTIES.store(stream, "SimpleCLI");
1081      stream.close();
1082    }
1083    catch (Exception e) {
1084      e.printStackTrace();
1085    }
1086  }
1087 
1088  /**
1089   * Displays the panel in a frame.
1090   *
1091   * @param args        ignored
1092   */
1093  public static void main(String[] args) {
1094    showPanel(new SimpleCLIPanel(), args);
1095  }
1096}
Note: See TracBrowser for help on using the repository browser.