/*
* (c) Copyright 2010-2011 AgileBirds
*
* This file is part of OpenFlexo.
*
* OpenFlexo 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 3 of the License, or
* (at your option) any later version.
*
* OpenFlexo 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 OpenFlexo. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.openflexo.wkf;
import java.util.Collections;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.openflexo.fge.geom.FGEPoint;
import org.openflexo.foundation.utils.FlexoProgress;
import org.openflexo.foundation.wkf.FlexoProcess;
import org.openflexo.foundation.wkf.edge.FlexoPostCondition;
import org.openflexo.foundation.wkf.edge.MessageEdge;
import org.openflexo.foundation.wkf.node.AbstractNode;
import org.openflexo.foundation.wkf.node.FatherNode;
import org.openflexo.foundation.wkf.node.FlexoNode;
import org.openflexo.foundation.wkf.node.FlexoPreCondition;
import org.openflexo.foundation.wkf.node.PetriGraphNode;
import org.openflexo.foundation.wkf.node.SelfExecutableNode;
import org.openflexo.foundation.wkf.ws.FlexoPortMap;
import org.openflexo.localization.FlexoLocalization;
public abstract class WKFLayoutManager {
private static final Logger logger = Logger.getLogger(WKFLayoutManager.class.getPackage().getName());
protected Hashtable<PetriGraphNode, AutoLayoutNode> nodeMap;
private FlexoProcess _process;
private AutoLayoutNodePath mainPath;
private Vector<AutoLayoutNodePath> secondaryPaths;
private Vector<AutoLayoutNode> isolatedNodes;
private Vector<FlexoPostCondition<AbstractNode, AbstractNode>> loopingEdges;
private Vector<FlexoPostCondition<AbstractNode, AbstractNode>> shortcutEdges;
private Vector<FlexoPostCondition<AbstractNode, AbstractNode>> uncoveredEdges;
public WKFLayoutManager(FlexoProcess process) {
_process = process;
nodeMap = new Hashtable<PetriGraphNode, AutoLayoutNode>();
isolatedNodes = new Vector<AutoLayoutNode>();
secondaryPaths = new Vector<AutoLayoutNodePath>();
}
public abstract void layoutProcess(FlexoProgress progress);
public FlexoProcess getProcess() {
return _process;
}
public void recomputeProcessStructure(FlexoProgress progress) {
buildNodeMap(progress);
buildPaths(progress);
if (logger.isLoggable(Level.FINE)) {
logger.fine("Best path = " + mainPath);
logger.fine("Secondary paths: ");
for (AutoLayoutNodePath p : secondaryPaths) {
System.out.println(p.toString());
}
logger.fine("Isolated nodes: " + isolatedNodes.size() + " : " + isolatedNodes);
logger.fine("Looping edges: " + loopingEdges.size());
for (FlexoPostCondition<?, ?> post : loopingEdges) {
logger.fine("Looping edge: " + post);
}
logger.fine("Shortcut edges: " + shortcutEdges.size());
for (FlexoPostCondition<?, ?> post : shortcutEdges) {
logger.fine("Shortcut edge: " + post);
}
logger.fine("Uncovered edges: " + uncoveredEdges.size());
for (FlexoPostCondition<?, ?> post : uncoveredEdges) {
logger.fine("Uncovered edge: " + post);
}
}
}
private void buildNodeMap(FlexoProgress progress) {
if (progress != null) {
progress.setProgress(FlexoLocalization.localizedForKey("building_node_map"));
}
nodeMap.clear();
for (PetriGraphNode n : getProcess().getActivityPetriGraph().getNodes()) {
AutoLayoutNode aln = new AutoLayoutNode(n);
nodeMap.put(n, aln);
}
if (progress != null) {
progress.resetSecondaryProgress(nodeMap.size());
}
for (PetriGraphNode n : nodeMap.keySet()) {
AutoLayoutNode aln = nodeMap.get(n);
if (progress != null) {
progress.setSecondaryProgress(FlexoLocalization.localizedForKey("current_node") + " " + aln.node.getName());
}
aln.addEdges(n);
if (n instanceof FatherNode && ((FatherNode) n).hasContainedPetriGraph()) {
for (FlexoNode end : ((FatherNode) n).getContainedPetriGraph().getAllEndNodes()) {
aln.addEdges(end);
}
}
if (n instanceof SelfExecutableNode && ((SelfExecutableNode) n).hasExecutionPetriGraph()) {
for (FlexoNode end : ((SelfExecutableNode) n).getExecutionPetriGraph().getAllEndNodes()) {
aln.addEdges(end);
}
}
}
}
private void buildPaths(FlexoProgress progress) {
mainPath = findMainPath(progress);
if (mainPath == null) {
if (logger.isLoggable(Level.WARNING)) {
logger.warning("No main path found!");
}
return;
}
buildSecondaryPaths(progress);
if (progress != null) {
progress.setProgress(FlexoLocalization.localizedForKey("finding_loops_and_shortcuts"));
}
findLoopingAndShortcutEdges();
if (progress != null) {
progress.setProgress(FlexoLocalization.localizedForKey("finding_uncovered_edges"));
}
uncoveredEdges = findUncoveredEdges();
}
private void buildSecondaryPaths(FlexoProgress progress) {
if (progress != null) {
progress.setProgress(FlexoLocalization.localizedForKey("building_secondary_path"));
}
isolatedNodes.clear();
Vector<AutoLayoutNode> forgottenNodes = new Vector<AutoLayoutNode>();
for (AutoLayoutNode node : nodeMap.values()) {
if (!mainPath.contains(node)) {
if (node.previousNodes.size() == 0 && node.followingNodes.size() == 0) {
isolatedNodes.add(node);
} else {
forgottenNodes.add(node);
}
}
}
secondaryPaths.clear();
if (progress != null) {
progress.resetSecondaryProgress(forgottenNodes.size());
}
while (forgottenNodes.size() > 0) {
Vector<AutoLayoutNodePath> potentialSubPaths = _buildAllSubPathForForForgottenNodes(forgottenNodes, mainPath, secondaryPaths);
AutoLayoutNodePath bestSecondaryPath = potentialSubPaths.firstElement();
bestSecondaryPath.name = "secondary" + secondaryPaths.size();
secondaryPaths.add(bestSecondaryPath);
for (AutoLayoutNode node : bestSecondaryPath) {
if (forgottenNodes.contains(node)) {
if (progress != null) {
progress.setSecondaryProgress(FlexoLocalization.localizedForKey("from_node") + " "
+ bestSecondaryPath.firstElement().node.getName());
}
forgottenNodes.remove(node);
}
}
}
}
private Vector<AutoLayoutNodePath> _buildAllSubPathForForForgottenNodes(Vector<AutoLayoutNode> forgottenNodes,
AutoLayoutNodePath mainPath, Vector<AutoLayoutNodePath> secondaryPaths) {
Vector<AutoLayoutNodePath> returned = new Vector<AutoLayoutNodePath>();
AutoLayoutNodePath alreadyVisited = new AutoLayoutNodePath(mainPath);
for (AutoLayoutNodePath subPath : secondaryPaths) {
alreadyVisited.addAll(subPath);
}
for (AutoLayoutNode node : forgottenNodes) {
Vector<AutoLayoutNodePath> potentialSubPaths = _buildPathsFrom(node, alreadyVisited.clone(), false);
for (AutoLayoutNodePath p : potentialSubPaths) {
if (p.firstElement().previousNodes.size() == 0) {
// This subpath has no begin
p.startPath = null;
} else {
for (AutoLayoutNode n : p.firstElement().previousNodes.values()) {
if (mainPath.contains(n)) { // Attach this subpath to first found node in main path
p.insertElementAt(n, 0);
p.startPath = mainPath;
break;
}
}
if (p.startPath == null) {
for (AutoLayoutNode n : p.firstElement().previousNodes.values()) {
for (AutoLayoutNodePath secondaryPath : secondaryPaths) {
if (secondaryPath.contains(n)) { // Attach this subpath to first found node in secondary path
p.insertElementAt(n, 0);
p.startPath = secondaryPath;
break;
}
}
if (p.startPath != null) {
break;
}
}
}
}
if (p.lastElement().followingNodes.size() == 0) {
// This subpath has no end
p.endPath = null;
} else {
for (AutoLayoutNode n : p.lastElement().followingNodes.values()) {
if (mainPath.contains(n)) { // Attach this subpath to first found node in main path
p.add(n);
p.endPath = mainPath;
break;
}
}
if (p.endPath == null) {
for (AutoLayoutNode n : p.lastElement().followingNodes.values()) {
for (AutoLayoutNodePath secondaryPath : secondaryPaths) {
if (secondaryPath.contains(n)) { // Attach this subpath to first found node in secondary path
p.add(n);
p.endPath = secondaryPath;
break;
}
}
if (p.endPath != null) {
break;
}
}
}
}
returned.add(p);
}
}
Collections.sort(returned, new Comparator<AutoLayoutNodePath>() {
@Override
public int compare(AutoLayoutNodePath p1, AutoLayoutNodePath p2) {
if (p1.size() != p2.size()) {
return p2.size() - p1.size();
}
int lp1 = (p1.startPath != null ? 1 : 0) + (p1.endPath != null ? 1 : 0);
int lp2 = (p2.startPath != null ? 1 : 0) + (p2.endPath != null ? 1 : 0);
if (lp1 != lp2) {
return lp2 - lp1;
}
if (lp1 == 2) {
if (p1.startPath == p1.endPath) {
return -1;
} else if (p2.startPath == p2.endPath) {
return 1;
}
}
return -1;
}
});
/*for (AutoLayoutNodePath p : returned) {
System.out.println("potential path : "+p
+" start="+(p.startPath !=null ? p.startPath.name : "null")
+" end="+(p.endPath != null ? p.endPath.name : "null"));
}*/
return returned;
}
private AutoLayoutNodePath findMainPath(FlexoProgress progress) {
if (progress != null) {
progress.setProgress(FlexoLocalization.localizedForKey("building_main_path"));
}
AutoLayoutNodePath returned = null;
int bestLength = 0;
Vector<PetriGraphNode> allBeginNodes = new Vector<PetriGraphNode>(getProcess().getActivityPetriGraph().getAllBeginNodes());
allBeginNodes.addAll(getProcess().getActivityPetriGraph().getAllStartNodes());
if (allBeginNodes.size() == 0) {
findBestBeginNodes(allBeginNodes, 0);
}
for (AbstractNode n : allBeginNodes) {
AutoLayoutNode beginNode = nodeMap.get(n);
for (AutoLayoutNodePath p : _buildPathsFrom(beginNode)) {
// System.out.println("Path: "+p);
if (p.size() > bestLength) {
returned = p;
bestLength = p.size();
}
}
}
if (returned != null) {
returned.name = "main";
}
return returned;
}
/**
*
*/
private void findBestBeginNodes(Vector<PetriGraphNode> allBeginNodes, int maxIncomingEdges) {
for (PetriGraphNode node : getProcess().getActivityPetriGraph().getNodes()) {
int count = 0;
if (node instanceof FlexoNode) {
for (FlexoPreCondition pc : ((FlexoNode) node).getPreConditions()) {
count += getIncomingNonMessageEdgeCount(pc);
}
}
count += getIncomingNonMessageEdgeCount(node);
if (count <= maxIncomingEdges) {
allBeginNodes.add(node);
}
}
if (allBeginNodes.size() == 0 && maxIncomingEdges < 10) {
findBestBeginNodes(allBeginNodes, maxIncomingEdges + 1);
}
}
/**
* @param allBeginNodes
* @param maxIncomingEdges
* @param node
*/
private int getIncomingNonMessageEdgeCount(AbstractNode node) {
Vector<FlexoPostCondition<AbstractNode, AbstractNode>> ipc = node.getIncomingPostConditions();
int count = 0;
for (FlexoPostCondition<?, ?> postCondition : ipc) {
if (postCondition instanceof MessageEdge) {
continue;
}
count++;
}
return count;
}
private Vector<AutoLayoutNodePath> _buildPathsFrom(AutoLayoutNode beginNode) {
return _buildPathsFrom(beginNode, new AutoLayoutNodePath(), true);
}
private Vector<AutoLayoutNodePath> _buildPathsFrom(AutoLayoutNode node, AutoLayoutNodePath alreadyVisited, boolean onlyTerminals) {
alreadyVisited.add(node);
Vector<AutoLayoutNodePath> returned = new Vector<AutoLayoutNodePath>();
boolean atLeastAPathWasFound = false;
for (FlexoPostCondition<AbstractNode, AbstractNode> post : node.followingNodes.keySet()) {
AutoLayoutNode n = node.followingNodes.get(post);
if (!alreadyVisited.contains(n)) {
Vector<AutoLayoutNodePath> subPaths = _buildPathsFrom(n, alreadyVisited.clone(), onlyTerminals);
for (AutoLayoutNodePath path : subPaths) {
if (path.isTerminal() || !onlyTerminals) {
returned.add(new AutoLayoutNodePath(node, path));
atLeastAPathWasFound = true;
} else {
// System.out.println("Found a loop because of link :"+post);
}
}
}
}
if (!atLeastAPathWasFound) {
returned.add(new AutoLayoutNodePath(node));
}
return returned;
}
private Vector<FlexoPostCondition<AbstractNode, AbstractNode>> findUncoveredEdges() {
Vector<FlexoPostCondition<AbstractNode, AbstractNode>> returned = new Vector<FlexoPostCondition<AbstractNode, AbstractNode>>();
for (AutoLayoutNode n1 : nodeMap.values()) {
for (FlexoPostCondition<AbstractNode, AbstractNode> post : n1.followingNodes.keySet()) {
AutoLayoutNode n2 = n1.followingNodes.get(post);
// Is there a path containing n1 AND n2 ????
boolean isContained = false;
if (mainPath.contains(n1) && mainPath.contains(n2)) {
isContained = true;
}
if (!isContained) {
for (AutoLayoutNodePath secondaryPath : secondaryPaths) {
if (secondaryPath.contains(n1) && secondaryPath.contains(n2)) {
isContained = true;
break;
}
}
}
if (!isContained && !returned.contains(post)) {
returned.add(post);
}
}
}
return returned;
}
private void findLoopingAndShortcutEdges() {
loopingEdges = new Vector<FlexoPostCondition<AbstractNode, AbstractNode>>();
shortcutEdges = new Vector<FlexoPostCondition<AbstractNode, AbstractNode>>();
_findLoopingAndShortcutEdges(mainPath, loopingEdges, shortcutEdges);
for (AutoLayoutNodePath secondaryPath : secondaryPaths) {
_findLoopingAndShortcutEdges(secondaryPath, loopingEdges, shortcutEdges);
}
}
private void _findLoopingAndShortcutEdges(AutoLayoutNodePath path,
Vector<FlexoPostCondition<AbstractNode, AbstractNode>> loopingEdgesVector,
Vector<FlexoPostCondition<AbstractNode, AbstractNode>> shortcutEdgesVector) {
/*boolean debug = false;
if (path.name.equals("secondary0")) {
System.out.println("On debuggue looping edges pour "+path);
debug = true;
}*/
AutoLayoutNodePath currentPath = new AutoLayoutNodePath();
for (AutoLayoutNode node : path) {
currentPath.add(node);
// if (debug) System.out.println("********* currentPath="+currentPath);
for (FlexoPostCondition<AbstractNode, AbstractNode> post : node.followingNodes.keySet()) {
AutoLayoutNode n = node.followingNodes.get(post);
// if (debug) System.out.println("------ post="+node+" to "+n);
if (!currentPath.contains(n)) {
if (path.contains(n) && !(path.indexOf(n) == path.indexOf(node) + 1)) {
// This is a shortcut to a node after current one
if (!shortcutEdgesVector.contains(post)) {
shortcutEdgesVector.add(post);
}
} else {
Vector<AutoLayoutNodePath> subPaths = _buildPathsFrom(n, currentPath.clone(), false);
for (AutoLayoutNodePath subPath : subPaths) {
// if (debug) System.out.println("subPath="+subPath+" terminal="+subPath.isTerminal());
if (!subPath.isTerminal()) {
// Explicit loop: inter-path looping edge
FlexoPostCondition<AbstractNode, AbstractNode> lastPost = subPath.getLastPostcondition();
if (logger.isLoggable(Level.FINE)) {
logger.fine("Found a loop because of subPath :" + subPath + " post=" + lastPost);
}
if (lastPost != null && !loopingEdgesVector.contains(lastPost)) {
loopingEdgesVector.add(lastPost);
}
}
}
}
} else { // Explicit loop: intra-path looping edge
if (logger.isLoggable(Level.FINE)) {
logger.fine("Found a loop because of post :" + post);
}
if (!loopingEdgesVector.contains(post)) {
loopingEdgesVector.add(post);
}
}
}
}
// if (debug)System.out.println("FIN debug looping edges pour "+path);
}
public class AutoLayoutNode {
public PetriGraphNode node;
public Hashtable<FlexoPostCondition<AbstractNode, AbstractNode>, AutoLayoutNode> previousNodes;
public Hashtable<FlexoPostCondition<AbstractNode, AbstractNode>, AutoLayoutNode> followingNodes;
public FGEPoint proposedLocation = new FGEPoint(0, 0);
AutoLayoutNode(PetriGraphNode node) {
super();
this.node = node;
followingNodes = new Hashtable<FlexoPostCondition<AbstractNode, AbstractNode>, AutoLayoutNode>();
previousNodes = new Hashtable<FlexoPostCondition<AbstractNode, AbstractNode>, AutoLayoutNode>();
}
void addEdges(AbstractNode p) {
for (FlexoPostCondition<AbstractNode, AbstractNode> post : p.getOutgoingPostConditions()) {
AbstractNode next = post.getNextNode();
if (!nodeMap.containsKey(next)) {
if (next instanceof FlexoPortMap) {
next = ((FlexoPortMap) next).getSubProcessNode();
}
}
if (next != null && nodeMap.containsKey(next)) {
followingNodes.put(post, nodeMap.get(next));
nodeMap.get(next).previousNodes.put(post, this);
}
}
}
void debugNode() {
System.out.println("\nNode: " + node);
for (AutoLayoutNode aln : followingNodes.values()) {
System.out.println("> " + aln.node.getName());
}
for (AutoLayoutNode aln : previousNodes.values()) {
System.out.println("< " + aln.node.getName());
}
}
public int accessibilityFrom(AutoLayoutNode aln) {
int returned = accessibilityFrom(aln, new Vector<AutoLayoutNode>(), 1);
// if (returned != -1) System.out.println(" // "+aln.node.getName() + " < "+node.getName()+ "["+returned+"]");
return returned;
}
private int accessibilityFrom(AutoLayoutNode aln, Vector<AutoLayoutNode> allVisitedNodes, int level) {
// System.out.println(" ???? "+aln.node.getName() + " < "+node.getName());
allVisitedNodes.add(aln);
Vector<AutoLayoutNode> notVisitedNodes = new Vector<AutoLayoutNode>();
for (AutoLayoutNode next : aln.followingNodes.values()) {
if (next == this) {
return level;
}
if (!allVisitedNodes.contains(next)) {
notVisitedNodes.add(next);
}
}
int best = Integer.MAX_VALUE;
for (AutoLayoutNode testThis : notVisitedNodes) {
Vector<AutoLayoutNode> visitedNodes = new Vector<AutoLayoutNode>();
visitedNodes.addAll(allVisitedNodes);
int a = accessibilityFrom(testThis, visitedNodes, level + 1);
if (a > 0 && a < best) {
best = a;
}
}
if (best == Integer.MAX_VALUE) {
// System.out.println(" Cannot compare "+aln.node.getName() + " and "+node.getName());
return -1;
}
// System.out.println(" // "+aln.node.getName() + " < "+node.getName()+ "["+best+"]");
return best;
}
@Override
public String toString() {
return "ALN:" + node.getName();
}
}
public class AutoLayoutNodePath extends Vector<AutoLayoutNode> {
public String name;
public AutoLayoutNodePath startPath;
public AutoLayoutNodePath endPath;
public AutoLayoutNodePath() {
super();
}
public AutoLayoutNodePath(AutoLayoutNode... nodes) {
super();
for (AutoLayoutNode n : nodes) {
add(n);
}
}
public AutoLayoutNodePath(Vector<AutoLayoutNode> nodes) {
super();
addAll(nodes);
}
public AutoLayoutNodePath(AutoLayoutNode node, Vector<AutoLayoutNode> nodes) {
super();
add(node);
addAll(nodes);
}
@Override
public synchronized AutoLayoutNodePath clone() {
return (AutoLayoutNodePath) super.clone();
}
public boolean isTerminal() {
return lastElement().followingNodes.size() == 0;
}
public FlexoPostCondition<AbstractNode, AbstractNode> getLastPostcondition() {
if (size() > 1) {
AutoLayoutNode last = lastElement();
AutoLayoutNode previous = get(size() - 2);
for (FlexoPostCondition<AbstractNode, AbstractNode> post : previous.followingNodes.keySet()) {
if (previous.followingNodes.get(post) == last) {
return post;
}
}
}
return null;
}
@Override
public synchronized String toString() {
return name + ": " + super.toString() + " start_from:" + (startPath != null ? startPath.name : null) + " ends_to:"
+ (endPath != null ? endPath.name : null);
}
}
public static class AutoLayoutNodeComparator implements Comparator<AutoLayoutNode> {
@Override
public int compare(AutoLayoutNode n1, AutoLayoutNode n2) throws NotComparableException {
int a1 = n1.accessibilityFrom(n2);
int a2 = n2.accessibilityFrom(n1);
if (a1 > -1) {
if (a2 == -1) {
return 1;
} else {
throw new InterdependantNodesException();
}
} else {
if (a2 > -1) {
return -1;
} else {
throw new IndependantNodesException();
}
}
}
static abstract class NotComparableException extends RuntimeException {
};
static class InterdependantNodesException extends NotComparableException {
};
static class IndependantNodesException extends NotComparableException {
};
}
public Vector<AutoLayoutNode> getIsolatedNodes() {
return isolatedNodes;
}
public Vector<FlexoPostCondition<AbstractNode, AbstractNode>> getLoopingEdges() {
return loopingEdges;
}
public AutoLayoutNodePath getMainPath() {
return mainPath;
}
public Vector<AutoLayoutNodePath> getSecondaryPaths() {
return secondaryPaths;
}
}