/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This 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; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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 software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jbpm.pvm.impl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jbpm.PvmException;
import org.jbpm.pvm.Activity;
import org.jbpm.pvm.Node;
import org.jbpm.pvm.Transition;
import org.jbpm.wire.Descriptor;
/**
* @author Tom Baeyens
*/
public class NodeImpl extends CompositeElementImpl implements Node {
private static final long serialVersionUID = 1L;
protected ObjectReference<Activity> behaviourReference;
protected List<TransitionImpl> outgoingTransitions;
protected List<TransitionImpl> incomingTransitions;
protected TransitionImpl defaultTransition;
protected NodeImpl parentNode;
protected boolean isExecutionAsync;
protected boolean isSignalAsync;
protected boolean isLeaveAsync;
protected boolean isPreviousNeeded;
transient protected Map<String, TransitionImpl> outgoingTransitionsMap;
/**
* Use {@link ProcessDefinitionImpl#createNode()} or {@link NodeImpl#createNode()} instead.
*/
public NodeImpl() {
super();
}
// specialized node containment methods /////////////////////////////////////
public NodeImpl addNode(NodeImpl node) {
node.setParentNode(this);
super.addNode(node);
return node;
}
public Node findNode(String nodeName) {
if (nodeName==null) {
if (name==null) {
return this;
}
} else if (nodeName.equals(name)) {
return this;
}
return super.findNode(nodeName);
}
// leaving transitions //////////////////////////////////////////////////////
/**
* creates a transition from this node to the given
* destination node. Also the transition pointers to source and destination
* node will be set appropriatly.
* @throws NullPointerException if destination is null.
*/
public Transition createOutgoingTransition(NodeImpl destination) {
return createOutgoingTransition(destination, null);
}
/**
* creates a transition with the given name from this node to the given
* destination node. Also the transition pointers to source and destination
* node will be set appropriatly.
* @param transitionName may be null.
* @throws NullPointerException if destination is null.
*/
public TransitionImpl createOutgoingTransition(NodeImpl destination, String transitionName) {
// create a new transition
TransitionImpl transition = new TransitionImpl();
transition.setName(transitionName);
// wire it between the source and destination
addOutgoingTransition(transition);
if (destination!=null) {
destination.addIncomingTransition(transition);
}
// if there is no default transition yet
if (defaultTransition==null) {
// make this the default outgoing transition
defaultTransition = transition;
}
return transition;
}
/**
* adds the given transition as a leaving transition to this node.
* Also the source of the transition is set to this node.
* Adding a transition that is already contained in the leaving
* transitions has no effect.
* @return the added transition.
* @throws NullPointerException if transition is null.
*/
public Transition addOutgoingTransition(TransitionImpl transition) {
transition.setSource(this);
if (outgoingTransitions==null) {
outgoingTransitions = new ArrayList<TransitionImpl>();
}
if (! outgoingTransitions.contains(transition)) {
outgoingTransitions.add(transition);
}
outgoingTransitionsMap = null;
return transition;
}
/**
* removes the given transition from the leaving transitions.
* Also the transition's source will be nulled.
* This method will do nothing if the transition is null or if
* the given transition is not in the list of this node's leaving
* transitions.
* In case this is the transition that was in the
* outgoingTransitionsMap and another transition exists with the same
* name, that transition (the first) will be put in the
* outgoingTransitionsMap as a replacement for the removed transition.
* If the transition is actually removed from the list of
* leaving transitions, the transition's source will be nulled.
*/
public boolean removeOutgoingTransition(TransitionImpl transition) {
if ( (transition!=null)
&& (outgoingTransitions!=null)
) {
boolean isRemoved = outgoingTransitions.remove(transition);
if (isRemoved) {
transition.setSource(null);
if (outgoingTransitions.isEmpty()) {
outgoingTransitions = null;
}
outgoingTransitionsMap = null;
}
return isRemoved;
}
return false;
}
/** the first leaving transition with the given name or null of no
* such leaving transition exists.
*/
public TransitionImpl getOutgoingTransition(String transitionName) {
return (getOutgoingTransitionsMap()!=null ? outgoingTransitionsMap.get(transitionName) : null);
}
/** searches for the given transitionName in this node and then up the
* parent chain. Returns null if no such transition is found. */
public TransitionImpl findOutgoingTransition(String transitionName) {
TransitionImpl transition = getOutgoingTransition(transitionName);
if (transition!=null) {
return transition;
}
if (parentNode!=null) {
return parentNode.findOutgoingTransition(transitionName);
}
return null;
}
/** searches for the default transition in this node and then up the
* parent chain. Returns null if no such transition is found. */
public TransitionImpl findDefaultTransition() {
if (defaultTransition!=null) {
return defaultTransition;
}
if (parentNode!=null) {
return parentNode.findDefaultTransition();
}
return null;
}
/** the list of leaving transitions.
* Beware: the actual member is returned. No copy is made.
*/
public List<Transition> getOutgoingTransitions() {
return (List) outgoingTransitions;
}
/** indicates if a leaving transition with the given transitionName exists. */
public boolean hasOutgoingTransition(String transitionName) {
return (getOutgoingTransition(transitionName)!=null);
}
/** indicates if this node has leaving transitions */
public boolean hasOutgoingTransitions() {
return ((outgoingTransitions!=null) && (!outgoingTransitions.isEmpty()));
}
/** sets the outgoingTransitions to the given list of outgoingTransitions.
* A copy of the collection is made. Also the outgoingTransitionsMap will
* be updated and the source of all the transitions in the given list will
* be set to this node.
* In case there was a leaving transitions list present, these transition's
* source will be nulled.
*/
public void setOutgoingTransitions(List<TransitionImpl> outgoingTransitions) {
if (this.outgoingTransitions!=null) {
for (TransitionImpl removedTransition: this.outgoingTransitions) {
removedTransition.setSource(null);
}
}
if (outgoingTransitions!=null) {
this.outgoingTransitions = new ArrayList<TransitionImpl>(outgoingTransitions);
for (TransitionImpl addedTransition: outgoingTransitions) {
addedTransition.setSource(this);
}
} else {
this.outgoingTransitions = null;
}
this.outgoingTransitionsMap = null;
}
/** the leaving transitions, keyed by transition name. If a transition with
* the same name occurs mutltiple times, the first one is returned.
* Leaving transitions with a null value for their name are not included
* in the map.
* Beware: the actual member is returned. No copy is made.
*/
public Map<String, Transition> getOutgoingTransitionsMap() {
if(outgoingTransitionsMap == null){
this.outgoingTransitionsMap = getTransitionsMap(outgoingTransitions);
}
return (Map) outgoingTransitionsMap;
}
// arriving transitions /////////////////////////////////////////////////////
/**
* adds the given transition as an arriving transition to this node.
* Also the source of the transition is set to this node.
* @return the added transition.
* @throws NullPointerException if transition is null.
*/
public Transition addIncomingTransition(TransitionImpl transition) {
transition.setDestination(this);
if (incomingTransitions==null) {
incomingTransitions = new ArrayList<TransitionImpl>();
}
incomingTransitions.add(transition);
return transition;
}
/** removes the given transition if it is contained in the arriving
* transitions of this node. If this transition was actually removed,
* its destination pointer is nulled.
* @return true if a transition was removed.
*/
public boolean removeIncomingTransition(TransitionImpl transition) {
if ( (transition!=null)
&& (incomingTransitions!=null)
&& (incomingTransitions.remove(transition))
) {
transition.setDestination(null);
if (incomingTransitions.isEmpty()) {
incomingTransitions = null;
}
return true;
}
return false;
}
/** the list of arriving transitions.
* Beware: the actual member is returned. No copy is made.
*/
public List<Transition> getIncomingTransitions() {
return (List) incomingTransitions;
}
/** indicates if this node has arriving transitions */
public boolean hasIncomingTransitions() {
return ((incomingTransitions!=null) && (!incomingTransitions.isEmpty()));
}
/** sets the incomingTransitions to the given list of incomingTransitions.
* A copy of the collection is made. Also the destination of all the transitions
* in the given list will be set to this node.
* In case there was an arriving transitions list present, these transition's
* destination will be nulled.
*/
public void setIncomingTransitions(List<TransitionImpl> incomingTransitions) {
if (this.incomingTransitions!=null) {
for (TransitionImpl removedTransition: this.incomingTransitions) {
removedTransition.setDestination(null);
}
}
if (incomingTransitions!=null) {
this.incomingTransitions = new ArrayList<TransitionImpl>(incomingTransitions);
for (TransitionImpl addedTransition: incomingTransitions) {
addedTransition.setDestination(this);
}
} else {
this.incomingTransitions = null;
}
}
// behaviour ////////////////////////////////////////////////////////////////
/** sets the given activity as the behaviour for this node.
* An object reference for the given activity is created. */
public void setBehaviour(Activity activity) {
behaviourReference = new ObjectReference<Activity>(activity);
}
/** sets the activity that can be created from the given descriptor as the
* behaviour for this node. It is assumed that the descriptor will
* create an {@link Activity}
* An object reference for the given descriptor is created. */
public void setBehaviour(Descriptor descriptor) {
behaviourReference = new ObjectReference<Activity>(descriptor);
}
/** sets the expression behaviour for this node. The evaluation of the
* expression will replace the {@link Activity#execute(org.jbpm.pvm.Execution) Activity's execute method}.
* An object reference for the given descriptor is created. */
public void setBehaviour(String expression) {
behaviourReference = new ObjectReference<Activity>(expression);
}
public ObjectReference<Activity> getBehaviourReference() {
return behaviourReference;
}
public void setBehaviourReference(ObjectReference<Activity> behaviourReference) {
this.behaviourReference = behaviourReference;
}
public Activity getBehaviour() {
Activity behaviour = ( behaviourReference!=null ? behaviourReference.get() : null);
if (behaviour==null) {
throw new PvmException("no behaviour on "+this);
}
return behaviour;
}
// various helper methods ///////////////////////////////////////////////////
private static Map<String, TransitionImpl> getTransitionsMap(List<TransitionImpl> transitions) {
Map<String, TransitionImpl> map = null;
if (transitions!=null) {
map = new HashMap<String, TransitionImpl>();
for (TransitionImpl transition: transitions) {
if (! map.containsKey(transition.getName())) {
map.put(transition.getName(), transition);
}
}
}
return map;
}
static Map<String, NodeImpl> getNodesMap(List<NodeImpl> nodes) {
Map<String, NodeImpl> map = null;
if (nodes!=null) {
map = new HashMap<String, NodeImpl>();
for (NodeImpl node: nodes) {
if (node.getName()!=null) {
if (! map.containsKey(node.getName())) {
map.put(node.getName(), node);
}
}
}
}
return map;
}
public String toString() {
if (name!=null) return "node("+name+")";
if (dbid!=0) return "node("+dbid+")";
return "node("+System.identityHashCode(this)+")";
}
/** collects the full stack of parent in a list. This node is the
* first element in the chain. The process definition will be the last element.
* the chain will never be null. */
public List<ObservableElementImpl> getParentChain() {
List<ObservableElementImpl> chain = new ArrayList<ObservableElementImpl>();
ObservableElementImpl processElement = this;
while (processElement!=null) {
chain.add(processElement);
processElement = processElement.getParent();
}
return chain;
}
// getters and setters //////////////////////////////////////////////////////
public ObservableElementImpl getParent() {
return (parentNode!=null ? parentNode : processDefinition);
}
public String getName() {
return name;
}
public void setName(String name) {
// if there is no processDefinition associated with this node
if (processDefinition==null) {
// it s just a setter
this.name = name;
} else { // otherwise
// make sure the processDefinition's activitiesMap remains up to date
if (this.name!=null) {
processDefinition.removeNode(this);
}
this.name = name;
if (name!=null) {
processDefinition.addNode(this);
}
}
}
public TransitionImpl getDefaultTransition() {
return defaultTransition;
}
public void setDefaultTransition(TransitionImpl defaultTransition) {
this.defaultTransition = defaultTransition;
}
public NodeImpl getParentNode() {
return parentNode;
}
public void setParentNode(NodeImpl parentNode) {
this.parentNode = parentNode;
}
public boolean isExecutionAsync() {
return isExecutionAsync;
}
public boolean isSignalAsync() {
return isSignalAsync;
}
public void setSignalAsync(boolean isSignalAsync) {
this.isSignalAsync = isSignalAsync;
}
public void setExecutionAsync(boolean isExecutionAsync) {
this.isExecutionAsync = isExecutionAsync;
}
public boolean isLeaveAsync() {
return isLeaveAsync;
}
public void setLeaveAsync(boolean isLeaveAsync) {
this.isLeaveAsync = isLeaveAsync;
}
public boolean isPreviousNeeded() {
return isPreviousNeeded;
}
public void setPreviousNeeded(boolean isPreviousNeeded) {
this.isPreviousNeeded = isPreviousNeeded;
}
}