/**
* Copyright (C) 2011 BonitaSoft S.A.
* BonitaSoft, 31 rue Gustave Eiffel - 38000 Grenoble
* This library is free software; you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Foundation
* version 2.1 of the License.
* This library 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 Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
* Floor, Boston, MA 02110-1301, USA.
**/
package org.bonitasoft.simulation.iteration;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bonitasoft.simulation.iteration.IterationNode.JoinType;
import org.bonitasoft.simulation.iteration.IterationNode.SplitType;
/**
* @author Nicolas Chabanoles
*
*/
public final class IterationDetection {
private static final Logger LOG = Logger.getLogger(IterationDetection.class.getName());
public static final String LINE_SEPARATOR = System.getProperty("line.separator");
private IterationDetection() { }
/*
* Implements a deep first search to list all the paths in the process.<br/>
* DFS (graph G, node s) {
* Mark(s);
* For each node n : Neighbor(s) do
* if !marked(n) then
* DFS(G,n);
* End-if
* end
* }
*/
public static SortedSet<IterationDescriptor> findIterations(final IterationProcess inProcess) {
final SortedMap<String, IterationNode> allProcessNodes = inProcess.getNodes();
final SortedSet<IterationNode> allPorcessNodesSorted = new TreeSet<IterationNode>(allProcessNodes.values());
// store path during recursive search
final List<IterationNode> path = new ArrayList<IterationNode>(allProcessNodes.size());
// store visited nodes
final SortedSet<IterationNode> visitedNodes = new TreeSet<IterationNode>();
final SortedSet<IterationDescriptor> iterationDescriptors = new TreeSet<IterationDescriptor>();
for (IterationNode sourceNode : allPorcessNodesSorted) {
SortedSet<IterationTransition> incomingTransitions = new TreeSet<IterationTransition>(sourceNode.getIncomingTransitions());
final SortedSet<IterationDescriptor> temporaryIterationDescriptors = new TreeSet<IterationDescriptor>();
for (IterationTransition transition : incomingTransitions) {
// List all paths form sourceNode to sourceNode, i.e. find all cycles.
// We need to take care not to looping forever, that is why we need to remember already visited nodes.
listCycles(inProcess, sourceNode, transition.getSource(), path, visitedNodes, temporaryIterationDescriptors);
iterationDescriptors.addAll(temporaryIterationDescriptors);
}
}
return iterationDescriptors;
}
private static void listCycles(IterationProcess inProcess,
IterationNode sourceNode, IterationNode targetNode, List<IterationNode> path, SortedSet<IterationNode> visitedNodes, SortedSet<IterationDescriptor> iterationDescriptors) {
// Add node into the path.
path.add(sourceNode);
// SourceNode == targetNode --> stop recursive search
if (sourceNode == null || targetNode == null || sourceNode.getName().equals(targetNode.getName())) {
final IterationDescriptor descriptor = buildIterationDescriptor(inProcess, path);
iterationDescriptors.add(descriptor) ;
path.remove(sourceNode);
return;
}
visitedNodes.add(sourceNode); // mark node
// search recursively...
SortedSet<IterationTransition> outgoingTransitions = new TreeSet<IterationTransition>(sourceNode.getOutgoingTransitions());
for (IterationTransition transition : outgoingTransitions) {
if(!visitedNodes.contains(transition.getDestination())) {
// do not follow transitions that points to a visited node to avoid looping infinitely.
listCycles(inProcess, transition.getDestination(), targetNode, path, visitedNodes, iterationDescriptors);
}
}
visitedNodes.remove(sourceNode); // un-mark node
path.remove(sourceNode);
}
/*
* Build an iteration descriptor based on nodes in path.
* Entry nodes: nodes that have an incoming transition from a node that do not belong to the cycle.
* Exit nodes: nodes that have an outgoing transition to a node that do not belong to the cycle.
* Other nodes: nodes that have an incoming transition from a node that do not belong to the cycle.
*/
private static IterationDescriptor buildIterationDescriptor(
final IterationProcess inProcess, List<IterationNode> path) {
final List<String> nodesInPath = getNodeNames(path);
final SortedSet<String> entryNodes = new TreeSet<String>();
final SortedSet<String> exitNodes = new TreeSet<String>();
final SortedSet<String> otherNodes = new TreeSet<String>();
boolean hasEntryPointXor = false;
for (final IterationNode node : path) {
final Set<IterationTransition> incomingTransitions = node.getIncomingTransitions();
final Set<IterationTransition> outgoingTransitions = node.getOutgoingTransitions();
final String nodeName = node.getName();
for (IterationTransition transition : incomingTransitions) {
if (!path.contains(transition.getSource())) {
hasEntryPointXor |= checkEntryNodeIntegrity(node);
entryNodes.add(nodeName);
}
}
for (IterationTransition transition : outgoingTransitions) {
if (!path.contains(transition.getDestination())) {
exitNodes.add(nodeName);
final SplitType splitType = node.getSplitType();
// Only allow XOR split for exit nodes
if (!SplitType.XOR.equals(splitType) && LOG.isLoggable(Level.SEVERE)) {
LOG.severe("Potential issue in iteration : " + nodeName + " is an exit node for cycle "
+ nodesInPath + "." + LINE_SEPARATOR
+ "Split type of this node is " + splitType + " but only XOR is supported." + LINE_SEPARATOR
+ "An exception will be thrown at runtime if more than one transition is enabled at the same time.");
}
}
}
if (!entryNodes.contains(nodeName) && !exitNodes.contains(nodeName)) {
otherNodes.add(nodeName);
}
}
final IterationDescriptor itDescr = new IterationDescriptor(otherNodes, entryNodes, exitNodes);
checkCycleIntegrity(hasEntryPointXor, itDescr);
return itDescr;
}
private static List<String> getNodeNames(final List<IterationNode> path) {
final List<String> nodeNames = new ArrayList<String>();
for (final IterationNode iterationNode : path) {
nodeNames.add(iterationNode.getName());
}
return nodeNames;
}
private static void checkCycleIntegrity(boolean hasEntryPointXor, final IterationDescriptor itDescr) throws RuntimeException {
if (itDescr.getEntryNodes().size() == 0) {
throw new RuntimeException("Error in cycle detection : cycle " + itDescr + " has no start node");
}
if (!hasEntryPointXor) {
throw new RuntimeException("Error in cycle detection : cycle " + itDescr + " has no start node with a XOR join. Process execution can never enter this cycle.");
}
}
private static boolean checkEntryNodeIntegrity(IterationNode sourceNode) throws RuntimeException {
final JoinType joinType = sourceNode.getJoinType();
if (JoinType.AND.equals(joinType)) {
throw new RuntimeException("Error in cycle detection : start node " + sourceNode.getName() + " has a AND join. This is not allowed.");
}
return JoinType.XOR.equals(joinType);
}
}