/* * 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.internal.model; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.jbpm.api.activity.ActivityBehaviour; import org.jbpm.pvm.internal.util.ReflectUtil; import org.jbpm.pvm.internal.wire.Descriptor; /** * @author Tom Baeyens */ public class ActivityImpl extends CompositeElementImpl implements Activity { private static final long serialVersionUID = 1L; protected ActivityBehaviour activityBehaviour; protected boolean isActivityBehaviourStateful = false; protected Descriptor activityBehaviourDescriptor; protected List outgoingTransitions = new ArrayList(); protected List incomingTransitions = new ArrayList(); protected TransitionImpl defaultOutgoingTransition; protected ActivityImpl parentActivity; protected String type; protected Continuation continuation = Continuation.SYNCHRONOUS; protected ActivityCoordinatesImpl coordinates; // Do not initialize. Caching is based on the nullity of this map transient protected Map outgoingTransitionsMap = null; /** * Use {@link ProcessDefinitionImpl#createActivity()} or {@link ActivityImpl#createActivity()} instead. */ public ActivityImpl() { super(); } // specialized activity containment methods ///////////////////////////////////// public ActivityImpl addActivity(ActivityImpl activity) { activity.setParentActivity(this); super.addActivity(activity); return activity; } public ActivityImpl findActivity(String activityName) { if (activityName==null) { if (name==null) { return this; } } else if (activityName.equals(name)) { return this; } return super.findActivity(activityName); } // outgoing transitions ////////////////////////////////////////////////////// /** creates an outgoing transition from this activity. */ public TransitionImpl createOutgoingTransition() { // create a new transition TransitionImpl transition = new TransitionImpl(); transition.setProcessDefinition(processDefinition); // wire it between the source and destination addOutgoingTransition(transition); // if there is no default transition yet if (defaultOutgoingTransition==null) { // make this the default outgoing transition defaultOutgoingTransition = transition; } return transition; } /** * adds the given transition as a leaving transition to this activity. * Also the source of the transition is set to this activity. * 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) { if (! outgoingTransitions.contains(transition)) { transition.setSource(this); transition.setSourceIndex(outgoingTransitions.size()); outgoingTransitions.add(transition); clearOutgoingTransitionsMap(); } 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 activity'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) { boolean isRemoved = outgoingTransitions.remove(transition); if (isRemoved) { transition.setSource(null); clearOutgoingTransitionsMap(); } 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 activity 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 (parentActivity!=null) { return parentActivity.findOutgoingTransition(transitionName); } return null; } /** searches for the default transition in this activity and then up the * parent chain. Returns null if no such transition is found. */ public TransitionImpl findDefaultTransition() { if (defaultOutgoingTransition!=null) { return defaultOutgoingTransition; } if (parentActivity!=null) { return parentActivity.findDefaultTransition(); } return null; } /** the list of leaving transitions. * Beware: the actual member is returned. No copy is made. */ public List 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 activity has leaving transitions */ public boolean hasOutgoingTransitions() { return !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 activity. * In case there was a leaving transitions list present, these transition's * source will be nulled. */ public void setOutgoingTransitions(List outgoingTransitions) { if (!this.outgoingTransitions.isEmpty()) { List removedTransitions = new ArrayList(outgoingTransitions); for (TransitionImpl removedTransition: removedTransitions) { removeOutgoingTransition(removedTransition); } } if (outgoingTransitions!=null) { this.outgoingTransitions = new ArrayList(); for (TransitionImpl addedTransition: outgoingTransitions) { addOutgoingTransition(addedTransition); } } else { this.outgoingTransitions = new ArrayList(); } clearOutgoingTransitionsMap(); } // arriving transitions ///////////////////////////////////////////////////// /** * adds the given transition as an arriving transition to this activity. * Also the source of the transition is set to this activity. * @return the added transition. * @throws NullPointerException if transition is null. */ public Transition addIncomingTransition(TransitionImpl transition) { transition.setDestination(this); incomingTransitions.add(transition); return transition; } /** removes the given transition if it is contained in the arriving * transitions of this activity. 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.remove(transition))) { transition.setDestination(null); return true; } return false; } /** the list of arriving transitions. * Beware: the actual member is returned. No copy is made. */ public List getIncomingTransitions() { return (List) incomingTransitions; } /** indicates if this activity has arriving transitions */ public boolean hasIncomingTransitions() { return !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 activity. * In case there was an arriving transitions list present, these transition's * destination will be nulled. */ public void setIncomingTransitions(List incomingTransitions) { if (!this.incomingTransitions.isEmpty()) { for (TransitionImpl removedTransition: this.incomingTransitions) { removedTransition.setDestination(null); } } if (incomingTransitions!=null) { this.incomingTransitions = new ArrayList(incomingTransitions); for (TransitionImpl addedTransition: incomingTransitions) { addedTransition.setDestination(this); } } else { this.incomingTransitions = 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 getOutgoingTransitionsMap() { if(outgoingTransitionsMap == null){ this.outgoingTransitionsMap = new HashMap(); for (TransitionImpl transition: outgoingTransitions) { if (! this.outgoingTransitionsMap.containsKey(transition.getName())) { this.outgoingTransitionsMap.put(transition.getName(), transition); } } } return (Map) outgoingTransitionsMap; } void clearOutgoingTransitionsMap() { outgoingTransitionsMap = null; } // various helper methods /////////////////////////////////////////////////// static Map getActivitiesMap(List activities) { Map map = null; if (activities!=null) { map = new HashMap(); for (ActivityImpl activity: activities) { if (! map.containsKey(activity.getName())) { map.put(activity.getName(), activity); } } } return map; } public String toString() { if (name!=null) return "activity("+name+")"; if (dbid!=0) return "activity("+dbid+")"; return "activity("+System.identityHashCode(this)+")"; } /** collects the full stack of parent in a list. This activity is the * first element in the chain. The process definition will be the last element. * the chain will never be null. */ public List getParentChain() { List chain = new ArrayList(); ObservableElementImpl processElement = this; while (processElement!=null) { chain.add(processElement); processElement = processElement.getParent(); } return chain; } public boolean isAsync() { return ! (continuation==Continuation.SYNCHRONOUS); } public boolean contains(ActivityImpl activity) { while (activity!=null) { if (activity.getParent()==this) { return true; } activity = activity.getParentActivity(); } return false; } // customized getters and setters /////////////////////////////////////////// public ActivityBehaviour getActivityBehaviour() { if (activityBehaviour!=null) { return activityBehaviour; } if (activityBehaviourDescriptor!=null) { ActivityBehaviour createdBehaviour = (ActivityBehaviour) ReflectUtil.instantiateUserCode(activityBehaviourDescriptor, processDefinition); if (!isActivityBehaviourStateful) { activityBehaviour = createdBehaviour; } return createdBehaviour; } return null; } // getters and setters ////////////////////////////////////////////////////// public ObservableElementImpl getParent() { return (parentActivity!=null ? parentActivity : processDefinition); } public String getName() { return name; } public void setName(String name) { this.name = name; } public TransitionImpl getDefaultOutgoingTransition() { return defaultOutgoingTransition; } public void setDefaultOutgoingTransition(TransitionImpl defaultOutgoingTransition) { this.defaultOutgoingTransition = defaultOutgoingTransition; } public ActivityImpl getParentActivity() { return parentActivity; } public void setParentActivity(ActivityImpl parentActivity) { this.parentActivity = parentActivity; } public String getType() { return type; } public void setType(String type) { this.type = type; } public ActivityCoordinatesImpl getCoordinates() { return coordinates; } public void setCoordinates(ActivityCoordinatesImpl coordinates) { this.coordinates = coordinates; } public Continuation getContinuation() { return continuation; } public void setContinuation(Continuation continuation) { this.continuation = continuation; } public void setActivityBehaviour(ActivityBehaviour activityBehaviour) { this.activityBehaviour = activityBehaviour; } public Descriptor getActivityBehaviourDescriptor() { return activityBehaviourDescriptor; } public void setActivityBehaviourDescriptor(Descriptor activityBehaviourDescriptor) { this.activityBehaviourDescriptor = activityBehaviourDescriptor; } public boolean isActivityBehaviourStateful() { return isActivityBehaviourStateful; } public void setActivityBehaviourStateful(boolean isActivityBehaviourStateful) { this.isActivityBehaviourStateful = isActivityBehaviourStateful; } }