/*
* The University of Wales, Cardiff Triana Project Software License (Based
* on the Apache Software License Version 1.1)
*
* Copyright (c) 2007 University of Wales, Cardiff. All rights reserved.
*
* Redistribution and use of the software in source and binary forms, with
* or without modification, are permitted provided that the following
* conditions are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. The end-user documentation included with the redistribution, if any,
* must include the following acknowledgment: "This product includes
* software developed by the University of Wales, Cardiff for the Triana
* Project (http://www.trianacode.org)." Alternately, this
* acknowledgment may appear in the software itself, if and wherever
* such third-party acknowledgments normally appear.
*
* 4. The names "Triana" and "University of Wales, Cardiff" must not be
* used to endorse or promote products derived from this software
* without prior written permission. For written permission, please
* contact triana@trianacode.org.
*
* 5. Products derived from this software may not be called "Triana," nor
* may Triana appear in their name, without prior written permission of
* the University of Wales, Cardiff.
*
* 6. This software may not be sold, used or incorporated into any product
* for sale to third parties.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL UNIVERSITY OF WALES, CARDIFF OR ITS CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*
* ------------------------------------------------------------------------
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Triana Project. For more information on the
* Triana Project, please see. http://www.trianacode.org.
*
* This license is based on the BSD license as adopted by the Apache
* Foundation and is governed by the laws of England and Wales.
*
*/
package org.trianacode.gui.action.tools;
import org.trianacode.enactment.TrianaRun;
import org.trianacode.gui.Display;
import org.trianacode.gui.action.ActionDisplayOptions;
import org.trianacode.gui.action.ToolSelectionHandler;
import org.trianacode.gui.desktop.DesktopView;
import org.trianacode.gui.hci.GUIEnv;
import org.trianacode.gui.panels.FormLayout;
import org.trianacode.gui.panels.ParameterPanel;
import org.trianacode.gui.panels.ParameterPanelManager;
import org.trianacode.gui.util.Env;
import org.trianacode.gui.windows.ErrorDialog;
import org.trianacode.gui.windows.ParameterWindow;
import org.trianacode.gui.windows.WindowButtonConstants;
import org.trianacode.taskgraph.*;
import org.trianacode.taskgraph.constants.ScriptConstants;
import org.trianacode.taskgraph.service.NonRunnableClient;
import org.trianacode.taskgraph.service.SchedulerException;
import org.trianacode.taskgraph.tool.Tool;
import org.trianacode.taskgraph.tool.ToolListener;
import org.trianacode.taskgraph.tool.ToolTable;
import org.trianacode.taskgraph.tool.Toolbox;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.List;
/**
* Action class to handle running Triana scripts.
*
* @author Matthew Shields
* @version $Revision: 4048 $
*/
public class RunScriptAction extends AbstractAction implements ActionDisplayOptions {
private ToolSelectionHandler selectionHandler;
private ToolTable tools;
public RunScriptAction(ToolSelectionHandler selhandler, ToolTable tools) {
this.selectionHandler = selhandler;
this.tools = tools;
putValue(SHORT_DESCRIPTION, Env.getString("RunScriptTip"));
putValue(ACTION_COMMAND_KEY, Env.getString("runScript"));
putValue(NAME, Env.getString("runScript") + "...");
}
/**
* Invoked when an action occurs.
*/
public void actionPerformed(ActionEvent e) {
if (selectionHandler.isSingleSelectedTool() && (selectionHandler.getSelectedTool() instanceof Task)) {
handleRunScript((Task) selectionHandler.getSelectedTool(), e.getSource());
}
}
/**
* Handles running a script on the specified task. The script is choosen via a GUI.
*/
public void handleRunScript(Task task, Object source) {
RunScriptPanel panel = new RunScriptPanel((Task) task, source);
panel.init();
ParameterWindow scriptWindow = new ParameterWindow(GUIEnv.getApplicationFrame(), panel.getPreferredButtons(),
true);
scriptWindow.setTitle(Env.getString("runScript") + ": " + task.getToolName());
scriptWindow.setParameterPanel(panel);
Point loc = Display.getAnchorPoint(source, scriptWindow);
loc.translate(140, 40);
scriptWindow.setLocation(loc);
scriptWindow.setVisible(true);
scriptWindow.requestFocus();
}
/**
* Handles running the specified script on the specified task
*/
public static void runScript(Task task, TaskGraph script, ToolTable tools, boolean replace, boolean open,
boolean view) {
runScript(task, script, tools, replace, open, view, null);
}
/**
* Handles running the specified script on the specified task, anchoring the script window to the specified source.
*/
public static void runScript(final Task task, final TaskGraph script, final ToolTable tools,
final boolean replace, final boolean open, final boolean view,
final Object source) {
Thread thread = new Thread() {
public void run() {
try {
Object[] result = executeScript(task, script, tools, view, source);
handleScriptResult(result, task, script.getToolName(), replace, open);
} catch (TaskGraphException except) {
ErrorDialog.show(GUIEnv.getApplicationFrame(), except);
} catch (SchedulerException except) {
ErrorDialog.show(GUIEnv.getApplicationFrame(), except);
}
}
};
thread.setPriority(Thread.NORM_PRIORITY);
thread.start();
}
/**
* Shows the parameter window for and then executes a script.
*
* @return the output from the script, or null if script execution is cancelled
*/
private static Object[] executeScript(final Task task, final TaskGraph script,
final ToolTable tools, final boolean view, final Object source)
throws TaskGraphException, SchedulerException {
GUIEnv.getApplicationFrame().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
TrianaRun run = new TrianaRun(script);
run.setDummyToolName("Script");
if (view) {
GUIEnv.getApplicationFrame()
.addChildTaskGraphPanel((TaskGraph) run.getTaskGraph(), new NonRunnableClient(tools));
} else {
GUIEnv.getApplicationFrame().registerTrianaClient(run.getTaskGraph(), new NonRunnableClient(tools));
}
boolean accepted = showParameterWindow(run.getTaskGraph(), source);
Object[] result = new Object[run.getOutputNodeCount()];
GUIEnv.getApplicationFrame().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
if (accepted) {
run.runTaskGraph();
run.sendInputData(0, task);
}
while (!run.isFinished()) {
try {
Thread.sleep(500);
} catch (InterruptedException except) {
}
receiveOutputData(run, result);
}
receiveOutputData(run, result);
if (view) {
DesktopView panel = GUIEnv.getDesktopViewFor(run.getTaskGraph());
if (panel != null) {
GUIEnv.getApplicationFrame().closeTaskGraphPanel(panel);
}
} else {
GUIEnv.getApplicationFrame().unregisterTrianaClient(run.getTaskGraph());
}
run.dispose();
return result;
}
private static void receiveOutputData(TrianaRun run, Object[] result) {
for (int count = 0; count < run.getOutputNodeCount(); count++) {
if (run.isOutputReady(count)) {
result[count] = run.receiveOutputData(count);
}
}
}
/**
* Show the parameter window for the specified task, and wait until the panel is either accepted or rejected.
*
* @return true if the panel is accepted
*/
private static boolean showParameterWindow(Task task, Object source) {
ParameterPanel panel = ParameterPanelManager.getParameterPanel(task);
if (panel != null) {
ParameterWindow paramWindow = new ParameterWindow(GUIEnv.getApplicationFrame(),
WindowButtonConstants.OK_CANCEL_BUTTONS, true);
paramWindow.setAutoDispose(true);
paramWindow.setTitle(task.getToolName());
paramWindow.setParameterPanel(panel);
paramWindow.setAutoCommit(false);
paramWindow.setAutoCommitVisible(false);
Point loc = Display.getAnchorPoint(source, paramWindow);
loc.translate(140, 40);
paramWindow.setLocation(loc);
paramWindow.setVisible(true);
while (paramWindow.isVisible()) {
try {
Thread.sleep(1000);
} catch (InterruptedException except) {
}
}
return paramWindow.isAccepted();
} else {
return true;
}
}
/**
* Handles the result from a script, such as replacing original task, opening output view and distributing
* protoservices.
*/
private static void handleScriptResult(Object[] result, final Task task, String scriptname,
final boolean replace, final boolean open) throws TaskGraphException {
if ((result != null) && (result.length > 0)) {
Task newtask;
int handleresult = 0;
if (replace && (result[0] instanceof Tool)) {
newtask = TaskLayoutUtils.replaceTask(task, (Tool) result[0], false);
if ((open) && (newtask instanceof TaskGraph)) {
GUIEnv.getApplicationFrame()
.addChildTaskGraphPanel((TaskGraph) newtask, GUIEnv.getTrianaClientFor(newtask));
} else if (open) {
JOptionPane.showMessageDialog(GUIEnv.getApplicationFrame(),
"Cannot display script output as it is not a TaskGraph",
Env.getString("runScript") + ": " + scriptname, JOptionPane.ERROR_MESSAGE,
GUIEnv.getTrianaIcon());
}
checkProtoService(newtask, scriptname);
handleresult++;
}
if (open) {
for (int count = handleresult; count < result.length; count++) {
if (result[count] instanceof TaskGraph) {
newtask = GUIEnv.getApplicationFrame().addParentTaskGraphPanel((TaskGraph) result[count]);
checkProtoService(newtask, scriptname);
}
}
}
}
}
private static void checkProtoService(Task newtask, String scriptname) {
/*if (ProtoServiceHandler.containsProtoService(newtask)) {
String[] options = {"Distribute Now?", "Distribute Later?"};
int result = JOptionPane.showOptionDialog(GUIEnv.getApplicationFrame(), "Script output (" + newtask.getToolName() + ") contains one or more ProtoServices. Would you like to...", Env.getString("runScript") + ": " + scriptname, JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0]);
if (result == 0)
ProtoServiceHandler.distributeProtoServices(newtask);
}*/
}
private class RunScriptPanel extends ParameterPanel implements ToolListener, TreeSelectionListener {
private Task runtask;
private Object source;
private JTree scripttree = new JTree(new ScriptTreeModel());
private JLabel name = new JLabel();
private JLabel pack = new JLabel(" ");
private JTextArea desc = new JTextArea(2, 25);
private JCheckBox replace;
private JCheckBox open = new JCheckBox("Open script output window");
private JCheckBox view = new JCheckBox("View script execution");
private TaskGraph curselect;
public RunScriptPanel(Task runtask) {
this.runtask = runtask;
this.replace = new JCheckBox("Replace " + runtask.getToolName() + " with script output ");
}
public RunScriptPanel(Task runtask, Object source) {
this(runtask);
this.source = source;
}
public boolean isAutoCommitVisible() {
return false;
}
public byte getPreferredButtons() {
return WindowButtonConstants.OK_CANCEL_BUTTONS;
}
/**
* This method is called when the task is set for this panel. It is overridden to create the panel layout.
*/
public void init() {
setLayout(new BorderLayout(8, 0));
populateScriptList();
tools.addToolTableListener(this);
JScrollPane scroll = new JScrollPane(scripttree, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scripttree.setPreferredSize(new JLabel("01234567890123456789012345678901234567890").getPreferredSize());
scripttree.setVisibleRowCount(10);
scripttree.addTreeSelectionListener(this);
scripttree.setRootVisible(true);
scripttree.expandRow(0);
JPanel infopanel = new JPanel(new FormLayout(3, 5));
infopanel.add(new JLabel("Name:"));
infopanel.add(name);
infopanel.add(new JLabel("Package:"));
infopanel.add(pack);
JScrollPane dscroll = new JScrollPane(desc, JScrollPane.VERTICAL_SCROLLBAR_NEVER,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
desc.setLineWrap(true);
desc.setWrapStyleWord(true);
desc.setBackground(name.getBackground());
dscroll.setBorder(new EmptyBorder(0, 0, 0, 0));
JPanel descpanel = new JPanel(new BorderLayout());
descpanel.add(new JLabel("Description:"), BorderLayout.NORTH);
descpanel.add(dscroll, BorderLayout.CENTER);
JPanel choicepanel = new JPanel(new GridLayout(3, 1));
choicepanel.add(replace);
choicepanel.add(open);
choicepanel.add(view);
choicepanel.setBorder(new EmptyBorder(5, 0, 0, 0));
replace.setEnabled(false);
open.setEnabled(false);
view.setEnabled(false);
JPanel infocont = new JPanel(new BorderLayout(0, 3));
infocont.add(infopanel, BorderLayout.NORTH);
infocont.add(descpanel, BorderLayout.CENTER);
infocont.add(choicepanel, BorderLayout.SOUTH);
JPanel infocont2 = new JPanel(new BorderLayout());
infocont2.add(infocont, BorderLayout.NORTH);
desc.setEditable(false);
add(new JLabel("Scripts"), BorderLayout.NORTH);
add(scroll, BorderLayout.CENTER);
add(infocont2, BorderLayout.EAST);
}
private void populateScriptList() {
String[] toolnames = tools.getToolNames();
Tool tool;
for (int count = 0; count < toolnames.length; count++) {
tool = tools.getTool(toolnames[count]);
handlePopulateTool(tool);
}
}
private void handlePopulateTool(Tool tool) {
ScriptTreeModel model = (ScriptTreeModel) scripttree.getModel();
RenderingHint hint;
String pack = "Unknown";
if ((tool.isRenderingHint(ScriptConstants.SCRIPT_RENDERING_HINT)) && (tool instanceof TaskGraph)) {
hint = tool.getRenderingHint(ScriptConstants.SCRIPT_RENDERING_HINT);
if (hint.isRenderingDetail(ScriptConstants.SCRIPT_PACKAGE)) {
pack = (String) hint.getRenderingDetail(ScriptConstants.SCRIPT_PACKAGE);
}
if (runtask instanceof TaskGraph) {
model.insertTool(tool, pack);
} else {
if ((!hint.isRenderingDetail(ScriptConstants.TASKGRAPHS_ONLY)) || (!new Boolean(
(String) hint.getRenderingDetail(ScriptConstants.TASKGRAPHS_ONLY)).booleanValue())) {
model.insertTool(tool, pack);
}
}
}
}
private void populateInfoPanel(TaskGraph taskgraph) {
if (taskgraph != null) {
TaskGraph group = taskgraph;
RenderingHint hint = group.getRenderingHint(ScriptConstants.SCRIPT_RENDERING_HINT);
name.setText(group.getToolName());
pack.setText(group.getToolPackage());
desc.setText(group.getPopUpDescription());
open.setSelected(hint.isRenderingDetail(ScriptConstants.OPEN_TASKGRAPH) && (new Boolean(
(String) hint.getRenderingDetail(ScriptConstants.OPEN_TASKGRAPH)).booleanValue()));
replace.setSelected(hint.isRenderingDetail(ScriptConstants.REPLACE_TASK) && (new Boolean(
(String) hint.getRenderingDetail(ScriptConstants.REPLACE_TASK)).booleanValue()));
open.setEnabled(taskgraph.getDataOutputNodeCount() > 0);
replace.setEnabled(taskgraph.getDataOutputNodeCount() > 0);
view.setEnabled(true);
curselect = taskgraph;
} else {
name.setText("");
pack.setText("");
desc.setText("");
open.setEnabled(false);
replace.setEnabled(false);
view.setEnabled(false);
curselect = null;
}
}
/**
* This method is called when the panel is reset or cancelled. It should reset all the panels components to the
* values specified by the associated task, e.g. a component representing a parameter called "noise" should be
* set to the value returned by a getTool().getParameter("noise") call.
*/
public void reset() {
}
/**
* This method is called when the panel is finished with. It should dispose of any components (e.g. windows)
* used by the panel.
*/
public void dispose() {
tools.removeToolTableListener(this);
}
/**
* Called when the ok button is clicked on the parameter window. Calls applyClicked by default to commit any
* parameter changes.
*/
public void okClicked() {
if (curselect != null) {
runScript(runtask, (TaskGraph) curselect, tools, replace.isEnabled() && replace.isSelected(),
open.isEnabled() && open.isSelected(), view.isEnabled() && view.isSelected(), source);
}
}
@Override
public void toolsAdded(java.util.List<Tool> tools) {
for (Tool tool : tools) {
handlePopulateTool(tool);
}
}
@Override
public void toolsRemoved(List<Tool> tools) {
}
/**
* Called when a new tool is added
*/
public void toolAdded(Tool tool) {
handlePopulateTool(tool);
}
/**
* Called when a tool is removed
*/
public void toolRemoved(Tool tool) {
}
/**
* Called when a Tool Box is added
*/
public void toolBoxAdded(Toolbox toolbox) {
}
/**
* Called when a Tool Box is Removed
*/
public void toolBoxRemoved(Toolbox toolbox) {
}
@Override
public void toolboxNameChanging(Toolbox toolbox, String newName) {
}
@Override
public void toolboxNameChanged(Toolbox toolbox, String newName) {
}
/**
* Called whenever the value of the selection changes.
*
* @param event the event that characterizes the change.
*/
public void valueChanged(TreeSelectionEvent event) {
if (event.getSource() == scripttree) {
TreePath path = event.getNewLeadSelectionPath();
if (path != null) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
if ((node != null) && node.isLeaf()) {
populateInfoPanel((TaskGraph) node.getUserObject());
} else {
populateInfoPanel(null);
}
} else {
populateInfoPanel(null);
}
}
}
}
private class ScriptTreeModel extends DefaultTreeModel {
public ScriptTreeModel() {
super(new DefaultMutableTreeNode("Scripts"));
}
public void insertTool(Tool tool, String pack) {
MutableTreeNode parent = handlePackageNode(pack);
insertChild(parent, tool);
}
private MutableTreeNode handlePackageNode(String pack) {
String[] packs = pack.split("\\.");
MutableTreeNode parent = (MutableTreeNode) getRoot();
MutableTreeNode child;
for (int count = 0; count < packs.length; count++) {
child = getChild(parent, packs[count]);
if (child == null) {
child = insertChild(parent, packs[count]);
}
parent = child;
}
return parent;
}
private MutableTreeNode getChild(TreeNode parent, String pack) {
for (int count = 0; count < parent.getChildCount(); count++) {
if (parent.getChildAt(count).toString().equals(pack)) {
return (MutableTreeNode) parent.getChildAt(count);
}
}
return null;
}
private MutableTreeNode insertChild(MutableTreeNode parent, String pack) {
DefaultMutableTreeNode node = new DefaultMutableTreeNode(pack);
boolean added = false;
for (int count = 0; (count < parent.getChildCount()) && (!added); count++) {
if (parent.getChildAt(count).toString().compareToIgnoreCase(pack) > 0) {
insertNodeInto(node, parent, count);
added = true;
}
}
if (!added) {
insertNodeInto(node, parent, parent.getChildCount());
}
return node;
}
private MutableTreeNode insertChild(MutableTreeNode parent, Tool tool) {
DefaultMutableTreeNode node = new DefaultMutableTreeNode(tool);
DefaultMutableTreeNode temp;
boolean added = false;
for (int count = 0; (count < parent.getChildCount()) && (!added); count++) {
temp = (DefaultMutableTreeNode) parent.getChildAt(count);
if ((temp.getUserObject() instanceof Tool) && (temp.toString().compareToIgnoreCase(tool.toString())
> 0)) {
insertNodeInto(node, parent, count);
added = true;
}
}
if (!added) {
insertNodeInto(node, parent, parent.getChildCount());
}
return node;
}
}
}