/**
* Copyright (C) 2008-2011 Daniel Senff
*
* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package de.danielsenff.imageflow.models.unit;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Vector;
import visualap.Node;
import de.danielsenff.imageflow.models.Model;
import de.danielsenff.imageflow.models.ModelListener;
import de.danielsenff.imageflow.models.NodeList;
import de.danielsenff.imageflow.models.connection.Connection;
import de.danielsenff.imageflow.models.connection.ConnectionList;
import de.danielsenff.imageflow.models.connection.Input;
import de.danielsenff.imageflow.models.connection.Output;
import de.danielsenff.imageflow.models.connection.Pin;
import de.danielsenff.imageflow.models.unit.UnitElement.Type;
/**
* List holding all {@link UnitElement}s. For the moment this is just an empty wrapper.
* @author danielsenff
*
*/
public class UnitList extends NodeList<Node> implements Model, Cloneable {
private ConnectionList connections;
/**
*
*/
private static final long serialVersionUID = -8204689428123811757L;
private HashSet<ModelListener> listeners;
/**
*
*/
public UnitList() {
this.listeners = new HashSet<ModelListener>();
this.connections = new ConnectionList();
}
@Override
public UnitList clone() {
UnitList clone = new UnitList();
Collection<Connection> tmpConn = new Vector<Connection>();
HashMap<Pin, Pin> correspondingPins = new HashMap<Pin, Pin>();
Node nodeClone;
UnitElement unit, unitClone;
for (Node node : this) {
try {
nodeClone = node.clone();
clone.add(nodeClone);
if(node instanceof UnitElement) {
unit = (UnitElement) node;
unitClone = (UnitElement) nodeClone;
/*
* clone inputs
*/
for (int i = 0; i < unit.getInputsCount(); i++) {
Input input = unit.getInput(i);
correspondingPins.put(input, unitClone.getInput(i));
Connection connection;
// we visit each connection
if(input.isConnected()) {
connection = input.getConnection();
cloneConnection(clone, tmpConn, correspondingPins, connection);
}
}
/*
* clone outputs
*/
for (int o = 0; o < unit.getOutputsCount(); o++) {
Output output = unit.getOutput(o);
correspondingPins.put(output, unitClone.getOutput(o));
// we visit each connection
if(output.isConnected()) {
for (Connection connection : output.getConnections()) {
cloneConnection(clone, tmpConn, correspondingPins, connection);
}
}
}
}
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
return clone;
}
/**
* @param clone
* @param tmpConn
* @param correspondingPins
* @param conn
*/
private void cloneConnection(UnitList clone,
Collection<Connection> tmpConn,
HashMap<Pin, Pin> correspondingPins, Connection conn) {
if(tmpConn.contains(conn)) {
// if this connection is already in the list
// the corresponding unit on the other end has already been cloned
// so we can create a new connection to this clone
Output output = (Output) correspondingPins.get(conn.getOutput());
Input input = (Input) correspondingPins.get(conn.getInput());
Connection newConn = new Connection(output, input);
clone.getConnections().add(newConn);
} else {
// we are on the first end of this connection
tmpConn.add(conn);
}
}
@Override
public boolean add(Node o) {
notifyModelListeners();
return super.add(o);
}
@Override
public boolean addAll(java.util.Collection<? extends Node> c) {
notifyModelListeners();
return super.addAll(c);
};
@Override
public Node remove(int index) {
notifyModelListeners();
return super.remove(index);
}
/**
* Checks all Inputs if they are connected or not.
* @param networkOK
* @return
*/
public boolean areAllInputsConnected() {
// check inputs of all units
for (final Node node : this) {
if(node instanceof UnitElement) {
final UnitElement unit = (UnitElement) node;
// does the unit actually have inputs?
if(unit.hasInputs()) {
//check all inputs of this unit
for (Input input : unit.getInputs()) {
// is this input connected?
if (!input.isConnected()
// is this input actually required?
&& input.isRequired()) {
System.err.println(input + " is not connected");
return false;
}
}
}
}
}
return true;
}
/**
* Remove this Unit from the workflow.
* This also removes and replaces the connections between possibly connected unit.
* @param unit
* @return
*/
@Override
public boolean remove(final Node node) {
if(node instanceof UnitElement) {
UnitElement unit = (UnitElement) node;
// new connection between the nodes that this deleted node was in between
replaceConnection(unit);
// delete old connections
unbindUnit(unit);
}
// remove unit itself
return removeUnchecked(node);
}
/**
* Remove this Unit from the workflow.
* This doesn't touch connections at all.
* @param node
* @return
*/
public boolean removeUnchecked(final Node node) {
// remove unit itself
boolean remove = super.remove(node);
notifyModelListeners();
return remove;
}
/**
* Removes all connections to this {@link UnitElement}.
* @param unit
*/
public void unbindUnit(final UnitElement unit) {
// find connections which are attached to this unit
Collection<Connection> tmpConn = new Vector<Connection>();
for (Connection connection : getConnections()) {
if(connection.isConnectedToUnit(unit)
&& !connection.isLocked()) {
// put in list to delete
tmpConn.add(connection);
}
}
for (Connection connection : tmpConn) {
// delete connections
connections.remove(connection);
}
}
private void replaceConnection(final UnitElement unit) {
// replacing makes only sense, when it has inputs and outputs
if(!unit.hasInputsConnected() || !unit.hasOutputsConnected()) {
return;
}
// get the outputs of the currently connected inputs
int numberConnectedInputs = unit.getInputsCount();
ArrayList<Output> connectedOutputs = new ArrayList<Output>(numberConnectedInputs);
// we use a vector, add stuff to the end and iterate over this without indicies
// check if pins are connected and ignore, if not
for (int i = 0; i < numberConnectedInputs; i++) {
Input input = unit.getInput(i);
if(input.isConnected()) {
connectedOutputs.add(input.getFromOutput());
}
}
// same for inputs
int numberConnectedOutputs = unit.getOutputsCount();
ArrayList<Input> connectedInputs = new ArrayList<Input>(numberConnectedOutputs);
for (int i = 0; i < numberConnectedOutputs; i++) {
Output output = unit.getOutput(i);
if(output.isConnected())
for (Connection connection : output.getConnections()) {
Input toInput = connection.getInput();
connectedInputs.add(toInput);
}
}
// now we create new connections based on the lists of
// formerly connected outputs and inputs.
// if it doesn't match, discard
// get the longer list, inputs or outputs
int numPins = Math.min(connectedInputs.size(), connectedOutputs.size());
for (int i = 0; i < numPins; i++) {
getConnections().add(connectedInputs.get(i), connectedOutputs.get(i));
}
}
/**
* Returns true, if any {@link UnitElement} in this UnitList is set as a displayUnit
* @return
*/
public boolean hasUnitAsDisplay() {
for (Node node : this) {
if(node instanceof UnitElement) {
if(((UnitElement)node).isDisplayAny())
return true;
}
}
return false;
}
/**
* Returns true, if any source in this UnitList is set as a displayUnit
* @return
*/
public boolean hasSourcesAsDisplay() {
UnitElement unit;
for (int i = 0; i < size(); i++) {
unit = (UnitElement) get(i);
if(unit.getUnitType() == Type.SOURCE && unit.isDisplayAny())
return true;
}
return false;
}
public void addModelListener(ModelListener listener) {
if (! this.listeners.contains(listener)) {
this.listeners.add(listener);
notifyModelListener(listener);
}
}
public void notifyModelListener(ModelListener listener) {
listener.modelChanged(this);
}
public void notifyModelListeners() {
for (final ModelListener listener : this.listeners) {
notifyModelListener(listener);
}
}
public void removeModelListener(ModelListener listener) {
this.listeners.remove(listener);
}
/**
* Get the {@link ConnectionList}
* @return
*/
public ConnectionList getConnections() {
return connections;
}
/**
* Add a {@link Connection}
* @param con
* @return
*/
public boolean addConnection(Connection con) {
return this.connections.add(con);
}
/**
* Adds the {@link Connection}s of a {@link ConnectionList} to this ConnectionList.
* They are added, the current ConnectionList is not replaced.
* @param newConnList
*/
public void addConnectionList(ConnectionList newConnList) {
for (Connection conn : newConnList) {
this.connections.add(conn);
}
}
/**
* Resets all marks to zero.
* @param units
*/
public void unmarkUnits() {
UnitElement unit;
for (Node node : this) {
if(node instanceof UnitElement) {
unit = (UnitElement) node;
unit.setMark(0);
}
}
}
@Override
public void clear() {
super.clear();
connections.clear();
notifyModelListeners();
}
/**
* Set the {@link ConnectionList}
* @param connList
*/
public void setConnectionList(final ConnectionList connList) {
this.connections = connList;
}
}