All Downloads are FREE. Search and download functionalities are using the official Maven repository.

weka.gui.SimpleCLIPanel Maven / Gradle / Ivy

/*
 *   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 3 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, see .
 */

/*
 * SimpleCLIPanel.java
 * Copyright (C) 2009-2012 University of Waikato, Hamilton, New Zealand
 */

package weka.gui;

import weka.core.Capabilities;
import weka.core.CapabilitiesHandler;
import weka.core.ClassDiscovery;
import weka.core.Defaults;
import weka.core.Instances;
import weka.core.OptionHandler;
import weka.core.Trie;
import weka.core.Utils;
import weka.gui.knowledgeflow.StepVisual;
import weka.gui.scripting.ScriptingPanel;

import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTextPane;
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.List;
import java.util.Properties;
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 ([email protected])
 * @author FracPete (fracpete at waikato dot ac dot nz)
 */
@PerspectiveInfo(ID = "simplecli", title = "Simple CLI",
  toolTipText = "Simple CLI for Weka",
  iconPath = "weka/gui/weka_icon_new_small.png")
public class SimpleCLIPanel extends ScriptingPanel implements ActionListener,
  Perspective {

  /** 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;

  /** Main application (if any) owning this perspective */
  protected GUIApplication m_mainApp;

  /** The Icon for this perspective */
  protected Icon m_perspectiveIcon;

  static {
    // Allow a properties file in the current directory to override
    try {
      PROPERTIES = Utils.readProperties(PROPERTY_FILE);
      java.util.Enumeration keys = 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;

  @Override
  public void instantiationComplete() {

  }

  @Override
  public boolean okToBeActive() {
    return true;
  }

  @Override
  public void setActive(boolean active) {

  }

  @Override
  public void setLoaded(boolean loaded) {

  }

  @Override
  public void setMainApplication(GUIApplication main) {
    m_mainApp = main;
  }

  @Override
  public GUIApplication getMainApplication() {
    return m_mainApp;
  }

  @Override
  public String getPerspectiveID() {
    return "simplecli";
  }

  @Override
  public String getPerspectiveTitle() {
    return "Simple CLI";
  }

  @Override
  public Icon getPerspectiveIcon() {
    if (m_perspectiveIcon != null) {
      return m_perspectiveIcon;
    }

    PerspectiveInfo perspectiveA =
      this.getClass().getAnnotation(PerspectiveInfo.class);
    if (perspectiveA != null && perspectiveA.iconPath() != null
      && perspectiveA.iconPath().length() > 0) {
      m_perspectiveIcon = StepVisual.loadIcon(perspectiveA.iconPath());
    }

    return m_perspectiveIcon;
  }

  @Override
  public String getPerspectiveTipText() {
    return "Simple CLI interface for Weka";
  }

  @Override
  public List getMenus() {
    return null;
  }

  @Override
  public Defaults getDefaultSettings() {
    return null;
  }

  @Override
  public void settingsChanged() {

  }

  @Override
  public boolean acceptsInstances() {
    return false;
  }

  @Override
  public void setInstances(Instances instances) {

  }

  @Override
  public boolean requiresLog() {
    return false;
  }

  @Override
  public void setLog(Logger log) {

  }

  /**
   * A class that handles running the main method of the class in a separate
   * thread.
   * 
   * @author Len Trigg ([email protected])
   * @version $Revision: 12778 $
   */
  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;

    /** True if the class to be executed is weka.Run */
    protected boolean m_classIsRun;

    /**
     * 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 {

      m_classIsRun = theClass == weka.Run.class;

      setDaemon(true);
      Class[] argTemplate = { String[].class };
      m_CommandArgs = commandArgs;
      if (m_classIsRun) {
        if (!commandArgs[0].equalsIgnoreCase("-h")
          && !commandArgs[0].equalsIgnoreCase("-help")) {
          m_CommandArgs = new String[commandArgs.length + 1];
          System
            .arraycopy(commandArgs, 0, m_CommandArgs, 1, commandArgs.length);
          m_CommandArgs[0] = "-do-not-prompt-if-multiple-matches";
        }
      }
      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.
     */
    @Override
    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: 12778 $
   */
  public static class CommandlineCompletion {

    /** all the available packages. */
    protected Vector m_Packages;

    /** a trie for storing the packages. */
    protected 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 set = new HashSet();
        for (int i = 0; i < list.size(); i++) {
          String[] parts = 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();
        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 getFileMatches(String partial) {
      Vector result;
      File file;
      File dir;
      File[] files;
      int i;
      String prefix;
      boolean caseSensitive;
      String name;
      boolean match;

      result = new Vector();

      // 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 getClassMatches(String partial) {
      String pkg;
      String cls;
      Vector result;
      Vector 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();

      // 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 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 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.
   */
  @Override
  protected void initialize() {
    super.initialize();

    m_CommandHistory = new Vector();
    m_HistoryPos = 0;
    m_Completion = new CommandlineCompletion();
  }

  /**
   * Sets up the GUI after initializing the members.
   */
  @Override
  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() {
      @Override
      public void keyPressed(KeyEvent e) {
        doHistory(e);
        doCommandlineCompletion(e);
      }
    });
    add(m_Input, "South");
  }

  /**
   * Finishes up after initializing members and setting up the GUI.
   */
  @Override
  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 . 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"
      + " 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
   */
  @Override
  public ImageIcon getIcon() {
    return ComponentHelper.getImageIcon("weka_icon_new_48.png");
  }

  /**
   * Returns the current title for the frame/dialog.
   * 
   * @return the title
   */
  @Override
  public String getTitle() {
    return "SimpleCLI";
  }

  /**
   * Returns the text area that is used for displaying output on stdout and
   * stderr.
   * 
   * @return the JTextArea
   */
  @Override
  public JTextPane getOutput() {
    return m_OutputArea;
  }

  /**
   * Not supported.
   * 
   * @return always null
   */
  @Override
  public JMenuBar getMenuBar() {
    return null;
  }

  /**
   * Executes a simple cli command.
   * 
   * @param commands the command string
   * @throws Exception if an error occurs
   */
  @SuppressWarnings("deprecation")
  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 "")
        Vector argv = new Vector();
        for (int i = 2; i < commandArgs.length; i++) {
          argv.add(commandArgs[i]);
        }

        m_RunThread =
          new ClassRunner(theClass, 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 args = new Vector();
            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("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  \n\n"
          + "Starts the main method of  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  \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   [ > file]\n" + "\tkill\n"
          + "\tcapabilities  \n" + "\tcls\n" + "\thistory\n"
          + "\texit\n" + "\thelp \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 = 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 = 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 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'
   */
  @Override
  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(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), 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);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy