/**
* Copyright (C) 2008-2010 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.imagej;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Vector;
import javax.swing.JOptionPane;
import visualap.Node;
import de.danielsenff.imageflow.ImageFlow;
import de.danielsenff.imageflow.controller.FlowRunner;
import de.danielsenff.imageflow.controller.GraphController;
import de.danielsenff.imageflow.imagej.MacroGenerator.ImageJResult;
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.unit.CommentNode;
import de.danielsenff.imageflow.models.unit.ForGroupUnitElement;
import de.danielsenff.imageflow.models.unit.GroupUnitElement;
import de.danielsenff.imageflow.models.unit.SourceUnitElement;
import de.danielsenff.imageflow.models.unit.UnitElement;
import de.danielsenff.imageflow.models.unit.UnitList;
import de.danielsenff.imageflow.models.unit.UnitElement.Type;
/**
* MacroRunner takes the current workflow and processes it, so that a clean Macro can be
* created. Since for example unconnected units shall be discarded by the {@link MacroGenerator}
* but not deleted, this creates a temporary {@link UnitList} with only the
* units and connections valid and useful without destroying the original workflow.
* @author Daniel Senff
*
*/
public class MacroFlowRunner implements FlowRunner {
private UnitList macroUnitList;
private MacroGenerator macroGenerator;
/**
* @param units
*/
public MacroFlowRunner(final UnitList units) {
this.macroUnitList = sortList(units.clone());
this.macroGenerator = new MacroGenerator(macroUnitList);
}
public String generateMacro(boolean extendedMacro, boolean silent) {
/*
* analysis and verification of the connection network
*/
if (!checkNetwork()) {
System.out.println("Error in node network.");
return null;
}
return getMacroGenerator().generateMacro(extendedMacro, silent);
}
public MacroGenerator getMacroGenerator() {
return this.macroGenerator;
}
/**
* Is true if a {@link UnitElement} exists in the {@link UnitList} that is cleaned for
* running as a macro. If the unit exists, it means, that the subgraph up until this
* unit is valid for running too.
* @param unit
* @return
*/
public boolean contains(UnitElement unit) {
return this.macroUnitList.contains(unit);
}
/**
* Returns a subset of a workflow. It only includes all elements to
* calculate the final {@link UnitElement} specified.
* @param endUnit
* @return
*/
public UnitList getSubUnitList(UnitElement endUnit) {
UnitList subgraph = new UnitList();
subgraph.add(endUnit);
traverseGraph(endUnit, subgraph);
return subgraph;
}
/**
* @param endUnit
* @return
*/
public MacroFlowRunner getSubMacroFlowRunner(final UnitElement endUnit) {
return new MacroFlowRunner(getSubUnitList(endUnit));
}
private void traverseGraph(UnitElement unit, UnitList subgraph) {
for (Input input : unit.getInputs()) {
UnitElement parent = input.getParent();
if(!subgraph.contains(parent)) {
subgraph.add(parent);
traverseGraph(parent, subgraph);
}
}
}
/**
* check if all connections have in and output
* @param connectionMap
* @return
*/
public boolean checkNetwork() {
if(macroUnitList.isEmpty()) {
System.err.println("The workflow is empty, running it doesn't do anything.");
JOptionPane.showMessageDialog(ImageFlow.getApplication().getMainFrame(),
"The workflow has no displayable units, running it doesn't do anything."
+'\n' + "The operation will not proceed.",
"Invalid workflow",
JOptionPane.WARNING_MESSAGE);
return false;
}
for (Node node : macroUnitList) {
UnitElement unit = (UnitElement)node;
for (Input input : unit.getInputs()) {
if(input.isRequired() && !input.isConnected()) {
JOptionPane.showMessageDialog(ImageFlow.getApplication().getMainFrame(),
"Some elements don't have all required input connections."
+'\n' + "The operation will not proceed.",
"Missing connections",
JOptionPane.WARNING_MESSAGE);
return false;
}
}
// sources -> file exists
if(node instanceof SourceUnitElement) {
SourceUnitElement sUnit = (SourceUnitElement) node;
if(!sUnit.existsFile()) {
JOptionPane.showMessageDialog(ImageFlow.getApplication().getMainFrame(),
"The file "+sUnit.getFilePath()+" doesn't exist."
+'\n' + "The operation will not proceed.",
"Invalid workflow",
JOptionPane.WARNING_MESSAGE);
return false;
}
}
}
ConnectionList connections = macroUnitList.getConnections();
if(!connections.isEmpty()) {
// wrong number if units are discarded, number as you can see in the workflow, but not howmany are used internally
System.out.println("Number of connections: "+ connections.size());
for (Connection connection : connections) {
if (!connection.isCompatible()) {
JOptionPane.showMessageDialog(ImageFlow.getApplication().getMainFrame(),
"The graph has conflicting imagetypes."
+'\n' + "The operation will not proceed.",
"Connection error",
JOptionPane.WARNING_MESSAGE);
System.err.println("Faulty connection, image type not compatible");
return false;
}
// TODO actually this is bullshit, a connection should always be valid and existing
switch(connection.checkConnection()) {
case MISSING_BOTH:
case MISSING_FROM_UNIT:
case MISSING_TO_UNIT:
System.err.println("Faulty connection, no input or output unit found.");
System.err.println(connection.toString()
+ " with status " + connection.checkConnection());
return false;
}
}
} else if (macroUnitList.hasSourcesAsDisplay()) {
// ok, we got no connections, but we have Source-units,
// which are set to display.
//do nothing
}
// check if units got all the inputs they need
if (!macroUnitList.areAllInputsConnected()) {
JOptionPane.showMessageDialog(ImageFlow.getApplication().getMainFrame(),
"Some elements don't have all required input connections."
+'\n' + "The operation will not proceed.",
"Missing connections",
JOptionPane.WARNING_MESSAGE);
System.err.println("not all required inputs are connected");
return false;
}
//TODO check parameters
return true;
}
/**
* @param unitElements
* @return
*/
public static UnitList sortList(final UnitList unitElements) {
// temporary list, discarded after this method call
UnitList orderedList = new UnitList();
// the unitElements can contain grouped units
// those need to be destroyed and reintegrated into
// the regular workflow
// as long as we got groups in the unitList
// we identify the group, ungroup it and see if all groups all
Collection<GroupUnitElement> foundGroups = new Vector<GroupUnitElement>();
for (Node node : unitElements) {
if(node instanceof GroupUnitElement && !(node instanceof ForGroupUnitElement)) {
addFoundGroup(foundGroups, node);
}
}
for (GroupUnitElement group : foundGroups) {
GraphController.ungroup(group, unitElements);
}
// reset all marks
unitElements.unmarkUnits();
int mark = 0; // nth element, that has been sorted
int i = 0; // nth lap in the loop
int index = 0; // index 0 < i < unitElements.size()
try {
//loop over all units, selection sort, levelorder
// TODO I don't like this condition, daniel
while(!unitElements.isEmpty()) {
index = i % unitElements.getSize();
Node node = unitElements.get(index);
// find out what kind of node is stored
if(node instanceof CommentNode) {
//if comment then remove and ignore, we don't need it
unitElements.remove(node);
} else if (node instanceof UnitElement) {
UnitElement unit = (UnitElement) node;
// check if all inputs of this node are marked
// if so, this unit is moved from the old list to the new one
if(unit.hasMarkedOutput()) throw new Exception("Unit has Output marked, "
+ "the output should be marked if this unit is already processed in this workflow, "
+ "by which point it shouldn't be here anymore");
if(!unit.hasDisplayBranch()) {
// unit itself is not a display and
// if it doesn't have any unit in its outputs that has
// then it can be removed without consequences
unitElements.remove(index);
}
else if(unit.hasAllInputsMarked()) {
// success
// increment mark & mark outputs
mark++;
unit.setMark(mark);
// remove from the old list and
// move this to the new ordered list
Node remove = unitElements.remove(index);
orderedList.add(remove);
} else if (!unit.hasRequiredInputsConnected()
&& unit.getUnitType() != Type.SOURCE) {
// if unit has no connections actually, it can be discarded right away
unitElements.remove(index);
// if there is a branch with two units connected, the first one will be discarded,
// the second will still exist, but as the input is now missing, it will
// be deleted in the next lap
} else if (!unit.hasOutputsConnected()
&& unit.getUnitType() == Type.SOURCE
&& !unit.isDisplayAny()) {
// if source has no connected outputs and is not visible
unitElements.remove(index);
}
//else throw new Exception("can't be handled");
} else throw new Exception("this is no node object");
// Selection Sort
// each time an element whose previous nodes have already been registered
// is found the next loop over the element list is one element shorter.
// thereby having O(n^2) maybe this can be done better later
i++;
}
} catch(Exception ex) {
// restore list, without damaging it
ex.printStackTrace();
}
return orderedList;
}
private static void addFoundGroup(Collection<GroupUnitElement> foundGroups,
Node node) {
if(node instanceof GroupUnitElement) {
GroupUnitElement group = (GroupUnitElement) node;
foundGroups.add(group);
for (Node embeddedNode : group.getNodes()) {
if(node instanceof GroupUnitElement)
addFoundGroup(foundGroups, embeddedNode);
}
}
}
public ArrayList<ImageJResult> getOpenedImages() {
if (getMacroGenerator() != null) {
return getMacroGenerator().getOpenedImages();
} else {
return new ArrayList<ImageJResult>();
}
}
}