/*
* (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.foundation.wkf.node;
/*
* FlexoNode.java
* Project WorkflowEditor
*
* Created by benoit on Mar 3, 2004
*/
import java.util.Enumeration;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.openflexo.foundation.DeletableObject;
import org.openflexo.foundation.Inspectors;
import org.openflexo.foundation.NameChanged;
import org.openflexo.foundation.validation.ParameteredFixProposal;
import org.openflexo.foundation.validation.Validable;
import org.openflexo.foundation.validation.ValidationIssue;
import org.openflexo.foundation.validation.ValidationRule;
import org.openflexo.foundation.validation.ValidationWarning;
import org.openflexo.foundation.wkf.FlexoLevel;
import org.openflexo.foundation.wkf.FlexoProcess;
import org.openflexo.foundation.wkf.LevelledObject;
import org.openflexo.foundation.wkf.WKFObject;
import org.openflexo.foundation.wkf.dm.NodeRemoved;
import org.openflexo.foundation.wkf.dm.PostInserted;
import org.openflexo.foundation.wkf.dm.PostRemoved;
import org.openflexo.foundation.wkf.edge.FlexoPostCondition;
import org.openflexo.foundation.wkf.edge.TokenEdge;
import org.openflexo.foundation.wkf.edge.WKFEdge;
import org.openflexo.foundation.wkf.ws.FlexoPortMap;
import org.openflexo.inspector.InspectableObject;
import org.openflexo.toolbox.ToolBox;
/**
* A AbstractNode is the base element representing a node in a PetriGraph. A AbstractNode is abstract and must be subsequently subclassed
* with FlexoNode, OperatorNode and FlexoPort
*
* @author sguerin
*/
public abstract class AbstractNode extends WKFNode implements InspectableObject, DeletableObject, LevelledObject {
private static final Logger logger = Logger.getLogger(AbstractNode.class.getPackage().getName());
// ==========================================================================
// ============================= Variables
// ==================================
// ==========================================================================
private String _nodeName = null;
// ==========================================================================
// ============================= Constructor
// ================================
// ==========================================================================
/**
* Default constructor
*/
public AbstractNode(FlexoProcess process) {
super(process);
_outgoingPostConditions = new Vector<FlexoPostCondition<AbstractNode, AbstractNode>>();
_incomingPostConditions = new Vector<FlexoPostCondition<AbstractNode, AbstractNode>>();
}
public AbstractNode getNode() {
return this;
}
public String getNiceName() {
String niceName = getName();
if (niceName != null && niceName.trim().length() > 0) {
return ToolBox.getJavaName(niceName);
}
return getNodeTypeName();
}
public String getNodeTypeName() {
String cls = getClass().getSimpleName();
if (cls.startsWith("Flexo")) {
cls = cls.substring("Flexo".length());
}
if (cls.endsWith("Node")) {
cls = cls.substring(0, cls.length() - "Node".length());
}
return cls;
}
// ==========================================================================
// ============================= InspectableObject
// ==========================
// ==========================================================================
/**
* Default inspector name: implemented in sub-classes !
*/
@Override
public String getInspectorName() {
return Inspectors.WKF.NODE_INSPECTOR;
}
/**
* Return a Vector of all embedded WKFObjects
*
* @return a Vector of WKFObject instances
*/
@Override
public Vector<WKFObject> getAllEmbeddedWKFObjects() {
Vector<WKFObject> returned = super.getAllEmbeddedWKFObjects();
returned.addAll(getIncomingPostConditions());
returned.addAll(getOutgoingPostConditions());
return returned;
}
protected void notifyPostInsertedToProcess(FlexoPostCondition<?, ?> post) {
if (getProcess() != null) {
getProcess().notifyPostInserted(post);
}
}
private Vector<FlexoPostCondition<AbstractNode, AbstractNode>> _outgoingPostConditions;
/**
* Stores the incoming post conditions, as a Vector of PreConditionEntry
*/
private Vector<FlexoPostCondition<AbstractNode, AbstractNode>> _incomingPostConditions;
/**
* Returns the incoming post conditions, as a Vector of PreConditionEntry
*/
public final Vector<FlexoPostCondition<AbstractNode, AbstractNode>> getIncomingPostConditions() {
return _incomingPostConditions;
}
public final void setIncomingPostConditions(Vector<FlexoPostCondition<AbstractNode, AbstractNode>> postConditions) {
_incomingPostConditions = postConditions;
}
public final void removeFromIncomingPostConditions(FlexoPostCondition post) {
if (_incomingPostConditions.contains(post)) {
_incomingPostConditions.remove(post);
post.setEndNode(null);
setChanged();
notifyObservers(new PostRemoved(post));
}
}
public final void addToIncomingPostConditions(FlexoPostCondition post) {
if (!_incomingPostConditions.contains(post) && post.getEndNodeClass().isAssignableFrom(getClass())
&& mayHaveIncomingPostConditions()) {
_incomingPostConditions.add(post);
post.setEndNode(this);
if (!isDeserializing()) {
setChanged();
notifyObservers(new PostInserted(post));
notifyPostInsertedToProcess(post);
}
}
}
public abstract boolean mayHaveIncomingPostConditions();
public final boolean hasIncomingPostConditions() {
return getIncomingPostConditions().size() > 0;
}
/**
* Returns all the start nodes of the incoming post-conditions of this node
*
* @return all the start nodes of the incoming post-conditions of this node.
*/
public Vector<AbstractNode> getFromPostconditionnedNodes() {
Vector<AbstractNode> v = new Vector<AbstractNode>();
for (FlexoPostCondition<AbstractNode, AbstractNode> a : getIncomingPostConditions()) {
if (!v.contains(a)) {
v.add(a.getStartNode());
}
}
return v;
}
@Override
public Vector<WKFEdge<?, ?>> getAllIncomingEdges() {
Vector<WKFEdge<?, ?>> v = super.getAllIncomingEdges();
v.addAll(getIncomingPostConditions());
return v;
}
// ==========================================================================
// ======================== Outgoing Post-conditions ======================
// ==========================================================================
/**
* @return Vector of FlexoPostCondition
*/
public Vector<FlexoPostCondition<AbstractNode, AbstractNode>> getOutgoingPostConditions() {
return _outgoingPostConditions;
}
public void setOutgoingPostConditions(Vector<FlexoPostCondition<AbstractNode, AbstractNode>> aVector) {
_outgoingPostConditions = aVector;
}
public void addToOutgoingPostConditions(FlexoPostCondition post) {
if (!_outgoingPostConditions.contains(post) && post.getStartNodeClass().isAssignableFrom(getClass())
&& mayHaveOutgoingPostConditions()) {
_outgoingPostConditions.add(post);
post.setStartNode(this);
if (!isDeserializing()) {
setChanged();
notifyObservers(new PostInserted(post));
notifyPostInsertedToProcess(post);
}
}
}
public void removeFromOutgoingPostConditions(FlexoPostCondition post) {
if (_outgoingPostConditions.contains(post)) {
_outgoingPostConditions.remove(post);
post.setStartNode(null);
setChanged();
notifyObservers(new PostRemoved(post));
}
}
public abstract boolean mayHaveOutgoingPostConditions();
public final boolean hasOutgoingPostConditions() {
return getOutgoingPostConditions().size() > 0;
}
/**
* Returns all the end nodes of the outgoing post-conditions of this node
*
* @return all the end nodes of the outgoing post-conditions of this node.
*/
public Vector<AbstractNode> getToPostconditionnedNodes() {
Vector<AbstractNode> v = new Vector<AbstractNode>();
for (FlexoPostCondition<AbstractNode, AbstractNode> a : getOutgoingPostConditions()) {
if (!v.contains(a)) {
v.add(a.getEndNode());
}
}
return v;
}
@Override
public Vector<WKFEdge<?, ?>> getAllOutgoingEdges() {
Vector<WKFEdge<?, ?>> v = super.getAllOutgoingEdges();
v.addAll(getOutgoingPostConditions());
return v;
}
// ==========================================================================
// ================================= Delete ===============================
// ==========================================================================
@Override
public void delete() {
Enumeration<FlexoPostCondition> en = new Vector<FlexoPostCondition>(_incomingPostConditions).elements();
while (en.hasMoreElements()) {
en.nextElement().delete();
}
en = new Vector<FlexoPostCondition>(_outgoingPostConditions).elements();
while (en.hasMoreElements()) {
en.nextElement().delete();
}
setChanged();
notifyObservers(new NodeRemoved(this));
super.delete();
}
/**
* Build and return a vector of all the objects that will be deleted during this deletion
*
* @param aVector
* of DeletableObject
*/
@Override
public Vector<WKFObject> getAllEmbeddedDeleted() {
return getAllEmbeddedWKFObjects();
}
@Override
public void setName(String aName) {
String oldValue = getName();
if (oldValue == null || !oldValue.equals(aName)) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("Name was: " + oldValue + " values now: " + aName);
}
setNodeName(aName);
setChanged();
notifyObservers(new NameChanged(oldValue, aName));
}
}
@Override
public String getName() {
return getNodeName();
}
public Vector<AbstractNode> nodesWithSameName() {
Vector<AbstractNode> returned = new Vector<AbstractNode>();
Vector<AbstractNode> allNodes = getProcess().getAllAbstractNodes();
for (Enumeration<AbstractNode> e = allNodes.elements(); e.hasMoreElements();) {
AbstractNode node = e.nextElement();
if (node != this && node.getFullyQualifiedName().equalsIgnoreCase(getFullyQualifiedName())) {
returned.add(node);
}
}
return returned;
}
public boolean isNameAmbigous() {
return nodesWithSameName().size() > 0;
}
public String findNextNonAmbigousName() {
return getProcess().findNextNonAmbigousNameForNode(getName(), this);
}
public abstract String getDefaultName();
public String getNodeName() {
return _nodeName;
}
public void setNodeName(String aName) {
_nodeName = aName;
}
@Override
public boolean isNodeValid() {
return getProcess() != null;
}
/**
* default implementation is false. overriden for OperatorNode
*
* @return false
*/
public boolean isOperatorNode() {
return false;
}
/**
* This attribute isn't supposed to be changed after node creation. Activity nodes are level ACTIVITY. Operations are level OPERATION.
* Actions are level ACTION. Other specific node can exist at level 3 or more.
*
* @return the node level.
*/
@Override
public abstract FlexoLevel getLevel();
@Override
public String toString() {
return getFullyQualifiedName();
}
@Override
public abstract String getFullyQualifiedName();
public static String formattedString(String s) {
if (s != null) {
s = s.replaceAll("\\s*", "");
}
return s;
}
// ==========================================================================
// ============================= Validation
// =================================
// ==========================================================================
public static class NodeShouldHaveNonAmbigousName extends ValidationRule<NodeShouldHaveNonAmbigousName, AbstractNode> {
public NodeShouldHaveNonAmbigousName() {
super(AbstractNode.class, "node_should_have_non_ambigous_name");
}
@Override
public ValidationIssue<NodeShouldHaveNonAmbigousName, AbstractNode> applyValidation(final AbstractNode node) {
Vector<? extends Validable> nodesWithSameName = node.nodesWithSameName();
if (nodesWithSameName.size() > 0) {
ValidationWarning<NodeShouldHaveNonAmbigousName, AbstractNode> warning = new ValidationWarning<NodeShouldHaveNonAmbigousName, AbstractNode>(
this, node, "node_($object.name)_has_ambigous_name");
warning.addToRelatedValidableObjects(nodesWithSameName);
warning.addToFixProposals(new RenameThisNode(node));
return warning;
}
if (node.getProcess() != null && node.getProcess().getName().equalsIgnoreCase(node.getName())) {
ValidationWarning<NodeShouldHaveNonAmbigousName, AbstractNode> warning = new ValidationWarning<NodeShouldHaveNonAmbigousName, AbstractNode>(
this, node, "node_($object.name)_has_ambigous_name");
warning.addToRelatedValidableObjects(node.getProcess());
warning.addToFixProposals(new RenameThisNode(node));
return warning;
}
return null;
}
public class RenameThisNode extends ParameteredFixProposal<NodeShouldHaveNonAmbigousName, AbstractNode> {
public RenameThisNode(AbstractNode node) {
super("rename_this_node", "newName", "enter_a_non_ambigous_name", node.findNextNonAmbigousName());
}
@Override
protected void fixAction() {
String newName = (String) getValueForParameter("newName");
getObject().setName(newName);
}
}
}
public static class NodeCannotHaveMoreThanOneDefaultOutgoingTokenEdge extends
ValidationRule<NodeCannotHaveMoreThanOneDefaultOutgoingTokenEdge, AbstractNode> {
public NodeCannotHaveMoreThanOneDefaultOutgoingTokenEdge() {
super(AbstractNode.class, "node_cannot_have_more_than_one_default_outgoing_token_edge");
}
@Override
public ValidationIssue<NodeCannotHaveMoreThanOneDefaultOutgoingTokenEdge, AbstractNode> applyValidation(final AbstractNode node) {
boolean hasAlreadyOne = false;
for (FlexoPostCondition p : node.getOutgoingPostConditions()) {
if (p instanceof TokenEdge && p.getIsDefaultFlow()) {
if (hasAlreadyOne) {
ValidationWarning<NodeCannotHaveMoreThanOneDefaultOutgoingTokenEdge, AbstractNode> warning = new ValidationWarning<NodeCannotHaveMoreThanOneDefaultOutgoingTokenEdge, AbstractNode>(
this, node, "node_($object.name)_has_more_than_one_default_outgoing_edge");
return warning;
} else {
hasAlreadyOne = true;
}
}
}
return null;
}
}
public static class NodeWithConditionalEdgeOrDefaultEdgeMustHaveMoreThanOneEdge extends
ValidationRule<NodeWithConditionalEdgeOrDefaultEdgeMustHaveMoreThanOneEdge, AbstractNode> {
public NodeWithConditionalEdgeOrDefaultEdgeMustHaveMoreThanOneEdge() {
super(AbstractNode.class, "node_with_conditional_edge_or_default_edge_must_have_more_than_one_edge");
}
@Override
public ValidationIssue<NodeWithConditionalEdgeOrDefaultEdgeMustHaveMoreThanOneEdge, AbstractNode> applyValidation(
final AbstractNode node) {
int defaultCount = 0;
int conditionalCount = 0;
int regular = 0;
Vector<FlexoPostCondition<?, ?>> listOfAllPostToConsider = new Vector<FlexoPostCondition<?, ?>>();
listOfAllPostToConsider.addAll(node.getOutgoingPostConditions());
if (node instanceof ActivityNode || node instanceof OperationNode) {
FlexoNode startNode = (FlexoNode) node;
if (startNode.isEndNode()) {
// try to find other brother end node with outgoing post
for (PetriGraphNode brotherNode : startNode.getParentPetriGraph().getNodes()) {
if (brotherNode instanceof FlexoNode) {
if (((FlexoNode) brotherNode).isEndNode() && brotherNode != startNode) {
return null;
}
}
}
if (startNode.getParentPetriGraph().getContainer() instanceof SubProcessNode) {
SubProcessNode sub = (SubProcessNode) startNode.getParentPetriGraph().getContainer();
if (sub.getPortMapRegistery() != null && sub.getPortMapRegistery().getAllOutPortmaps().size() > 0) {
return null;
}
}
}
} else if (node instanceof FlexoPortMap && ((FlexoPortMap) node).isOutputPort()) {
SubProcessNode sub = ((FlexoPortMap) node).getSubProcessNode();
if (sub.getPortMapRegistery().getAllOutPortmaps().size() > 0) {
return null;
}
if (sub.getOperationPetriGraph() != null && sub.getOperationPetriGraph().getAllEndNodes().size() > 0) {
return null;
}
}
for (FlexoPostCondition p : listOfAllPostToConsider) {
if (p.getIsDefaultFlow()) {
defaultCount++;
} else if (p.getIsConditional()) {
conditionalCount++;
} else {
regular++;
}
}
if (defaultCount > 0 && regular + conditionalCount == 0) {
if (node instanceof OperatorNode && ((OperatorNode) node).isExclusiveGateway()) {
return null; // There is a rule on the post conditions to prevent this already!
} else {
ValidationWarning<NodeWithConditionalEdgeOrDefaultEdgeMustHaveMoreThanOneEdge, AbstractNode> warning = new ValidationWarning<NodeWithConditionalEdgeOrDefaultEdgeMustHaveMoreThanOneEdge, AbstractNode>(
this, node, "node_($object.name)_has_one_default_outgoing_edge_should_have_at_least_another_outgoing_edge");
return warning;
}
}
if (conditionalCount == 1 && regular == 0 && defaultCount == 0 && !listOfAllPostToConsider.firstElement().mustBeConditional()) {
if (node instanceof OperatorNode && ((OperatorNode) node).isExclusiveGateway()) {
return null; // There is a rule on the post conditions to prevent this already!
} else {
ValidationWarning<NodeWithConditionalEdgeOrDefaultEdgeMustHaveMoreThanOneEdge, AbstractNode> warning = new ValidationWarning<NodeWithConditionalEdgeOrDefaultEdgeMustHaveMoreThanOneEdge, AbstractNode>(
this, node, "node_($object.name)_has_one_conditional_outgoing_edge_should_have_at_least_another_outgoing_edge");
return warning;
}
}
return null;
}
}
public static class NodeWithDefaultFlowMustHaveConditionOneOtherEdge extends
ValidationRule<NodeWithDefaultFlowMustHaveConditionOneOtherEdge, AbstractNode> {
public NodeWithDefaultFlowMustHaveConditionOneOtherEdge() {
super(AbstractNode.class, "node_cannot_have_more_than_one_default_outgoing_token_edge");
}
@Override
public ValidationIssue<NodeWithDefaultFlowMustHaveConditionOneOtherEdge, AbstractNode> applyValidation(final AbstractNode node) {
int defaultCount = 0;
int conditionalCount = 0;
int totalCount = 0;
for (FlexoPostCondition p : node.getOutgoingPostConditions()) {
if (p instanceof TokenEdge) {
totalCount++;
if (p.getIsDefaultFlow()) {
defaultCount++;
} else if (p.getIsConditional()) {
conditionalCount++;
}
}
}
if (defaultCount > 0 && totalCount != defaultCount + conditionalCount) {
if (node instanceof OperatorNode && ((OperatorNode) node).isExclusiveGateway()) {
return null; // There is a rule on the post conditions to prevent this already!
} else {
ValidationWarning<NodeWithDefaultFlowMustHaveConditionOneOtherEdge, AbstractNode> warning = new ValidationWarning<NodeWithDefaultFlowMustHaveConditionOneOtherEdge, AbstractNode>(
this, node, "node_($object.name)_has_one_default_outgoing_flow_and_so_other_edges_must_be_conditionnal");
return warning;
}
}
return null;
}
}
}