/* * 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. */ /* * Script.java * Copyright (C) 2009 University of Waikato, Hamilton, New Zealand */ package weka.gui.scripting; import weka.core.Option; import weka.core.OptionHandler; import weka.core.SerializedObject; import weka.core.Utils; import weka.core.WekaException; import weka.gui.ExtensionFileFilter; import weka.gui.scripting.event.ScriptExecutionEvent; import weka.gui.scripting.event.ScriptExecutionListener; import weka.gui.scripting.event.ScriptExecutionEvent.Type; import java.io.File; import java.io.Serializable; import java.util.Enumeration; import java.util.HashSet; import java.util.Iterator; import java.util.Vector; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.text.Document; /** * A simple helper class for loading, saving scripts. * * @author fracpete (fracpete at waikato dot ac dot nz) * @version $Revision: 5142 $ */ public abstract class Script implements OptionHandler, Serializable { /** for serialization. */ private static final long serialVersionUID = 5053328052680586401L; /** * The Thread for running a script. * * @author fracpete (fracpete at waikato dot ac dot nz) * @version $Revision: 5142 $ */ public abstract static class ScriptThread extends Thread { /** the owning script. */ protected Script m_Owner; /** commandline arguments. */ protected String[] m_Args; /** whether the thread was stopped. */ protected boolean m_Stopped; /** * Initializes the thread. * * @param owner the owning script * @param args the commandline arguments */ public ScriptThread(Script owner, String[] args) { super(); m_Owner = owner; m_Args = args.clone(); } /** * Returns the owner. * * @return the owning script */ public Script getOwner() { return m_Owner; } /** * Returns the commandline args. * * @return the arguments */ public String[] getArgs() { return m_Args; } /** * Performs the actual run. */ protected abstract void doRun(); /** * Executes the script. */ public void run() { m_Stopped = false; getOwner().notifyScriptFinishedListeners(new ScriptExecutionEvent(m_Owner, Type.STARTED)); try { doRun(); if (!m_Stopped) getOwner().notifyScriptFinishedListeners(new ScriptExecutionEvent(m_Owner, Type.FINISHED)); } catch (Exception e) { e.printStackTrace(); getOwner().notifyScriptFinishedListeners(new ScriptExecutionEvent(m_Owner, Type.ERROR, e)); } getOwner().m_ScriptThread = null; } /** * Stops the script execution. */ public void stopScript() { if (isAlive()) { m_Stopped = true; try { stop(); } catch (Exception e) { // ignored } } } } /** the backup extension. */ public final static String BACKUP_EXTENSION = ".bak"; /** the document this script is a wrapper around. */ protected Document m_Document; /** the filename of the script. */ protected File m_Filename; /** the newline used on this platform. */ protected String m_NewLine; /** whether the script is modified. */ protected boolean m_Modified; /** the current script thread. */ protected transient ScriptThread m_ScriptThread; /** optional listeners when the script finishes. */ protected HashSet<ScriptExecutionListener> m_FinishedListeners; /** * Initializes the script. */ public Script() { this(null); } /** * Initializes the script. * * @param doc the document to use as basis */ public Script(Document doc) { this(doc, null); } /** * Initializes the script. Automatically loads the specified file, if not * null. * * @param doc the document to use as basis * @param file the file to load (if not null) */ public Script(Document doc, File file) { initialize(); m_Document = doc; if (m_Document != null) { m_Document.addDocumentListener(new DocumentListener() { public void changedUpdate(DocumentEvent e) { m_Modified = true; } public void insertUpdate(DocumentEvent e) { m_Modified = true; } public void removeUpdate(DocumentEvent e) { m_Modified = true; } }); } if (file != null) open(file); } /** * Initializes the script. */ protected void initialize() { m_Filename = null; m_NewLine = System.getProperty("line.separator"); m_Modified = false; m_ScriptThread = null; m_FinishedListeners = new HashSet<ScriptExecutionListener>(); } /** * Returns an enumeration describing the available options. * * @return an enumeration of all the available options */ public Enumeration listOptions() { return new Vector().elements(); } /** * Parses a given list of options. * * @param options the list of options as an array of strings * @throws Exception if an option is not supported */ public void setOptions(String[] options) throws Exception { } /** * Gets the current settings of the script. * * @return an array of strings suitable for passing to setOptions */ public String[] getOptions() { return new String[0]; } /** * Returns the extension filters for this type of script. * * @return the filters */ public abstract ExtensionFileFilter[] getFilters(); /** * Returns the default extension. Gets automatically added to files if * their name doesn't end with this. * * @return the default extension (incl. the dot) * @see #saveAs(File) */ public abstract String getDefaultExtension(); /** * Returns the current filename. * * @return the filename, null if no file loaded/saved */ public File getFilename() { return m_Filename; } /** * Returns the new line string in use. * * @return the new line string */ public String getNewLine() { return m_NewLine; } /** * Returns whether the script is modified. * * @return true if the script is modified */ public boolean isModified() { return m_Modified; } /** * Returns the content. * * @return the content or null in case of an error */ public String getContent() { String result; if (m_Document == null) return ""; try { synchronized(m_Document) { result = m_Document.getText(0, m_Document.getLength()); } } catch (Exception e) { e.printStackTrace(); result = null; } return result; } /** * Sets the content. * * @param value the new content */ public void setContent(String value) { if (m_Document == null) return; try { m_Document.insertString(0, value, null); } catch (Exception e) { e.printStackTrace(); } } /** * Checks whether the extension of the file is a known one. * * @param file the file to check * @return true if the exetnsion is known */ protected boolean checkExtension(File file) { boolean result; int i; int n; ExtensionFileFilter[] filters; String[] exts; result = false; filters = getFilters(); for (i = 0; i < filters.length; i++) { exts = filters[i].getExtensions(); for (n = 0; n < exts.length; n++) { if (file.getName().endsWith(exts[n])) { result = true; break; } } if (result) break; } return result; } /** * Empties the document. */ public void empty() { if (m_Document != null) { try { m_Document.remove(0, m_Document.getLength()); } catch (Exception e) { // ignored } } m_Modified = false; m_Filename = null; } /** * Tries to open the file. * * @param file the file to open * @return true if successfully read */ public boolean open(File file) { boolean result; String content; if (m_Document == null) return true; // Warn if extension unwknown if (!checkExtension(file)) System.err.println("Extension of file '" + file + "' is unknown!"); try { // clear old content m_Document.remove(0, m_Document.getLength()); // add new content content = ScriptUtils.load(file); if (content == null) throw new WekaException("Error reading content of file '" + file + "'!"); m_Document.insertString(0, content, null); m_Modified = false; m_Filename = file; result = true; } catch (Exception e) { e.printStackTrace(); try { m_Document.remove(0, m_Document.getLength()); } catch (Exception ex) { // ignored } result = false; m_Filename = null; } return result; } /** * Saves the file under with the current filename. * * @return true if successfully written */ public boolean save() { if (m_Filename == null) return false; else return saveAs(m_Filename); } /** * Saves the file under with the given filename (and updates the internal * filename). * * @param file the filename to write the content to * @return true if successfully written */ public boolean saveAs(File file) { boolean result; File backupFile; if (m_Document == null) return true; // correct extension? if (!checkExtension(file)) file = new File(file.getPath() + getDefaultExtension()); // backup previous file if (file.exists()) { backupFile = new File(file.getPath() + BACKUP_EXTENSION); try { ScriptUtils.copy(file, backupFile); } catch (Exception e) { e.printStackTrace(); } } // save current content try { result = ScriptUtils.save(file, m_Document.getText(0, m_Document.getLength())); m_Filename = file; m_Modified = false; } catch (Exception e) { e.printStackTrace(); result = false; } return result; } /** * Returns whether scripts can be executed. * * @return true if scripts can be executed */ protected abstract boolean canExecuteScripts(); /** * Returns a new thread to execute. * * @param args optional commandline arguments * @return the new thread object */ public abstract ScriptThread newThread(String[] args); /** * Performs pre-execution checks: * <ul> * <li>whether a script is currently running.</li> * <li>whether script has changed and needs saving</li> * <li>whether a filename is set (= empty content)</li> * </ul> * Throws exceptions if checks not met. * * @param args optional commandline arguments * @throws Exception if checks fail */ protected void preCheck(String[] args) throws Exception { if (m_ScriptThread != null) throw new Exception("A script is currently running!"); if (m_Modified) throw new Exception("The Script has been modified!"); if (m_Filename == null) throw new Exception("The Script contains no content?"); } /** * Executes the script. * * @param args optional commandline arguments */ protected void execute(String[] args) { m_ScriptThread = newThread(args); try { m_ScriptThread.start(); } catch (Exception e) { e.printStackTrace(); } } /** * Executes the script. * * @param args optional commandline arguments, can be null * @throws Exception if checks or execution fail */ public void start(String[] args) throws Exception { if (args == null) args = new String[0]; preCheck(args); execute(args); } /** * Stops the execution of the script. */ public void stop() { if (isRunning()) { m_ScriptThread.stopScript(); m_ScriptThread = null; notifyScriptFinishedListeners(new ScriptExecutionEvent(this, Type.STOPPED)); } } /** * Executes the script without loading it first. * * @param file the script to execute * @param args the commandline parameters for the script */ public void run(File file, String[] args) { Script script; try { script = (Script) new SerializedObject(this).getObject(); script.m_Filename = file; script.m_Modified = false; script.start(args); } catch (Exception e) { e.printStackTrace(); } } /** * Returns whether the script is still running. * * @return true if the script is still running */ public boolean isRunning() { return (m_ScriptThread != null); } /** * Adds the given listener to its internal list. * * @param l the listener to add */ public void addScriptFinishedListener(ScriptExecutionListener l) { m_FinishedListeners.add(l); } /** * Removes the given listener from its internal list. * * @param l the listener to remove */ public void removeScriptFinishedListener(ScriptExecutionListener l) { m_FinishedListeners.remove(l); } /** * Notifies all listeners. * * @param e the event to send to all listeners */ protected void notifyScriptFinishedListeners(ScriptExecutionEvent e) { Iterator<ScriptExecutionListener> iter; iter = m_FinishedListeners.iterator(); while (iter.hasNext()) iter.next().scriptFinished(e); } /** * Returns the content as string. * * @return the current content */ public String toString() { String result; try { if (m_Document == null) result = ""; else result = m_Document.getText(0, m_Document.getLength()); } catch (Exception e) { result = ""; } return result.toString(); } /** * Make up the help string giving all the command line options. * * @param script the script to include options for * @return a string detailing the valid command line options */ protected static String makeOptionString(Script script) { StringBuffer result; Enumeration enm; Option option; result = new StringBuffer(""); result.append("\nHelp requested:\n\n"); result.append("-h or -help\n"); result.append("\tDisplays this help screen.\n"); result.append("-s <file>\n"); result.append("\tThe script to execute.\n"); enm = script.listOptions(); while (enm.hasMoreElements()) { option = (Option) enm.nextElement(); result.append(option.synopsis() + '\n'); result.append(option.description() + "\n"); } result.append("\n"); result.append("Any additional options are passed on to the script as\n"); result.append("command-line parameters.\n"); result.append("\n"); return result.toString(); } /** * Runs the specified script. All options that weren't "consumed" (like * "-s" for the script filename), will be used as commandline arguments for * the actual script. * * @param script the script object to use * @param args the commandline arguments * @throws Exception if execution fails */ public static void runScript(Script script, String[] args) throws Exception { String tmpStr; File scriptFile; Vector<String> options; int i; if (Utils.getFlag('h', args) || Utils.getFlag("help", args)) { System.out.println(makeOptionString(script)); } else { // process options tmpStr = Utils.getOption('s', args); if (tmpStr.length() == 0) throw new WekaException("No script supplied!"); else scriptFile = new File(tmpStr); script.setOptions(args); // remove empty elements from array options = new Vector<String>(); for (i = 0; i < args.length; i++) { if (args[i].length() > 0) options.add(args[i]); } // run script script.run(scriptFile, options.toArray(new String[options.size()])); } } }