/*
* 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()]));
}
}
}