package sushi.bpmn.decomposition;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.jbpt.algo.tree.rpst.IRPSTNode;
import org.jbpt.algo.tree.rpst.RPST;
import org.jbpt.algo.tree.tctree.TCType;
import sushi.bpmn.DirectedBPMNEdge;
import sushi.bpmn.MultiDirectedBPMNGraph;
import sushi.bpmn.element.AbstractBPMNElement;
import sushi.bpmn.element.BPMNProcess;
import sushi.bpmn.element.BPMNSubProcess;
import sushi.event.collection.SushiTree;
/**
* This class constructs a RPST and a {@link SushiTree} with the {@link AbstractBPMNElement}s of the process derived from the RPST.
* @author micha
*
*/
public class RPSTBuilder {
private BPMNProcess process;
/**
* Graph derived from the BPMNProcess.
*/
private MultiDirectedBPMNGraph graph;
/**
* The RPST tree.
*/
private RPST<DirectedBPMNEdge, AbstractBPMNElement> rpst;
/**
* Tree of the RPST nodes. RPST nodes are edges of the original process.
*/
private SushiTree<IRPSTNode<DirectedBPMNEdge, AbstractBPMNElement>> rpstNodesTree;
/**
* Tree, which contains the BPMNProcess elements in the decomposition hierarchy of the RPST.
*/
private SushiTree<AbstractBPMNElement> processDecompositionTree;
/**
* @param process
*/
public RPSTBuilder(BPMNProcess process) {
this.process = (BPMNProcess) process.clone();
this.process = BPMNProcessPreprocessor.structureProcess(this.process);
this.graph = convertBPMNToGraph(this.process);
this.rpst = new RPST<DirectedBPMNEdge,AbstractBPMNElement>(graph);
this.buildRPSTNodesTree();
this.buildProcessDecompositionTree();
PatternUtil.determinePatternForTreeComponents(processDecompositionTree);
}
/**
* Builds a {@link SushiTree} from the components of the RPST for better handling.
*/
private void buildRPSTNodesTree() {
IRPSTNode<DirectedBPMNEdge, AbstractBPMNElement> rootNode = rpst.getRoot();
rpstNodesTree = new SushiTree<IRPSTNode<DirectedBPMNEdge, AbstractBPMNElement>>();
addElementToRPSTNodesTree(rootNode, null);
}
/**
* Determines if a node in the RPST has children.
* @param node
* @param rpst
* @return
*/
private boolean hasChildren(IRPSTNode<DirectedBPMNEdge, AbstractBPMNElement> node, RPST<DirectedBPMNEdge,AbstractBPMNElement> rpst){
return !rpst.getChildren(node).isEmpty();
}
private void addElementToRPSTNodesTree(IRPSTNode<DirectedBPMNEdge, AbstractBPMNElement> element, IRPSTNode<DirectedBPMNEdge, AbstractBPMNElement> parent){
rpstNodesTree.addChild(parent, element);
if(hasChildren(element, rpst)){
for(IRPSTNode<DirectedBPMNEdge, AbstractBPMNElement> child : rpst.getChildren(element)){
addElementToRPSTNodesTree(child, element);
}
}
}
/**
* This method converts an node-oriented {@link BPMNProcess} to an edge-oriented {@link MultiDirectedBPMNGraph}.
* @param process
* @return
*/
private MultiDirectedBPMNGraph convertBPMNToGraph(BPMNProcess process){
MultiDirectedBPMNGraph g = new MultiDirectedBPMNGraph();
for(AbstractBPMNElement element : process.getBPMNElementsWithOutSequenceFlows()){
for(AbstractBPMNElement successor : element.getSuccessors()){
g.addEdge(element,successor);
}
}
return g;
}
/**
* Builds the processDecompositionTree from the RPST.
*/
private void buildProcessDecompositionTree() {
processDecompositionTree = new SushiTree<AbstractBPMNElement>();
addElementsToProcessDecompositionTree(rpstNodesTree, rpstNodesTree.getRootElements(), null, null);
//Children für Components im ProcessDecompositionTree setzen
for(AbstractBPMNElement rootElement : processDecompositionTree.getRootElements()){
setChildrenForProcessDecompositionTreeElements(rootElement);
}
determineEntryAndExitPoints();
}
private void setChildrenForProcessDecompositionTreeElements(AbstractBPMNElement element) {
if(element instanceof Component && processDecompositionTree.hasChildren(element)){
Component component = (Component) element;
component.addChildren(processDecompositionTree.getChildren(component));
for(AbstractBPMNElement child : processDecompositionTree.getChildren(component)){
setChildrenForProcessDecompositionTreeElements(child);
}
}
}
/**
* Sets the entry and exit point for every element of the tree, that is a component.
*/
private void determineEntryAndExitPoints() {
for(AbstractBPMNElement element : processDecompositionTree.getElements()){
if(element instanceof Component){
Component component = (Component) element;
component.setEntryPoint(determineEntryPoint(component));
component.setExitPoint(determineExitPoint(component));
}
}
}
private AbstractBPMNElement determineEntryPoint(Component component){
AbstractBPMNElement sourceElement = component.getSourceElement();
//TODO: Testen: Kann noch falsch sein bei Schleifen
AbstractBPMNElement entryPoint = null;
if(!sourceElement.getPredecessors().isEmpty()){
//Direkte Vorgänger des SourceElement
Set<AbstractBPMNElement> predecessors = new HashSet<AbstractBPMNElement>(sourceElement.getPredecessors());
//Alle Childs des aktuellen Parent
Set<AbstractBPMNElement> parentIndirectChildElements = processDecompositionTree.getIndirectChildren(processDecompositionTree.getParent(component));
//Alle Childs der aktuellen Component
Set<AbstractBPMNElement> componentChildren = processDecompositionTree.getIndirectChildren(component);
//Sinnvolle Entrypoints sind Vorgänger der Component, die nicht Kinder der Component sind
parentIndirectChildElements.removeAll(componentChildren);
predecessors.retainAll(parentIndirectChildElements);
if(!predecessors.isEmpty()){
entryPoint = predecessors.iterator().next();
}
}
return entryPoint;
}
private AbstractBPMNElement determineExitPoint(Component component){
//TODO: Testen: Kann noch falsch sein bei Schleifen
AbstractBPMNElement sinkElement = component.getSinkElement();
AbstractBPMNElement exitPoint = null;
if(!sinkElement.getSuccessors().isEmpty()){
//Direkte Nachfolger des SinkElements
Set<AbstractBPMNElement> successors = new HashSet<AbstractBPMNElement>(sinkElement.getSuccessors());
//Alle Childs des aktuellen Parent
Set<AbstractBPMNElement> parentIndirectChildElements = processDecompositionTree.getIndirectChildren(processDecompositionTree.getParent(component));
//Alle Childs der aktuellen Component
Set<AbstractBPMNElement> componentChildren = processDecompositionTree.getIndirectChildren(component);
//Sinnvolle Exitpoints sind Nachfolger der Component, die nicht Kinder der Component sind
parentIndirectChildElements.removeAll(componentChildren);
successors.retainAll(parentIndirectChildElements);
if(!successors.isEmpty()){
exitPoint = successors.iterator().next();
}
}
return exitPoint;
}
/**
* Adds the elements of the RPSTNodesTree to the ProcessDecompositionTree with the specified parent elements.
* @param rpstNodesTree
* @param rpstNodesTreeElements
* @param rpstNodesTreeParentElement
* @param processDecompositionTreeParent
*/
private void addElementsToProcessDecompositionTree(
SushiTree<IRPSTNode<DirectedBPMNEdge, AbstractBPMNElement>> rpstNodesTree,
Collection<IRPSTNode<DirectedBPMNEdge, AbstractBPMNElement>> rpstNodesTreeElements,
IRPSTNode<DirectedBPMNEdge, AbstractBPMNElement> rpstNodesTreeParentElement,
AbstractBPMNElement processDecompositionTreeParent){
Set<IRPSTNode<DirectedBPMNEdge, AbstractBPMNElement>> trivialElements = new HashSet<IRPSTNode<DirectedBPMNEdge, AbstractBPMNElement>>();
for(IRPSTNode<DirectedBPMNEdge, AbstractBPMNElement> rpstNodesTreeElement: rpstNodesTreeElements){
if(rpstNodesTreeElement.getType() == TCType.TRIVIAL){
trivialElements.add(rpstNodesTreeElement);
continue;
}
AbstractBPMNElement createdElement = null;
AbstractBPMNElement sourceElement = rpstNodesTreeElement.getEntry();
AbstractBPMNElement sinkElement = rpstNodesTreeElement.getExit();
if(rpstNodesTreeElement.getType() == TCType.POLYGON){
createdElement = new PolygonComponent(null, sourceElement, null, sinkElement);
createdElement.setName(rpstNodesTreeElement.getName());
} else if(rpstNodesTreeElement.getType() == TCType.BOND){
createdElement = new BondComponent(null, sourceElement, null, sinkElement);
createdElement.setName(rpstNodesTreeElement.getName());
} else if(rpstNodesTreeElement.getType() == TCType.RIGID){
throw new RuntimeException("RIGIDs sind noch nicht durchdacht ;)");
}
processDecompositionTree.addChild(processDecompositionTreeParent, createdElement);
if(rpstNodesTree.hasChildren(rpstNodesTreeElement)){
addElementsToProcessDecompositionTree(rpstNodesTree, rpstNodesTree.getChildren(rpstNodesTreeElement), rpstNodesTreeElement, createdElement);
}
}
if(!trivialElements.isEmpty()){
addTrivialElementsToProcessDecompositionTree(trivialElements, rpstNodesTreeParentElement, processDecompositionTreeParent);
}
}
private void addTrivialElementsToProcessDecompositionTree(Set<IRPSTNode<DirectedBPMNEdge, AbstractBPMNElement>> trivialElements, IRPSTNode<DirectedBPMNEdge, AbstractBPMNElement> rpstNodesTreeParentElement, AbstractBPMNElement processDecompositionTreeParent) {
Map<AbstractBPMNElement, Integer> elementsMap = new HashMap<AbstractBPMNElement, Integer>();
elementsMap.put(rpstNodesTreeParentElement.getEntry(), 1);
elementsMap.put(rpstNodesTreeParentElement.getExit(), 1);
for(IRPSTNode<DirectedBPMNEdge, AbstractBPMNElement> rpstNodesTreeElement : trivialElements){
for(DirectedBPMNEdge directedBPMNEdge : rpstNodesTreeElement.getFragment()){
for(AbstractBPMNElement element : directedBPMNEdge.getVertices()){
if(!elementsMap.containsKey(element)){
elementsMap.put(element, 1);
} else {
elementsMap.put(element, elementsMap.get(element) + 1);
}
}
}
}
for(AbstractBPMNElement element : elementsMap.keySet()){
if(elementsMap.get(element) > 1){
if(!(element instanceof BPMNSubProcess)){
processDecompositionTree.addChild(processDecompositionTreeParent, element);
} else {
//TODO: in eigene Methode auslagern
//SubProcess zerlegt in den RPST
BPMNSubProcess subProcess = (BPMNSubProcess) element;
RPSTBuilder subProcessRPST = new RPSTBuilder(subProcess);
//Sollte möglich sein, da im PreProcessingStep beim RPST bauen alle Start- und Endevents vereinigt werden
AbstractBPMNElement sourceElement = subProcessRPST.getProcess().getStartEvent();
AbstractBPMNElement sinkElement = subProcessRPST.getProcess().getEndEvent();
AbstractBPMNElement entryPoint = getPredecessorFromEdges(subProcess, trivialElements);
if(entryPoint == null){ /* dann ist Element erstes Element der Component und hat nur einen Vorgänger */
if(subProcess.getPredecessors().size() > 1){
throw new RuntimeException("Fehler in der Logik!");
} else {
entryPoint = (!subProcess.getPredecessors().isEmpty()) ? subProcess.getPredecessors().iterator().next() : null;
}
}
AbstractBPMNElement exitPoint = getSuccessorFromEdges(subProcess, trivialElements);
if(exitPoint == null){ /* dann ist Element letztes Element der Component und hat nur einen Nachfolger */
if(subProcess.getSuccessors().size() > 1){
throw new RuntimeException("Fehler in der Logik!");
} else {
exitPoint = (!subProcess.getSuccessors().isEmpty()) ? subProcess.getSuccessors().iterator().next() : null;
}
}
//TODO: Vielleicht keine eigene SubProcess-Component, sondern eher Component-Eigenschaft SubProcess hinzufügen?
SubProcessComponent subProcessComponent = new SubProcessComponent(entryPoint, sourceElement, exitPoint, sinkElement);
subProcessComponent.setSubProcess(subProcess);
//RPST des SubProcess unter der SubProcessComponent einhängen
if(processDecompositionTreeParent instanceof Component){
Component parentComponent = (Component) processDecompositionTreeParent;
if(parentComponent.getSourceElement().equals(subProcess)){
parentComponent.setSourceElement(subProcessComponent);
} else if(parentComponent.getSinkElement().equals(subProcess)){
parentComponent.setSinkElement(subProcessComponent);
}
}
processDecompositionTree.addChild(processDecompositionTreeParent, subProcessComponent);
addElementsToProcessDecompositionTree(subProcessRPST.getRPSTNodesTree(), subProcessRPST.getRPSTNodesTree().getRootElements(), null, subProcessComponent);
}
}
}
}
private AbstractBPMNElement getPredecessorFromEdges(AbstractBPMNElement element, Set<IRPSTNode<DirectedBPMNEdge, AbstractBPMNElement>> trivialElements) {
for(IRPSTNode<DirectedBPMNEdge, AbstractBPMNElement> rpstNodesTreeElement : trivialElements){
for(DirectedBPMNEdge directedBPMNEdge : rpstNodesTreeElement.getFragment()){
if(directedBPMNEdge.getTarget().equals(element)){
return directedBPMNEdge.getSource();
}
}
}
return null;
}
private AbstractBPMNElement getSuccessorFromEdges(AbstractBPMNElement element, Set<IRPSTNode<DirectedBPMNEdge, AbstractBPMNElement>> trivialElements) {
for(IRPSTNode<DirectedBPMNEdge, AbstractBPMNElement> rpstNodesTreeElement : trivialElements){
for(DirectedBPMNEdge directedBPMNEdge : rpstNodesTreeElement.getFragment()){
if(directedBPMNEdge.getSource().equals(element)){
return directedBPMNEdge.getTarget();
}
}
}
return null;
}
public BPMNProcess getProcess() {
return process;
}
public MultiDirectedBPMNGraph getGraph() {
return graph;
}
public RPST<DirectedBPMNEdge, AbstractBPMNElement> getRpst() {
return rpst;
}
public SushiTree<IRPSTNode<DirectedBPMNEdge, AbstractBPMNElement>> getRPSTNodesTree() {
return rpstNodesTree;
}
public SushiTree<AbstractBPMNElement> getProcessDecompositionTree() {
return processDecompositionTree;
}
}