/* * 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.hci; import java.util.ArrayList; import java.util.Iterator; import org.trianacode.gui.main.TrianaLayoutConstants; import org.trianacode.taskgraph.CableException; import org.trianacode.taskgraph.Node; import org.trianacode.taskgraph.Task; import org.trianacode.taskgraph.TaskGraph; import org.trianacode.taskgraph.service.TypeChecking; /** * Auto connect the nodes of a Task within a Main Triana * <p/> * <<<<<<< AutoConnect.java * * @author Ian Wang * @author Ian Wang * @version $Revision: 4048 $ */ public class AutoConnect { public static int TOOL_WIDTH = TrianaLayoutConstants.DEFAULT_TOOL_SIZE.width; public static int TOOL_HEIGHT = TrianaLayoutConstants.DEFAULT_TOOL_SIZE.height; private static double LEFT_SCALE = 2; private static double Y_SCALE = 2; private int x; private int y; public void autoConnect(Task task, int x, int y, TaskGraph taskgraph) { Node innode = getAvailableNode(task, true); Node outnode = getAvailableNode(task, false); boolean connection = true; this.x = x; this.y = y; while (connection && ((innode != null) || (outnode != null))) { connection = tryConnect(innode, outnode, taskgraph); innode = getAvailableNode(task, true); outnode = getAvailableNode(task, false); } } /** * try to connect a node to either the input or output node specified */ private boolean tryConnect(Node innode, Node outnode, TaskGraph taskgraph) { ArrayList ininfo = new ArrayList(); ArrayList outinfo = new ArrayList(); if (outnode != null) { Node[] innodes = getNodes(taskgraph.getTasks(false), true); double inscore; for (int count = 0; count < innodes.length; count++) { inscore = getConnectionScore(outnode, innodes[count], taskgraph, false); if (inscore > Double.NEGATIVE_INFINITY) { ininfo.add(new ConnectionInfo(innodes[count], inscore)); } } } if (innode != null) { Node[] outnodes = getNodes(taskgraph.getTasks(false), false); double outscore; for (int count = 0; count < outnodes.length; count++) { outscore = getConnectionScore(outnodes[count], innode, taskgraph, true); if (outscore > Double.NEGATIVE_INFINITY) { outinfo.add(new ConnectionInfo(outnodes[count], outscore)); } } } return tryConnect(innode, outnode, ininfo, outinfo, taskgraph); } /** * try to connect either the input or output node specified to the best possible node */ private boolean tryConnect(Node innode, Node outnode, ArrayList ininfo, ArrayList outinfo, TaskGraph taskgraph) { boolean tryconnect = true; boolean connection = false; while (tryconnect && (!connection)) { ConnectionInfo bestin = getBestConnectionInfo(ininfo); ConnectionInfo bestout = getBestConnectionInfo(outinfo); boolean input = false; if ((bestin != null) && (bestout != null)) { if (bestin.score > bestout.score) { input = true; } else { input = false; } } else if (bestin != null) { input = true; } else if (bestout != null) { input = false; } else { tryconnect = false; } double oldscore = Double.NEGATIVE_INFINITY; Node oldnode = null; if (tryconnect) { if (input) { if (bestin.node.isConnected()) { oldnode = bestin.node.getCable().getSendingNode(); oldscore = getConnectionScore(oldnode, bestin.node, taskgraph, true); } if (oldscore < bestin.score) { try { taskgraph.connect(outnode, bestin.node); connection = true; if (oldnode != null) { tryConnect(null, oldnode, taskgraph); } } catch (CableException except) { } } else { ininfo.remove(bestin); } } else { if (bestout.node.isConnected()) { oldnode = bestout.node.getCable().getReceivingNode(); oldscore = getConnectionScore(bestout.node, oldnode, taskgraph, false); } if (oldscore < bestout.score) { try { taskgraph.connect(bestout.node, innode); connection = true; if ((oldnode != null) && (oldnode.getTask() != null)) { tryConnect(oldnode, null, taskgraph); } } catch (CableException except) { } } else { outinfo.remove(bestout); } } } } return connection; } /** * @return an unconnected input/output node on the specified task */ private Node getAvailableNode(Task task, boolean input) { Node nodes[]; Node node = null; if (input) { nodes = task.getDataInputNodes(); } else { nodes = task.getDataOutputNodes(); } for (int count = 0; ((count < nodes.length) && (node == null)); count++) { if (!nodes[count].isConnected()) { node = nodes[count]; } } return node; } /** * @return all the input/output nodes in a set of tasks */ private Node[] getNodes(Task[] tasks, boolean input) { ArrayList list = new ArrayList(); Node[] nodes; for (int tcount = 0; tcount < tasks.length; tcount++) { if (input == true) { nodes = tasks[tcount].getDataInputNodes(); } else { nodes = tasks[tcount].getDataOutputNodes(); } for (int ncount = 0; ncount < nodes.length; ncount++) { list.add(nodes[ncount]); } } return (Node[]) list.toArray(new Node[list.size()]); } /** * @return a score denoting the suitability of connection an input node to an output node (higher = better) */ private double getConnectionScore(Node outnode, Node innode, TaskGraph taskgraph, boolean input) { if (!TypeChecking.isCompatibility(outnode, innode)) { return Double.NEGATIVE_INFINITY; } if (taskgraph.getTask(outnode) == taskgraph.getTask(innode)) { return Double.NEGATIVE_INFINITY; } if (isLoop(outnode, innode, new ArrayList())) { return Double.NEGATIVE_INFINITY; } if ((!outnode.isBottomLevelNode()) || (!innode.isBottomLevelNode())) { return Double.NEGATIVE_INFINITY; } if (outnode.isConnected() && (outnode.getCable().getReceivingTask() == taskgraph.getControlTask())) { return Double.NEGATIVE_INFINITY; } if (innode.isConnected() && (innode.getCable().getSendingTask() == taskgraph.getControlTask())) { return Double.NEGATIVE_INFINITY; } double inx = x; double iny = y; double outx = x + TOOL_WIDTH; double outy = y; try { if (outnode.getTask().isParameterName(Task.GUI_X)) { outx = Double.parseDouble((String) outnode.getTask().getParameter(Task.GUI_X)) + TOOL_WIDTH; } if (outnode.getTask().isParameterName(Task.GUI_Y)) { outy = Double.parseDouble((String) outnode.getTask().getParameter(Task.GUI_Y)); } if (innode.getTask().isParameterName(Task.GUI_X)) { inx = Double.parseDouble((String) innode.getTask().getParameter(Task.GUI_X)) + TOOL_WIDTH; } if (innode.getTask().isParameterName(Task.GUI_Y)) { iny = Double.parseDouble((String) innode.getTask().getParameter(Task.GUI_Y)); } double xval = Math.abs(outx - inx); double yval = Math.abs(outy - iny) * Y_SCALE; if (inx < outx) { xval *= LEFT_SCALE; } return -xval - (yval * Y_SCALE); } catch (Exception except) { return Double.NEGATIVE_INFINITY; } } /** * @return true if connecting outnode to innode will cause a loop */ public boolean isLoop(Node outnode, Node innode, ArrayList list) { if (innode.getTask() == outnode.getTask()) { return true; } else { list.add(outnode.getTask()); Node[] innodes = outnode.getTask().getDataInputNodes(); boolean loop = false; for (int count = 0; (count < innodes.length) && (!loop); count++) { if (innodes[count].isConnected() && (!list.contains(innodes[count].getCable().getSendingTask()))) { loop = isLoop(innodes[count].getCable().getSendingNode(), innode, list); } } return loop; } } /** * @return the maximum score in an array list of connection infos */ private ConnectionInfo getBestConnectionInfo(ArrayList list) { ConnectionInfo best = null; ConnectionInfo info; Iterator iter = list.iterator(); while (iter.hasNext()) { info = (ConnectionInfo) iter.next(); if ((best == null) || (info.score > best.score)) { best = info; } } return best; } private class ConnectionInfo { public Node node; public double score; public ConnectionInfo(Node node, double score) { this.node = node; this.score = score; } } }