/***************************************************************** JADE - Java Agent DEvelopment Framework is a framework to develop multi-agent systems in compliance with the FIPA specifications. Copyright (C) 2000 CSELT S.p.A. GNU Lesser General Public License 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 library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *****************************************************************/ package jade.core.behaviours; //#CUSTOM_EXCLUDE_FILE import java.util.Hashtable; import jade.util.leap.*; import jade.core.Agent; /** Composite behaviour with concurrent children scheduling. It is a <code>CompositeBehaviour</code> that executes its children behaviours concurrently, and it terminates when a particular condition on its sub-behaviours is met i.e. when all children are done, <em>N</em> children are done or any child is done. @author Giovanni Rimassa - Universita` di Parma @author Giovanni Caire - Telecom Italia Lab @version $Date: 2007-06-14 11:02:43 +0200 (gio, 14 giu 2007) $ $Revision: 5969 $ */ public class ParallelBehaviour extends CompositeBehaviour { /** Predefined constant to be used in the constructor to create a <code>ParallelBehaviour</code> that terminates when all its children are done. */ public static final int WHEN_ALL = 0; /** Predefined constant to be used in the constructor to create a <code>ParallelBehaviour</code> that terminates when any of its child is done. */ public static final int WHEN_ANY = 1; private int whenToStop; private BehaviourList subBehaviours = new BehaviourList(); private Hashtable blockedChildren = new Hashtable(); private BehaviourList terminatedChildren = new BehaviourList(); /** Construct a <code>ParallelBehaviour</code> without setting the owner agent, and using the default termination condition (i.e. the parallel behaviour terminates as soon as all its children behaviours terminate. */ public ParallelBehaviour() { whenToStop = WHEN_ALL; } /** Construct a <code>ParallelBehaviour</code> without setting the owner agent. @param endCondition this value defines the termination condition for this <code>ParallelBehaviour</code>. Use <ol> <li> <code>WHEN_ALL</code> to terminate this <code>ParallelBehaviour</code> when all its children are done. </li> <li> <code>WHEN_ANY</code> to terminate this <code>ParallelBehaviour</code> when any of its child is done. </li> <li> a positive <code>int</code> value n to terminate this <code>ParallelBehaviour</code> when n of its children are done. </li> </ol> */ public ParallelBehaviour(int endCondition) { whenToStop = endCondition; } /** Construct a <code>ParallelBehaviour</code> setting the owner agent. @param a the agent this <code>ParallelBehaviour</code> belongs to. @param endCondition this value defines the termination condition for this <code>ParallelBehaviour</code>. Use <ol> <li> <code>WHEN_ALL</code> to terminate this <code>ParallelBehaviour</code> when all its children are done. </li> <li> <code>WHEN_ANY</code> to terminate this <code>ParallelBehaviour</code> when any of its child is done. </li> <li> a positive <code>int</code> value n to terminate this <code>ParallelBehaviour</code> when n of its children are done. </li> </ol> */ public ParallelBehaviour(Agent a, int endCondition) { super(a); whenToStop = endCondition; } /** Prepare the first child for execution @see jade.core.behaviours.CompositeBehaviour#scheduleFirst */ protected void scheduleFirst() { // Schedule the first child subBehaviours.begin(); Behaviour b = subBehaviours.getCurrent(); // If there are no children just do nothing if (b != null) { // If there is at least one runnable child, then schedule // the first runnable child, else just do nothing. if (blockedChildren.size() < subBehaviours.size()) { while (!b.isRunnable()) { b = subBehaviours.next(); } } } } /** This method schedules children behaviours one at a time, in a round robin fashion. @see jade.core.behaviours.CompositeBehaviour#scheduleNext(boolean, int) */ protected void scheduleNext(boolean currentDone, int currentResult) { // Regardless of whether the current child is terminated, schedule // the next one; Behaviour b = subBehaviours.next(); // If there are no children just do nothing (this can happen // if there was just one child and someone suddenly removed it) if (b != null) { // If there is at least one runnable child, then schedule // the first runnable child, else just do nothing. if (blockedChildren.size() < subBehaviours.size()) { while (!b.isRunnable()) { b = subBehaviours.next(); } } } } /** Check whether this <code>ParallelBehaviour</code> must terminate. @see jade.core.behaviours.CompositeBehaviour#checkTermination */ protected boolean checkTermination(boolean currentDone, int currentResult) { if(currentDone) { // If the current child is terminated --> remove it from // the list of sub-behaviours Behaviour b = subBehaviours.getCurrent(); subBehaviours.removeElement(b); b.setParent(null); terminatedChildren.addElement(b); } if (!evalCondition()) { // The following check must be done regardless of the fact // that the current child is done or not, but provided that // this ParallelBehaviour is not terminated if (blockedChildren.size() == subBehaviours.size()) { // If all children are blocked --> this // ParallelBehaviour must block too and notify upwards myEvent.init(false, NOTIFY_UP); super.handle(myEvent); } return false; } else { return true; } } /** Get the current child @see jade.core.behaviours.CompositeBehaviour#getCurrent */ protected Behaviour getCurrent() { return subBehaviours.getCurrent(); } /** Return a Collection view of the children of this <code>ParallelBehaviour</code> @see jade.core.behaviours.CompositeBehaviour#getChildren */ public Collection getChildren() { return subBehaviours; } /** Add a sub behaviour to this <code>ParallelBehaviour</code> */ public void addSubBehaviour(Behaviour b) { subBehaviours.addElement(b); b.setParent(this); b.setAgent(myAgent); if (b.isRunnable()) { // If all previous children were blocked (this Parallel Behaviour // was blocked too), restart this ParallelBehaviour and notify // upwards if (!isRunnable()) { if(myAgent != null) myAgent.removeTimer(this); myEvent.init(true, NOTIFY_UP); super.handle(myEvent); if(myAgent != null) myAgent.notifyRestarted(this); // Also reset the currentExecuted flag so that a runnable // child will be scheduled for execution currentExecuted = true; } } else { blockedChildren.put(b, b); } } /** Remove a sub behaviour from this <code>ParallelBehaviour</code> */ public void removeSubBehaviour(Behaviour b) { terminatedChildren.removeElement(b); boolean rc = subBehaviours.removeElement(b); if(rc) { b.setParent(null); } else { // The specified behaviour was not found. Do nothing } if (!b.isRunnable()) { blockedChildren.remove(b); } else { // If some children still exist and they are all blocked, // block this ParallelBehaviour and notify upwards if ((!subBehaviours.isEmpty()) && (blockedChildren.size() == subBehaviours.size()) ) { myEvent.init(false, NOTIFY_UP); super.handle(myEvent); } } } /** Resets this behaviour. This methods puts a <code>ParallelBehaviour</code> back in initial state, besides calling <code>reset()</code> on each child behaviour recursively. */ public void reset() { blockedChildren.clear(); terminatedChildren.begin(); Behaviour b = terminatedChildren.getCurrent(); // Restore all terminated sub-behaviours while(b != null) { terminatedChildren.removeElement(b); b.setParent(this); subBehaviours.addElement(b); b = terminatedChildren.next(); } subBehaviours.begin(); super.reset(); } //#APIDOC_EXCLUDE_BEGIN /** Handle block/restart notifications. A <code>ParallelBehaviour</code> object is blocked <em>only</em> when all its children behaviours are blocked and becomes ready to run as soon as any of its children is runnable. This method takes care of the various possibilities. @param rce The event to handle. */ protected void handle(RunnableChangedEvent rce) { // This method may be executed by an auxiliary thread posting // a message into the Agent's message queue --> it must be // synchronized with sub-behaviour additions/removal. synchronized (subBehaviours) { if(rce.isUpwards()) { // Upwards notification Behaviour b = rce.getSource(); if (b == this) { // If the event is from this behaviour, set the new // runnable state and notify upwards. super.handle(rce); } else { // If the event is from a child --> if(rce.isRunnable()) { // If this is a restart, remove the child from the // list of blocked children Object child = blockedChildren.remove(b); // Only if all children were blocked (this ParallelBehaviour was // blocked too), restart this ParallelBehaviour and notify upwards if( (child != null) && !isRunnable() ) { myEvent.init(true, NOTIFY_UP); super.handle(myEvent); // Also reset the currentExecuted flag so that a runnable // child will be scheduled for execution currentExecuted = true; } } else { // If this is a block, put the child in the list of // blocked children Object child = blockedChildren.put(b, b); // Only if, with the addition of this child all sub-behaviours // are now blocked, block this ParallelBehaviour and notify upwards if ( (child == null) && (blockedChildren.size() == subBehaviours.size()) ) { myEvent.init(false, NOTIFY_UP); super.handle(myEvent); } } } // END of upwards notification from children } // END of upwards notification else { // Downwards notification (from parent) boolean r = rce.isRunnable(); // Set the new runnable state setRunnable(r); // Notify all children Iterator it = getChildren().iterator(); while (it.hasNext()) { Behaviour b = (Behaviour) it.next(); b.handle(rce); } // Clear or completely fill the list of blocked children // according to whether this is a block or restart if (r) { blockedChildren.clear(); } else { it = getChildren().iterator(); while (it.hasNext()) { Behaviour b = (Behaviour) it.next(); blockedChildren.put(b, b); } } } // END of downwards notification } } //#APIDOC_EXCLUDE_END private boolean evalCondition() { boolean cond; switch(whenToStop) { case WHEN_ALL: cond = subBehaviours.isEmpty(); break; case WHEN_ANY: cond = (terminatedChildren.size() > 0); break; default: cond = (terminatedChildren.size() >= whenToStop); break; } return cond; } }