/*****************************************************************
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;
import java.io.IOException;
import java.io.InterruptedIOException;
import jade.util.leap.Serializable;
import jade.util.leap.Iterator;
import java.util.Hashtable;
import java.util.Enumeration;
import jade.core.behaviours.Behaviour;
import jade.lang.acl.*;
import jade.security.JADESecurityException;
//#MIDP_EXCLUDE_BEGIN
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.OutputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import jade.core.mobility.AgentMobilityHelper;
import jade.core.mobility.Movable;
import jade.util.leap.List;
import jade.util.leap.ArrayList;
import jade.util.leap.Map;
import jade.util.leap.HashMap;
//#MIDP_EXCLUDE_END
import jade.util.leap.Properties;
/*#MIDP_INCLUDE_BEGIN
import javax.microedition.midlet.*;
#MIDP_INCLUDE_END*/
/**
The <code>Agent</code> class is the common superclass for user
defined software agents. It provides methods to perform basic agent
tasks, such as:
<ul>
<li> <b> Message passing using <code>ACLMessage</code> objects,
both unicast and multicast with optional pattern matching. </b></li>
<li> <b> Complete Agent Platform life cycle support, including
starting, suspending and killing an agent. </b></li>
<li> <b> Scheduling and execution of multiple concurrent activities. </b></li>
</ul>
Application programmers must write their own agents as
<code>Agent</code> subclasses, adding specific behaviours as needed
and exploiting <code>Agent</code> class capabilities.
@author Giovanni Rimassa - Universita' di Parma
@author Giovanni Caire - TILAB
@version $Date: 2011-06-06 09:17:37 +0200(lun, 06 giu 2011) $ $Revision: 6414 $
*/
public class Agent implements Runnable, Serializable
//#APIDOC_EXCLUDE_BEGIN
, TimerListener
//#APIDOC_EXCLUDE_END
{
private static final long serialVersionUID = 3487495895819000L;
/**
Inner class Interrupted.
This class is used to handle change state requests that occur
in particular situations such as when the agent thread is
blocked in the doWait() method.
*/
public static class Interrupted extends Error {
public Interrupted() {
super();
}
} // END of inner class Interrupted
/**
Inner class AssociationTB.
This class manages bidirectional associations between Timer and
Behaviour objects, using hash tables. This class is
synchronized with the operations
carried out by the TimerDispatcher. It allows also to avoid a deadlock when:
1) A behaviour blocks for a very short time --> A Timer is added
to the TimerDispatcher
2) The Timer immediately expires and the TimerDispatcher try to
restart the behaviour before the pair (b, t) is added to the
pendingTimers of this agent.
*/
private class AssociationTB {
private Hashtable BtoT = new Hashtable();
private Hashtable TtoB = new Hashtable();
public void clear() {
synchronized (theDispatcher) {
Enumeration e = timers();
while (e.hasMoreElements()) {
Timer t = (Timer) e.nextElement();
theDispatcher.remove(t);
}
BtoT.clear();
TtoB.clear();
//#J2ME_EXCLUDE_BEGIN
// For persistence service
persistentPendingTimers.clear();
//#J2ME_EXCLUDE_END
} //end synch
}
public void addPair(Behaviour b, Timer t) {
TBPair pair = new TBPair(Agent.this, t, b);
addPair(pair);
}
public void addPair(TBPair pair) {
synchronized (theDispatcher) {
if(pair.getOwner() == null) {
pair.setOwner(Agent.this);
}
pair.setTimer(theDispatcher.add(pair.getTimer()));
TBPair old = (TBPair)BtoT.put(pair.getBehaviour(), pair);
if(old != null) {
theDispatcher.remove(old.getTimer());
//#J2ME_EXCLUDE_BEGIN
persistentPendingTimers.remove(old);
//#J2ME_EXCLUDE_END
TtoB.remove(old.getTimer());
}
// Note that timers added to the TimerDispatcher are unique --> there
// can't be an old value to handle
TtoB.put(pair.getTimer(), pair);
//#J2ME_EXCLUDE_BEGIN
// For persistence service
persistentPendingTimers.add(pair);
//#J2ME_EXCLUDE_END
} //end synch
}
public void removeMapping(Behaviour b) {
synchronized (theDispatcher) {
TBPair pair = (TBPair)BtoT.remove(b);
if(pair != null) {
TtoB.remove(pair.getTimer());
//#J2ME_EXCLUDE_BEGIN
// For persistence service
persistentPendingTimers.remove(pair);
//#J2ME_EXCLUDE_END
theDispatcher.remove(pair.getTimer());
}
} //end synch
}
public Timer getPeer(Behaviour b) {
// this is not synchronized because BtoT is an Hashtable (that is already synch!)
TBPair pair = (TBPair)BtoT.get(b);
if(pair != null) {
return pair.getTimer();
}
else {
return null;
}
}
public Behaviour getPeer(Timer t) {
// this is not synchronized because BtoT is an Hashtable (that is already synch!)
TBPair pair = (TBPair)TtoB.get(t);
if(pair != null) {
return pair.getBehaviour();
}
else {
return null;
}
}
private Enumeration timers() {
return TtoB.keys();
}
} // End of inner class AssociationTB
/** Inner class TBPair
*
*/
private static class TBPair {
public TBPair() {
expirationTime = -1;
}
public TBPair(Agent a, Timer t, Behaviour b) {
owner = a;
myTimer = t;
expirationTime = t.expirationTime();
myBehaviour = b;
}
public void setTimer(Timer t) {
myTimer = t;
}
public Timer getTimer() {
return myTimer;
}
public Behaviour getBehaviour() {
return myBehaviour;
}
public void setBehaviour(Behaviour b) {
myBehaviour = b;
}
public Agent getOwner() {
return owner;
}
public void setOwner(Agent o) {
owner = o;
createTimerIfNeeded();
}
public long getExpirationTime() {
return expirationTime;
}
public void setExpirationTime(long when) {
expirationTime = when;
createTimerIfNeeded();
}
// If both the owner and the expiration time have been set,
// but the Timer object is still null, create one
private void createTimerIfNeeded() {
if(myTimer == null) {
if((owner != null) && (expirationTime > 0)) {
myTimer = new Timer(expirationTime, owner);
}
}
}
private Timer myTimer;
private long expirationTime;
private Behaviour myBehaviour;
private Agent owner;
} // End of inner class TBPair
//#MIDP_EXCLUDE_BEGIN
/**
Inner class CondVar
A simple class for a boolean condition variable
*/
private static class CondVar {
private boolean value = false;
public synchronized void waitOn() throws InterruptedException {
while(!value) {
wait();
}
}
public synchronized void set() {
value = true;
notifyAll();
}
} // End of inner class CondVar
//#MIDP_EXCLUDE_END
//#APIDOC_EXCLUDE_BEGIN
/**
Schedules a restart for a behaviour, after a certain amount of
time has passed.
@param b The behaviour to restart later.
@param millis The amount of time to wait before restarting
<code>b</code>.
@see jade.core.behaviours.Behaviour#block(long millis)
*/
public void restartLater(Behaviour b, long millis) {
if (millis <= 0)
return;
Timer t = new Timer(System.currentTimeMillis() + millis, this);
pendingTimers.addPair(b, t);
}
/**
Restarts the behaviour associated with t.
This method runs within the time-critical Timer Dispatcher thread and
is not intended to be called by users. It is defined public only because
is part of the <code>TimerListener</code> interface.
*/
public void doTimeOut(Timer t) {
Behaviour b = null;
// This synchronized block avoids that if a behaviour is blocked
// again just after pendingTimers.getPeer(t) is called, a new mapping
// is added before the old one is removed --> The new mapping is
// removed instead of the old one.
// In any case b.restart() must be called outside the synchronized
// block to avoid a deadlock between the TimerDispatcher and the Scheduler.
synchronized (theDispatcher) {
b = pendingTimers.getPeer(t);
if(b != null) {
pendingTimers.removeMapping(b);
}
}
if(b != null) {
b.restart();
}
else {
System.out.println("Warning: No mapping found for expired timer "+t.expirationTime());
}
}
/**
Notifies this agent that one of its behaviours has been restarted
for some reason. This method clears any timer associated with
behaviour object <code>b</code>, and it is unneeded by
application level code. To explicitly schedule behaviours, use
<code>block()</code> and <code>restart()</code> methods.
@param b The behaviour object which was restarted.
@see jade.core.behaviours.Behaviour#restart()
*/
public void notifyRestarted(Behaviour b) {
// Did this restart() cause the root behaviour to become runnable ?
// If so, put the root behaviour back into the ready queue.
Behaviour root = b.root();
if(root.isRunnable()) {
myScheduler.restart(root);
}
}
public void removeTimer(Behaviour b) {
// The mapping for b in general has already been removed in doTimeOut().
// There is however a case related to ParallelBehaviours where
// notifyRestarted() is not called as a consequence of a timer
// expiration --> doTimeOut() is not called in this case -->
// We remove the mapping in any case.
Timer t = pendingTimers.getPeer(b);
if(t != null) {
pendingTimers.removeMapping(b);
}
}
/**
Out of band value for Agent Platform Life Cycle states.
*/
public static final int AP_MIN = 0; // Hand-made type checking
/**
Represents the <em>initiated</em> agent state.
*/
public static final int AP_INITIATED = 1;
/**
Represents the <em>active</em> agent state.
*/
public static final int AP_ACTIVE = 2;
/**
Represents the <em>idle</em> agent state.
*/
public static final int AP_IDLE = 3;
/**
Represents the <em>suspended</em> agent state.
*/
public static final int AP_SUSPENDED = 4;
/**
Represents the <em>waiting</em> agent state.
*/
public static final int AP_WAITING = 5;
/**
Represents the <em>deleted</em> agent state.
*/
public static final int AP_DELETED = 6;
/**
Out of band value for Agent Platform Life Cycle states.
*/
public static final int AP_MAX = 13; // Hand-made type checking
//#MIDP_EXCLUDE_BEGIN
/**
These constants represent the various Domain Life Cycle states
*/
/**
Out of band value for Domain Life Cycle states.
*/
public static final int D_MIN = 9; // Hand-made type checking
/**
Represents the <em>active</em> agent state.
*/
public static final int D_ACTIVE = 10;
/**
Represents the <em>suspended</em> agent state.
*/
public static final int D_SUSPENDED = 20;
/**
Represents the <em>retired</em> agent state.
*/
public static final int D_RETIRED = 30;
/**
Represents the <em>unknown</em> agent state.
*/
public static final int D_UNKNOWN = 40;
/**
Out of band value for Domain Life Cycle states.
*/
public static final int D_MAX = 41; // Hand-made type checking
//#MIDP_EXCLUDE_END
//#APIDOC_EXCLUDE_END
public static final String MSG_QUEUE_CLASS = "jade_core_Agent_msgQueueClass";
private transient AgentToolkit myToolkit;
private transient MessageQueue msgQueue;
private int msgQueueMaxSize = 0;
//#MIDP_EXCLUDE_BEGIN
private transient boolean temporaryMessageQueue;
private transient List o2aQueue;
private int o2aQueueSize = 0;
private transient Map o2aLocks;
private Behaviour o2aManager = null;
private transient Object suspendLock;
//#MIDP_EXCLUDE_END
private String myName = null;
private AID myAID = null;
private String myHap = null;
private transient Object stateLock;
private transient Thread myThread;
private transient TimerDispatcher theDispatcher;
private Scheduler myScheduler;
private transient AssociationTB pendingTimers;
private boolean restarting = false;
private LifeCycle myLifeCycle;
private LifeCycle myBufferedLifeCycle;
private LifeCycle myActiveLifeCycle;
private transient LifeCycle myDeletedLifeCycle;
//#MIDP_EXCLUDE_BEGIN
private transient LifeCycle mySuspendedLifeCycle;
//#MIDP_EXCLUDE_END
/**
This flag is used to distinguish the normal AP_ACTIVE state from
the particular case in which the agent state is set to AP_ACTIVE
during agent termination (takeDown()) to allow it to clean-up properly.
In this case in fact a call to <code>doDelete()</code>,
<code>doMove()</code>, <code>doClone()</code> and <code>doSuspend()</code>
should have no effect.
*/
private boolean terminating = false;
//#MIDP_EXCLUDE_BEGIN
/**
When set to false (default) all behaviour-related events (such as ADDED_BEHAVIOUR
or CHANGED_BEHAVIOUR_STATE) are not generated in order to improve performances.
These events in facts are very frequent.
*/
private boolean generateBehaviourEvents = false;
//#MIDP_EXCLUDE_END
/*#MIDP_INCLUDE_BEGIN
public static MIDlet midlet;
// Flag for agent interruption (necessary as Thread.interrupt() is not available in MIDP)
private boolean isInterrupted = false;
#MIDP_INCLUDE_END*/
/**
Default constructor.
*/
public Agent() {
//#MIDP_EXCLUDE_BEGIN
myToolkit = DummyToolkit.instance();
o2aLocks = new HashMap();
suspendLock = new Object();
temporaryMessageQueue = true;
//#MIDP_EXCLUDE_END
msgQueue = new InternalMessageQueue(msgQueueMaxSize);
stateLock = new Object();
pendingTimers = new AssociationTB();
myActiveLifeCycle = new ActiveLifeCycle();
myLifeCycle = myActiveLifeCycle;
myScheduler = new Scheduler(this);
theDispatcher = TimerDispatcher.getTimerDispatcher();
}
//#MIDP_EXCLUDE_BEGIN
/**
* Developer can override this method to provide an alternative message queue creation mechanism
* @return The MessageQueue to be used by this agent or null if the internal message queue must be used
*/
protected MessageQueue createMessageQueue() {
String msgQueueClass = getProperty(MSG_QUEUE_CLASS, null);
if (msgQueueClass != null) {
try {
return (MessageQueue) Class.forName(msgQueueClass).newInstance();
}
catch (Exception e) {
System.out.println("Error loading MessageQueue of class "+msgQueueClass+" ["+e+"]");
}
}
return null;
}
/**
* If the agent still has a temporary message queue, create the real one and copy messages if any
*/
void initMessageQueue() {
if (temporaryMessageQueue) {
temporaryMessageQueue = false;
MessageQueue queue = createMessageQueue();
if (queue != null) {
queue.setMaxSize(msgQueueMaxSize);
// Copy messages (if any) from the old message queue to the new one
synchronized (msgQueue) {
int size = msgQueue.size();
if (size > 0) {
List l = new ArrayList(size);
msgQueue.copyTo(l);
Iterator it = l.iterator();
while (it.hasNext()) {
queue.addLast((ACLMessage) it.next());
}
}
msgQueue = queue;
}
}
}
}
/**
This is only called by AgentContainerImpl
*/
MessageQueue getMessageQueue() {
return msgQueue;
}
// For persistence service
private void setMessageQueue(MessageQueue mq) {
msgQueue = mq;
}
/**
Constructor to be used by special "agents" that will never powerUp.
*/
Agent(AID id) {
setAID(id);
}
/**
* Declared transient because the container changes in case
* of agent migration.
*/
private transient jade.wrapper.AgentContainer myContainer = null;
/**
* Return a controller for the container this agent lives in.
* <br>
* <b>NOT available in MIDP</b>
* <br>
* @return jade.wrapper.AgentContainer a controller for the container this agent lives in.
*/
public final jade.wrapper.AgentContainer getContainerController() {
if (myContainer == null) { // first time called
try {
jade.security.JADEPrincipal principal = null;
jade.security.Credentials credentials = null;
try {
jade.security.CredentialsHelper ch = (jade.security.CredentialsHelper) getHelper("jade.core.security.Security");
principal = ch.getPrincipal();
credentials = ch.getCredentials();
}
catch (ServiceException se) {
// Security plug-in not present. Ignore it
}
myContainer = myToolkit.getContainerController(principal, credentials);
} catch (Exception e) {
throw new IllegalStateException("A ContainerController cannot be got for this agent. Probably the method has been called at an appropriate time before the complete initialization of the agent.");
}
}
return myContainer;
}
//#MIDP_EXCLUDE_END
private transient Object[] arguments = null; // array of arguments
//#APIDOC_EXCLUDE_BEGIN
/**
* Called by AgentContainerImpl in order to pass arguments to a
* just created Agent.
* <p>Usually, programmers do not need to call this method in their code.
* @see #getArguments() how to get the arguments passed to an agent
**/
public final void setArguments(Object args[]) {
// I have declared the method final otherwise getArguments would not work!
arguments=args;
}
//#APIDOC_EXCLUDE_END
/**
* Get the array of arguments passed to this agent.
* <p> Take care that the arguments are transient and they do not
* migrate with the agent neither are cloned with the agent!
* @return the array of arguments passed to this agent.
* @see <a href=../../../tutorials/ArgsAndPropsPassing.htm>How to use arguments or properties to configure your agent.</a>
**/
public Object[] getArguments() {
return arguments;
}
void setRestarting(boolean restarting) {
this.restarting = restarting;
}
/**
* This method returns <code>true</code> when this agent is restarting after a crash.
* The restarting indication is automatically reset as soon as the <code>setup()</code> method of
* this agent terminates.
* @return <code>true</code> when this agent is restarting after a crash. <code>false</code> otherwise.
*/
public final boolean isRestarting() {
return restarting;
}
/**
Get the Agent ID for the platform AMS.
@return An <code>AID</code> object, that can be used to contact
the AMS of this platform.
*/
public final AID getAMS() {
return myToolkit.getAMS();
}
/**
Get the Agent ID for the platform default DF.
@return An <code>AID</code> object, that can be used to contact
the default DF of this platform.
*/
public final AID getDefaultDF() {
return myToolkit.getDefaultDF();
}
/**
Method to query the agent local name.
@return A <code>String</code> containing the local agent name
(e.g. <em>peter</em>).
*/
public final String getLocalName() {
return myName;
}
/**
Method to query the agent complete name (<em><b>GUID</b></em>).
@return A <code>String</code> containing the complete agent name
(e.g. <em>peter@fipa.org:50</em>).
*/
public final String getName() {
if (myHap != null) {
return myName + '@' + myHap;
}
else {
return myName;
}
}
/**
* Method to query the Home Agent Platform. This is the name of
* the platform where the agent has been created, therefore it will
* never change during the entire lifetime of the agent.
* In JADE the name of an agent by default is composed by the
* concatenation (using '@') of the agent local name and the Home
* Agent Platform name
*
* @return A <code>String</code> containing the name of the home agent platform
* (e.g. <em>myComputerName:1099/JADE</em>).
*/
public final String getHap() {
return myHap;
}
/**
Method to query the private Agent ID. Note that this Agent ID is
<b>different</b> from the one that is registered with the
platform AMS.
@return An <code>Agent ID</code> object, containing the complete
agent GUID, addresses and resolvers.
*/
public final AID getAID() {
return myAID;
}
void setAID(AID id) {
myName = id.getLocalName();
myHap = id.getHap();
myAID = id;
}
/**
This method adds a new platform address to the AID of this Agent.
It is called by the container when a new MTP is activated
in the platform (in the local container - installMTP() -
or in a remote container - updateRoutingTable()) to keep the
Agent AID updated.
*/
synchronized void addPlatformAddress(String address) { // Mutual exclusion with Agent.powerUp()
if (myAID != null) {
// Cloning the AID is necessary as the agent may be using its AID.
// If this is the case a ConcurrentModificationException would be thrown
myAID = (AID)myAID.clone();
myAID.addAddresses(address);
}
}
/**
This method removes an old platform address from the AID of this Agent.
It is called by the container when a new MTP is deactivated
in the platform (in the local container - uninstallMTP() -
or in a remote container - updateRoutingTable()) to keep the
Agent AID updated.
*/
synchronized void removePlatformAddress(String address) { // Mutual exclusion with Agent.powerUp()
if (myAID != null) {
// Cloning the AID is necessary as the agent may be using its AID.
// If this is the case a ConcurrentModificationException would be thrown
myAID = (AID)myAID.clone();
myAID.removeAddresses(address);
}
}
/**
Method to retrieve the location this agent is currently at.
@return A <code>Location</code> object, describing the location
where this agent is currently running.
*/
public Location here() {
return myToolkit.here();
}
//#APIDOC_EXCLUDE_BEGIN
/**
* This method is used internally by the framework and should NOT be used by programmers.
* This is used by the agent container to wait for agent termination.
* We have already called doDelete on the thread which would have
* issued an interrupt on it. However, it still may decide not to exit.
* So we will wait no longer than 5 seconds for it to exit and we
* do not care of this zombie agent.
* FIXME: we must further isolate container and agents, for instance
* by using custom class loader and dynamic proxies and JDK 1.3.
* FIXME: the timeout value should be got by Profile
*/
public void join() {
//#MIDP_EXCLUDE_BEGIN
try {
if(myThread == null) {
return;
}
myThread.join(5000);
if (myThread.isAlive()) {
System.out.println("*** Warning: Agent " + myName + " did not terminate when requested to do so.");
if(!myThread.equals(Thread.currentThread())) {
myThread.interrupt();
System.out.println("*** Second interrupt issued.");
}
}
}
catch(InterruptedException ie) {
ie.printStackTrace();
}
//#MIDP_EXCLUDE_END
/*#MIDP_INCLUDE_BEGIN
if (myThread != null && myThread.isAlive()) {
try {
myThread.join();
}
catch (InterruptedException ie) {
ie.printStackTrace();
}
}
#MIDP_INCLUDE_END*/
}
//#APIDOC_EXCLUDE_END
/**
Set message queue size. This method allows to change the number
of ACL messages that can be buffered before being actually read
by the agent or discarded.
@param newSize A non negative integer value to set message queue
size to. Passing 0 means unlimited message queue. When the number of
buffered
messages exceeds this value, older messages are discarded
according to a <b><em>FIFO</em></b> replacement policy.
@throws IllegalArgumentException If <code>newSize</code> is negative.
@see jade.core.Agent#getQueueSize()
*/
public void setQueueSize(int newSize) throws IllegalArgumentException {
msgQueue.setMaxSize(newSize);
msgQueueMaxSize = newSize;
}
/**
* This method retrieves the current length of the message queue
* of this agent.
* @return The number of messages that are currently stored into the
* message queue.
**/
public int getCurQueueSize() {
return msgQueue.size();
}
/**
Reads message queue size. A zero value means that the message
queue is unbounded (its size is limited only by amount of
available memory).
@return The actual size of the message queue (i.e. the max number
of messages that can be stored into the queue)
@see jade.core.Agent#setQueueSize(int newSize)
@see jade.core.Agent#getCurQueueSize()
*/
public int getQueueSize() {
return msgQueue.getMaxSize();
}
/////////////////////////////////
// Agent state management
/////////////////////////////////
public void changeStateTo(LifeCycle newLifeCycle) {
boolean changed = false;
newLifeCycle.setAgent(this);
synchronized (stateLock) {
if (!myLifeCycle.equals(newLifeCycle)) {
// The new state is actually different from the current one
if (myLifeCycle.transitionTo(newLifeCycle)) {
myBufferedLifeCycle = myLifeCycle;
myLifeCycle = newLifeCycle;
changed = true;
//#MIDP_EXCLUDE_BEGIN
notifyChangedAgentState(myBufferedLifeCycle.getState(), myLifeCycle.getState());
//#MIDP_EXCLUDE_END
}
}
}
if (changed) {
myLifeCycle.transitionFrom(myBufferedLifeCycle);
if (!Thread.currentThread().equals(myThread)) {
// If the state-change is forced from the outside, interrupt
// the agent thread to allow the state change to take place
interruptThread();
}
}
}
public void restoreBufferedState() {
changeStateTo(myBufferedLifeCycle);
}
/**
The ActiveLifeCycle handles different internal states (INITIATED,
ACTIVE, WAITING, IDLE). This method switches between them.
*/
private void setActiveState(int newState) {
synchronized (stateLock) {
if (myLifeCycle == myActiveLifeCycle) {
int oldState = myLifeCycle.getState();
if (newState != oldState) {
((ActiveLifeCycle) myLifeCycle).setState(newState);
//#MIDP_EXCLUDE_BEGIN
notifyChangedAgentState(oldState, newState);
//#MIDP_EXCLUDE_END
}
}
else {
// A change state request arrived in the meanwhile.
// Let it take place.
throw new Interrupted();
}
}
}
//#APIDOC_EXCLUDE_BEGIN
/**
Read current agent state. This method can be used to query an
agent for its state from the outside.
@return the Agent Platform Life Cycle state this agent is currently in.
*/
public int getState() {
return myLifeCycle.getState();
}
//#APIDOC_EXCLUDE_END
//#MIDP_EXCLUDE_BEGIN
public AgentState getAgentState() {
return AgentState.getInstance(getState());
}
/**
This is only called by the NotificationService to provide the Introspector
agent with a snapshot of the behaviours currently loaded in the agent
*/
Scheduler getScheduler() {
return myScheduler;
}
/////////////////////////////
// Mobility related code
/////////////////////////////
private transient AgentMobilityHelper mobHelper;
private void initMobHelper() throws ServiceException {
if (mobHelper == null) {
mobHelper = (AgentMobilityHelper) getHelper(AgentMobilityHelper.NAME);
mobHelper.registerMovable(new Movable() {
public void beforeMove() {
Agent.this.beforeMove();
}
public void afterMove() {
Agent.this.afterMove();
}
public void beforeClone() {
Agent.this.beforeClone();
}
public void afterClone() {
Agent.this.afterClone();
}
} );
}
}
/**
Make this agent move to a remote location. This method
is intended to support agent mobility and is called either by the
Agent Platform or by the agent itself to start a migration process.
It should be noted that this method just changes the agent
state to <code>AP_TRANSIT</code>. The actual migration takes
place asynchronously.
<br>
<b>NOT available in MIDP</b>
<br>
@param destination The <code>Location</code> to migrate to.
*/
public void doMove(Location destination) {
// Do nothing if the mobility service is not installed
try {
initMobHelper();
mobHelper.move(destination);
}
catch(ServiceException se) {
// FIXME: Log a proper warning
return;
}
}
/**
Make this agent be cloned on another location. This method
is intended to support agent mobility and is called either by the
Agent Platform or by the agent itself to start a clonation process.
It should be noted that this method just changes the agent
state to <code>AP_COPY</code>. The actual clonation takes
place asynchronously.
<br>
<b>NOT available in MIDP</b>
<br>
@param destination The <code>Location</code> where the copy agent will start.
@param newName The name that will be given to the copy agent.
*/
public void doClone(Location destination, String newName) {
// Do nothing if the mobility service is not installed
try {
initMobHelper();
mobHelper.clone(destination, newName);
}
catch(ServiceException se) {
// FIXME: Log a proper warning
return;
}
}
//#MIDP_EXCLUDE_END
/**
Make a state transition from <em>active</em> or <em>waiting</em>
to <em>suspended</em> within Agent Platform Life Cycle; the
original agent state is saved and will be restored by a
<code>doActivate()</code> call. This method can be called from
the Agent Platform or from the agent itself and stops all agent
activities. Incoming messages for a suspended agent are buffered
by the Agent Platform and are delivered as soon as the agent
resumes. Calling <code>doSuspend()</code> on a suspended agent
has no effect.
<br>
<b>NOT available in MIDP</b>
<br>
@see jade.core.Agent#doActivate()
*/
public void doSuspend() {
//#MIDP_EXCLUDE_BEGIN
if (mySuspendedLifeCycle == null) {
mySuspendedLifeCycle = new SuspendedLifeCycle();
}
changeStateTo(mySuspendedLifeCycle);
//#MIDP_EXCLUDE_END
}
/**
Make a state transition from <em>suspended</em> to
<em>active</em> or <em>waiting</em> (whichever state the agent
was in when <code>doSuspend()</code> was called) within Agent
Platform Life Cycle. This method is called from the Agent
Platform and resumes agent execution. Calling
<code>doActivate()</code> when the agent is not suspended has no
effect.
<br>
<b>NOT available in MIDP</b>
<br>
@see jade.core.Agent#doSuspend()
*/
public void doActivate() {
//#MIDP_EXCLUDE_BEGIN
//doExecute();
restoreBufferedState();
//#MIDP_EXCLUDE_END
}
/**
Make a state transition from <em>active</em> to <em>waiting</em>
within Agent Platform Life Cycle. This method has only effect
when called by the agent thread and causes the agent to
block, stopping all its activities until
a message arrives or the
<code>doWake()</code> method is called.
@see jade.core.Agent#doWake()
*/
public void doWait() {
doWait(0);
}
/**
Make a state transition from <em>active</em> to <em>waiting</em>
within Agent Platform Life Cycle. This method adds a timeout to
the other <code>doWait()</code> version.
@param millis The timeout value, in milliseconds.
@see jade.core.Agent#doWait()
*/
public void doWait(long millis) {
if (Thread.currentThread().equals(myThread)) {
setActiveState(AP_WAITING);
synchronized(msgQueue) {
try {
// Blocks on msgQueue monitor for a while
waitOn(msgQueue, millis);
}
catch (InterruptedException ie) {
if (myLifeCycle != myActiveLifeCycle && !terminating) {
// Change state request from the outside
throw new Interrupted();
}
else {
// Spurious wake up. Just print a warning
System.out.println("Agent "+getName()+" interrupted while waiting");
}
}
setActiveState(AP_ACTIVE);
}
}
}
/**
Make a state transition from <em>waiting</em> to <em>active</em>
within Agent Platform Life Cycle. This method is called from
Agent Platform and resumes agent execution. Calling
<code>doWake()</code> when an agent is not waiting has no effect.
@see jade.core.Agent#doWait()
*/
public void doWake() {
synchronized(stateLock) {
int previous = myLifeCycle.getState();
if((previous == AP_WAITING) || (previous == AP_IDLE)) {
setActiveState(AP_ACTIVE);
}
}
if(myLifeCycle.isMessageAware()) {
activateAllBehaviours();
synchronized(msgQueue) {
msgQueue.notifyAll(); // Wakes up the embedded thread
}
}
}
/**
Make a state transition from <em>active</em>, <em>suspended</em>
or <em>waiting</em> to <em>deleted</em> state within Agent
Platform Life Cycle, thereby destroying the agent. This method
can be called either from the Agent Platform or from the agent
itself. Calling <code>doDelete()</code> on an already deleted
agent has no effect.
*/
public void doDelete() {
if (myDeletedLifeCycle == null) {
myDeletedLifeCycle = new DeletedLifeCycle();
}
changeStateTo(myDeletedLifeCycle);
}
// This is called only by the scheduler
void idle() throws InterruptedException {
setActiveState(AP_IDLE);
// No need for synchronized block since this is only called by the
// scheduler in the synchronized schedule() method
waitOn(myScheduler, 0);
setActiveState(AP_ACTIVE);
}
//#MIDP_EXCLUDE_BEGIN
/**
Write this agent to an output stream; this method can be used to
record a snapshot of the agent state on a file or to send it
through a network connection. Of course, the whole agent must
be serializable in order to be written successfully.
<br>
<b>NOT available in MIDP</b>
<br>
@param s The stream this agent will be sent to. The stream is
<em>not</em> closed on exit.
@exception IOException Thrown if some I/O error occurs during
writing.
@see jade.core.Agent#read(InputStream s)
*/
public void write(OutputStream s) throws IOException {
ObjectOutput out = new ObjectOutputStream(s);
out.writeUTF(myName);
out.writeObject(this);
}
/**
Read a previously saved agent from an input stream and restarts
it under its former name. This method can realize some sort of
mobility through time, where an agent is saved, then destroyed
and then restarted from the saved copy.
<br>
<b>NOT available in MIDP</b>
<br>
@param s The stream the agent will be read from. The stream is
<em>not</em> closed on exit.
@exception IOException Thrown if some I/O error occurs during
stream reading.
@see jade.core.Agent#write(OutputStream s)
*
public static void read(InputStream s) throws IOException {
try {
ObjectInput in = new ObjectInputStream(s);
String name = in.readUTF();
Agent a = (Agent)in.readObject();
a.doStart(name);
}
catch(ClassNotFoundException cnfe) {
cnfe.printStackTrace();
}
}*/
/**
Read a previously saved agent from an input stream and restarts
it under a different name. This method can realize agent cloning
through streams, where an agent is saved, then an exact copy of
it is restarted as a completely separated agent, with the same
state but with different identity and address.
<br>
<b>NOT available in MIDP</b>
<br>
@param s The stream the agent will be read from. The stream is
<em>not</em> closed on exit.
@param agentName The name of the new agent, copy of the saved
original one.
@exception IOException Thrown if some I/O error occurs during
stream reading.
@see jade.core.Agent#write(OutputStream s)
*
public static void read(InputStream s, String agentName) throws IOException {
try {
ObjectInput in = new ObjectInputStream(s);
String oldName = in.readUTF();
Agent a = (Agent)in.readObject();
a.doStart(agentName);
}
catch(ClassNotFoundException cnfe) {
cnfe.printStackTrace();
}
}*/
/**
This method reads a previously saved agent, replacing the current
state of this agent with the one previously saved. The stream
must contain the saved state of <b>the same agent</b> that it is
trying to restore itself; that is, <em>both</em> the Java object
<em>and</em> the agent name must be the same.
<br>
<b>NOT available in MIDP</b>
<br>
@param s The input stream the agent state will be read from.
@exception IOException Thrown if some I/O error occurs during
stream reading.
<em>Note: This method is currently not implemented</em>
*/
public void restore(InputStream s) throws IOException {
// FIXME: Not implemented
}
/**
This method should not be used by application code. Use the
same-named method of <code>jade.wrapper.Agent</code> instead.
<br>
<b>NOT available in MIDP</b>
<br>
@see jade.wrapper.AgentController#putO2AObject(Object o, boolean blocking)
*/
public void putO2AObject(Object o, boolean blocking) throws InterruptedException {
// Drop object on the floor if object-to-agent communication is
// disabled.
if(o2aQueue == null)
return;
CondVar cond = null;
// the following block is synchronized because o2aQueue.add and o2aLocks.put must be
// an atomic operation
synchronized (o2aQueue) {
// If the queue has a limited capacity and it is full, discard the
// first element
if((o2aQueueSize != 0) && (o2aQueue.size() == o2aQueueSize))
o2aQueue.remove(0);
o2aQueue.add(o);
// If we are going to block, then activate behaviours after storing the CondVar object
if(blocking) {
cond = new CondVar();
// Store lock for later, when getO2AObject will be called
o2aLocks.put(o, cond);
}
} // end synchronization
// Reactivate the O2AManager if any or the whole agent if no O2AManager is set.
if (o2aManager == null){
// This method is synchronized on the scheduler
activateAllBehaviours();
} else {
o2aManager.restart();
}
if (blocking)
// Sleep on the condition. This method is synchronized on the condvar
cond.waitOn();
}
/**
This method picks an object (if present) from the internal
object-to-agent communication queue. In order for this method to
work, the agent must have declared its will to accept objects
from other software components running within its JVM. This can
be achieved by calling the
<code>jade.core.Agent.setEnabledO2ACommunication()</code> method.
If the retrieved object was originally inserted by an external
component using a blocking call, that call will return during the
execution of this method.
<br>
<b>NOT available in MIDP</b>
<br>
@return the first object in the queue, or <code>null</code> if
the queue is empty.
@see jade.wrapper.AgentController#putO2AObject(Object o, boolean blocking)
@see jade.core.Agent#setEnabledO2ACommunication(boolean enabled, int queueSize)
*/
public Object getO2AObject() {
// Return 'null' if object-to-agent communication is disabled
if(o2aQueue == null)
return null;
CondVar cond = null;
Object result = null;
synchronized (o2aQueue) {
if(o2aQueue.isEmpty())
return null;
// Retrieve the first object from the object-to-agent
// communication queue
result = o2aQueue.remove(0);
// If some thread issued a blocking putO2AObject() call with this
// object, wake it up. cond.set is synchronized on CondVar object
cond = (CondVar)o2aLocks.remove(result);
}
if(cond != null) {
cond.set();
}
return result;
}
/**
This method declares this agent attitude towards object-to-agent
communication, that is, whether the agent accepts to communicate
with other non-JADE components living within the same JVM.
<br>
<b>NOT available in MIDP</b>
<br>
@param enabled Tells whether Java objects inserted with
<code>putO2AObject()</code> will be accepted.
@param queueSize If the object-to-agent communication is enabled,
this parameter specifiies the maximum number of Java objects that
will be queued. If the passed value is 0, no maximum limit is set
up for the queue.
@see jade.wrapper.AgentController#putO2AObject(Object o, boolean blocking)
@see getO2AObject()
*/
public void setEnabledO2ACommunication(boolean enabled, int queueSize) {
if(enabled) {
if(o2aQueue == null)
o2aQueue = new ArrayList(queueSize);
// Ignore a negative value
if(queueSize >= 0)
o2aQueueSize = queueSize;
}
else {
// Wake up all threads blocked in putO2AObject() calls
Iterator it = o2aLocks.values().iterator();
while(it.hasNext()) {
CondVar cv = (CondVar)it.next();
if (cv != null) cv.set();
}
o2aQueue = null;
}
}
/**
* Sets the behaviour responsible for managing objects passed to the agent by
* means of the Object-To-Agent (O2A) communication mechanism.
* If the O2A manager behaviour is set, whenever an object is inserted in the
* O2A queue by means of the <code>putO2AObject()</code> method, only the manager
* is waken up. This improves the efficiency since all behaviours not interested in
* O2A communication remain sleeping. <br>
* NOTE that this method only declares a behaviour as being responsible for managing
* objects received by the agent by means of the O2A communication channel; in order to
* correctly run, the behaviour must be added to the agent by means of the
* <code>addBehaviour()<code> method as usual.
<br>
<b>NOT available in MIDP</b>
<br>
* @param b The behaviour that will act as O2A manager.
*
* @see jade.wrapper.AgentController#putO2AObject(Object o, boolean blocking)
* @see getO2AObject()
*/
public void setO2AManager(Behaviour b) {
o2aManager = b;
}
//#MIDP_EXCLUDE_END
//#APIDOC_EXCLUDE_BEGIN
/**
This method is the main body of every agent. It
provides startup and cleanup hooks for application
programmers to put their specific code into.
@see jade.core.Agent#setup()
@see jade.core.Agent#takeDown()
*/
public final void run() {
try {
myLifeCycle.init();
while (myLifeCycle.alive()) {
try {
myLifeCycle.execute();
// Let other agents go on
Thread.yield();
}
catch (JADESecurityException jse) {
// FIXME: maybe we should send a message to the agent
System.out.println("JADESecurityException: "+jse.getMessage());
}
catch (InterruptedException ie) {
// Change LC state request from the outside. Just do nothing
// and let the new LC state do its job
}
catch (InterruptedIOException ie) {
// Change LC state request from the outside. Just do nothing
// and let the new LC state do its job
}
catch (Interrupted i) {
// Change LC state request from the outside. Just do nothing
// and let the new LC state do its job
}
}
}
catch(Throwable t) {
System.err.println("*** Uncaught Exception for agent " + myName + " ***");
t.printStackTrace();
}
terminating = true;
myLifeCycle.end();
}
//#APIDOC_EXCLUDE_END
/**
Inner class ActiveLifeCycle
*/
private class ActiveLifeCycle extends LifeCycle {
private static final long serialVersionUID = 11111;
private ActiveLifeCycle() {
super(AP_INITIATED);
}
public void setState(int s) {
myState = s;
}
public void init() {
setActiveState(AP_ACTIVE);
//#MIDP_EXCLUDE_BEGIN
notifyStarted();
//#MIDP_EXCLUDE_END
setup();
restarting = false;
}
public void execute() throws JADESecurityException, InterruptedException, InterruptedIOException {
// Select the next behaviour to execute
Behaviour currentBehaviour = myScheduler.schedule();
long oldRestartCounter = currentBehaviour.getRestartCounter();
// Just do it!
currentBehaviour.actionWrapper();
// When it is needed no more, delete it from the behaviours queue
if(currentBehaviour.done()) {
currentBehaviour.onEnd();
myScheduler.remove(currentBehaviour);
currentBehaviour = null;
}
else {
synchronized(myScheduler) {
// If the current Behaviour has blocked and it was restarted in the meanwhile
// (e.g. because a message arrived), restart the behaviour to give it another chance.
// Furthermore restart it even if it appears to be runnable since, due to the fact that block/restart
// events are managed in an un-synchronized way, we may end up in a situation where the root is runnable,
// but some of its childern are not.
if(oldRestartCounter != currentBehaviour.getRestartCounter()) {
currentBehaviour.handleRestartEvent();
}
// Need synchronized block (Crais Sayers, HP): What if
// 1) it checks to see if its runnable, sees its not,
// so it begins to enter the body of the if clause
// 2) meanwhile, in another thread, a message arrives, so
// the behaviour is restarted and moved to the ready list.
// 3) now back in the first thread, the agent executes the
// body of the if clause and, by calling block(), moves
// the behaviour back to the blocked list.
if(!currentBehaviour.isRunnable()) {
// Remove blocked behaviour from ready behaviours queue
// and put it in blocked behaviours queue
myScheduler.block(currentBehaviour);
currentBehaviour = null;
}
}
}
}
public void end() {
clean(false);
}
public boolean transitionTo(LifeCycle to) {
// We can go to whatever state unless we are terminating
if (!terminating) {
// The agent is going to leave this state. When
// the agent will enter this state again it must be
// in AP_ACTIVE
myState = AP_ACTIVE;
return true;
}
else {
return false;
}
}
public void transitionFrom(LifeCycle from) {
activateAllBehaviours();
}
public boolean isMessageAware() {
return true;
}
} // END of inner class ActiveLifeCycle
/**
Inner class DeletedLifeCycle
*/
private class DeletedLifeCycle extends LifeCycle {
private static final long serialVersionUID = 11112;
private DeletedLifeCycle() {
super(AP_DELETED);
}
public void end() {
clean(true);
}
public boolean alive() {
return false;
}
} // END of inner class DeletedLifeCycle
//#MIDP_EXCLUDE_BEGIN
/**
Inner class SuspendedLifeCycle
*/
private class SuspendedLifeCycle extends LifeCycle {
private static final long serialVersionUID = 11113;
private SuspendedLifeCycle() {
super(AP_SUSPENDED);
}
public void execute() throws JADESecurityException, InterruptedException, InterruptedIOException {
waitUntilActivate();
}
public void end() {
clean(false);
}
public boolean transitionTo(LifeCycle to) {
// We can only die or resume
return (to.getState() == AP_ACTIVE || to.getState() == AP_DELETED);
}
} // END of inner class SuspendedLifeCycle
//#MIDP_EXCLUDE_END
//#APIDOC_EXCLUDE_BEGIN
public void clean(boolean ok) {
if (!ok) {
System.out.println("ERROR: Agent " + myName + " died without being properly terminated !!!");
System.out.println("State was " + myLifeCycle.getState());
}
//#MIDP_EXCLUDE_BEGIN
// Reset the interrupted state of the Agent Thread
Thread.interrupted();
//#MIDP_EXCLUDE_END
myBufferedLifeCycle = myLifeCycle;
myLifeCycle = myActiveLifeCycle;
takeDown();
pendingTimers.clear();
myToolkit.handleEnd(myAID);
myLifeCycle = myBufferedLifeCycle;
}
//#APIDOC_EXCLUDE_END
/**
This protected method is an empty placeholder for application
specific startup code. Agent developers can override it to
provide necessary behaviour. When this method is called the agent
has been already registered with the Agent Platform <b>AMS</b>
and is able to send and receive messages. However, the agent
execution model is still sequential and no behaviour scheduling
is active yet.
This method can be used for ordinary startup tasks such as
<b>DF</b> registration, but is essential to add at least a
<code>Behaviour</code> object to the agent, in order for it to be
able to do anything.
@see jade.core.Agent#addBehaviour(Behaviour b)
@see jade.core.behaviours.Behaviour
*/
protected void setup() {}
/**
This protected method is an empty placeholder for application
specific cleanup code. Agent developers can override it to
provide necessary behaviour. When this method is called the agent
has not deregistered itself with the Agent Platform <b>AMS</b>
and is still able to exchange messages with other
agents. However, no behaviour scheduling is active anymore and
the Agent Platform Life Cycle state is already set to
<em>deleted</em>.
This method can be used for ordinary cleanup tasks such as
<b>DF</b> deregistration, but explicit removal of all agent
behaviours is not needed.
*/
protected void takeDown() {}
//#MIDP_EXCLUDE_BEGIN
/**
* This empty placeholder shall be overridden by user defined agents
* to execute some actions before the original agent instance on the
* source container is stopped (e.g. releasing local resources such
* as a GUI).<br>
* <b>IMPORTANT:</b> At this point, it is ensured that the move process
* is successful and that a moved agent instance has been created on the
* destination container
* Therefore setting the value of a class field in this method will have
* no impact on the moved agent instance. Such parameters must indeed be
* set <b>before</b> the <code>doMove()</code> method is called.
<br>
<b>NOT available in MIDP</b>
<br>
*/
protected void beforeMove() {}
/**
Actions to perform after moving. This empty placeholder method can be
overridden by user defined agents to execute some actions just after
arriving to the destination agent container for a migration.
<br>
<b>NOT available in MIDP</b>
<br>
*/
protected void afterMove() {}
/**
* This empty placeholder method shall be overridden by user defined agents
* to execute some actions before copying an agent to another agent container.
* <br>
* <b>NOT available in MIDP</b>
* <br>
* @see beforeMove()
* @see afterClone()
*/
protected void beforeClone() {}
/**
Actions to perform after cloning. This empty placeholder method can be
overridden by user defined agents to execute some actions just after
creating an agent copy to the destination agent container.
<br>
<b>NOT available in MIDP</b>
<br>
*/
protected void afterClone() {}
//#MIDP_EXCLUDE_END
// This method is used by the Agent Container to fire up a new agent for the first time
// Mutual exclusion with itself and Agent.addPlatformAddress()
synchronized void powerUp(AID id, Thread t) {
if (myThread == null) {
// Set this agent's name and address and start its embedded thread
myName = id.getLocalName();
myHap = id.getHap();
myAID = id;
myToolkit.setPlatformAddresses(myAID);
myThread = t;
myThread.start();
}
}
//#J2ME_EXCLUDE_BEGIN
// Return agent thread
// Package scooped as it is called by JadeMisc add-on for container monitor purpose
Thread getThread() {
return myThread;
}
//#J2ME_EXCLUDE_END
//#MIDP_EXCLUDE_BEGIN
private void writeObject(ObjectOutputStream out) throws IOException {
// Updates the queue maximum size field, before serialising
msgQueueMaxSize = msgQueue.getMaxSize();
out.defaultWriteObject();
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
// Restore transient fields apart from myThread, that will be set when the agent will be powered up)
stateLock = new Object();
suspendLock = new Object();
pendingTimers = new AssociationTB();
theDispatcher = TimerDispatcher.getTimerDispatcher();
// restore O2AQueue
if (o2aQueueSize > 0)
o2aQueue = new ArrayList(o2aQueueSize);
o2aLocks = new HashMap();
myToolkit = DummyToolkit.instance();
temporaryMessageQueue = true;
msgQueue = new InternalMessageQueue(msgQueueMaxSize);
//#PJAVA_EXCLUDE_BEGIN
//For persistence service
persistentPendingTimers = new java.util.HashSet();
//#PJAVA_EXCLUDE_END
}
//#MIDP_EXCLUDE_END
/**
This method is executed when blockingReceive() is called
from a separate Thread.
It does not affect the agent state.
*/
private void waitUntilWake(long millis) {
synchronized(msgQueue) {
try {
// Blocks on msgQueue monitor for a while
waitOn(msgQueue, millis);
}
catch (InterruptedException ie) {
throw new Interrupted();
}
}
}
//#MIDP_EXCLUDE_BEGIN
private void waitUntilActivate() throws InterruptedException {
synchronized(suspendLock) {
waitOn(suspendLock, 0);
}
}
//#MIDP_EXCLUDE_END
/**
This method adds a new behaviour to the agent. This behaviour
will be executed concurrently with all the others, using a
cooperative round robin scheduling. This method is typically
called from an agent <code>setup()</code> to fire off some
initial behaviour, but can also be used to spawn new behaviours
dynamically.
@param b The new behaviour to add to the agent.
@see jade.core.Agent#setup()
@see jade.core.behaviours.Behaviour
*/
public void addBehaviour(Behaviour b) {
b.setAgent(this);
myScheduler.add(b);
}
/**
This method removes a given behaviour from the agent. This method
is called automatically when a top level behaviour terminates,
but can also be called from a behaviour to terminate itself or
some other behaviour.
@param b The behaviour to remove.
@see jade.core.behaviours.Behaviour
*/
public void removeBehaviour(Behaviour b) {
myScheduler.remove(b);
b.setAgent(null);
}
/**
Send an <b>ACL</b> message to another agent. This methods sends
a message to the agent specified in <code>:receiver</code>
message field (more than one agent can be specified as message
receiver).
@param msg An ACL message object containing the actual message to
send.
@see jade.lang.acl.ACLMessage
*/
public final void send(ACLMessage msg) {
// Set the sender of the message if not yet set
// FIXME. Probably we should always set the sender of the message!
try {
msg.getSender().getName().charAt(0);
}
catch (Exception e) {
msg.setSender(myAID);
}
boolean cloneMessage = !("true".equals(msg.clearUserDefinedParameter(ACLMessage.NO_CLONE)));
myToolkit.handleSend(msg, myAID, cloneMessage);
}
/**
Receives an <b>ACL</b> message from the agent message
queue. This method is non-blocking and returns the first message
in the queue, if any. Therefore, polling and busy waiting is
required to wait for the next message sent using this method.
@return A new ACL message, or <code>null</code> if no message is
present.
@see jade.lang.acl.ACLMessage
*/
public final ACLMessage receive() {
return receive(null);
}
/**
Receives an <b>ACL</b> message matching a given template. This
method is non-blocking and returns the first matching message in
the queue, if any. Therefore, polling and busy waiting is
required to wait for a specific kind of message using this method.
@param pattern A message template to match received messages
against.
@return A new ACL message matching the given template, or
<code>null</code> if no such message is present.
@see jade.lang.acl.ACLMessage
@see jade.lang.acl.MessageTemplate
*/
public final ACLMessage receive(MessageTemplate pattern) {
ACLMessage msg = null;
synchronized (msgQueue) {
msg = msgQueue.receive(pattern);
//#MIDP_EXCLUDE_BEGIN
if (msg != null) {
myToolkit.handleReceived(myAID, msg);
}
//#MIDP_EXCLUDE_END
}
return msg;
}
/**
Receives an <b>ACL</b> message from the agent message
queue. This method is blocking and suspends the whole agent until
a message is available in the queue.
@return A new ACL message, blocking the agent until one is
available.
@see jade.core.Agent#receive()
@see jade.lang.acl.ACLMessage
*/
public final ACLMessage blockingReceive() {
return blockingReceive(null, 0);
}
/**
Receives an <b>ACL</b> message from the agent message queue,
waiting at most a specified amount of time.
@param millis The maximum amount of time to wait for the message.
@return A new ACL message, or <code>null</code> if the specified
amount of time passes without any message reception.
*/
public final ACLMessage blockingReceive(long millis) {
return blockingReceive(null, millis);
}
/**
Receives an <b>ACL</b> message matching a given message
template. This method is blocking and suspends the whole agent
until a message is available in the queue.
@param pattern A message template to match received messages
against.
@return A new ACL message matching the given template, blocking
until such a message is available.
@see jade.core.Agent#receive(MessageTemplate)
@see jade.lang.acl.ACLMessage
@see jade.lang.acl.MessageTemplate
*/
public final ACLMessage blockingReceive(MessageTemplate pattern) {
return blockingReceive(pattern, 0);
}
/**
Receives an <b>ACL</b> message matching a given message template,
waiting at most a specified time.
@param pattern A message template to match received messages
against.
@param millis The amount of time to wait for the message, in
milliseconds.
@return A new ACL message matching the given template, or
<code>null</code> if no suitable message was received within
<code>millis</code> milliseconds.
@see jade.core.Agent#blockingReceive()
*/
public final ACLMessage blockingReceive(MessageTemplate pattern, long millis) {
ACLMessage msg = null;
synchronized(msgQueue) {
msg = receive(pattern);
long timeToWait = millis;
while(msg == null) {
long startTime = System.currentTimeMillis();
if (Thread.currentThread().equals(myThread)) {
doWait(timeToWait);
}
else {
// blockingReceive() called from an external thread --> Do not change the agent state
waitUntilWake(timeToWait);
}
long elapsedTime = System.currentTimeMillis() - startTime;
msg = receive(pattern);
if(millis != 0) {
timeToWait -= elapsedTime;
if(timeToWait <= 0)
break;
}
}
}
return msg;
}
/**
Puts a received <b>ACL</b> message back into the message
queue. This method can be used from an agent behaviour when it
realizes it read a message of interest for some other
behaviour. The message is put in front of the message queue, so
it will be the first returned by a <code>receive()</code> call.
@see jade.core.Agent#receive()
*/
public final void putBack(ACLMessage msg) {
synchronized(msgQueue) {
msgQueue.addFirst(msg);
}
}
final void setToolkit(AgentToolkit at) {
myToolkit = at;
}
final void resetToolkit() {
//#MIDP_EXCLUDE_BEGIN
myToolkit = DummyToolkit.instance();
//#MIDP_EXCLUDE_END
/*#MIDP_INCLUDE_BEGIN
myToolkit = null;
#MIDP_INCLUDE_END*/
}
//#MIDP_EXCLUDE_BEGIN
//#APIDOC_EXCLUDE_BEGIN
/**
This method blocks until the agent has finished its start-up phase
(i.e. until just before its setup() method is called.
When this method returns, the target agent is registered with the
AMS and the JADE platform is aware of it.
*/
public synchronized void waitUntilStarted() {
while(myLifeCycle.getState() == AP_INITIATED) {
try {
wait();
}
catch(InterruptedException ie) {
// Do nothing...
}
}
}
//#APIDOC_EXCLUDE_END
// Notify creator that the start-up phase has completed
private synchronized void notifyStarted() {
notifyAll();
}
// Notify toolkit of the added behaviour
// Package scooped as it is called by the Scheduler
void notifyAddBehaviour(Behaviour b) {
if (generateBehaviourEvents) {
myToolkit.handleBehaviourAdded(myAID, b);
}
}
// Notify the toolkit of the removed behaviour
// Package scooped as it is called by the Scheduler
void notifyRemoveBehaviour(Behaviour b) {
if (generateBehaviourEvents) {
myToolkit.handleBehaviourRemoved(myAID, b);
}
}
//#APIDOC_EXCLUDE_BEGIN
// Notify the toolkit of the change in behaviour state
// Public as it is called by the Scheduler and by the Behaviour class
public void notifyChangeBehaviourState(Behaviour b, String from, String to) {
b.setExecutionState(to);
if (generateBehaviourEvents) {
myToolkit.handleChangeBehaviourState(myAID, b, from, to);
}
}
public void setGenerateBehaviourEvents(boolean b) {
generateBehaviourEvents = b;
}
//#APIDOC_EXCLUDE_END
// For persistence service
private boolean getGenerateBehaviourEvents() {
return generateBehaviourEvents;
}
// Notify toolkit that the current agent has changed its state
private void notifyChangedAgentState(int oldState, int newState) {
myToolkit.handleChangedAgentState(myAID, oldState, newState);
}
//#MIDP_EXCLUDE_END
private void activateAllBehaviours() {
myScheduler.restartAll();
}
/**
Put a received message into the agent message queue. The message
is put at the back end of the queue. This method is called by
JADE runtime system when a message arrives, but can also be used
by an agent, and is just the same as sending a message to oneself
(though slightly faster).
@param msg The ACL message to put in the queue.
@see jade.core.Agent#send(ACLMessage msg)
*/
public final void postMessage(final ACLMessage msg) {
synchronized (msgQueue) {
if (msg != null) {
//#MIDP_EXCLUDE_BEGIN
myToolkit.handlePosted(myAID, msg);
//#MIDP_EXCLUDE_END
msgQueue.addLast(msg);
doWake();
}
}
}
//#CUSTOM_EXCLUDE_BEGIN
private jade.content.ContentManager theContentManager = null;
/**
* Retrieves the agent's content manager
* @return The content manager.
*/
public jade.content.ContentManager getContentManager() {
if (theContentManager == null) {
theContentManager = new jade.content.ContentManager();
}
return theContentManager;
}
// All the agent's service helper
private transient Hashtable helpersTable;
/**
* Retrieves the agent's service helper
* @return The service helper.
*/
public ServiceHelper getHelper( String serviceName ) throws ServiceException {
ServiceHelper se = null;
if (helpersTable == null) {
helpersTable = new Hashtable();
}
se = (ServiceHelper) helpersTable.get(serviceName);
// is the helper already into the agent's helpersTable ?
if (se == null) {
// there isn't, request its creation
se = myToolkit.getHelper(this, serviceName);
if (se != null) {
se.init(this);
helpersTable.put(serviceName, se);
}
else {
throw new ServiceException("Null helper");
}
}
return se;
}
//#CUSTOM_EXCLUDE_END
/**
Retrieve a configuration property set in the <code>Profile</code>
of the local container (first) or as a System property.
@param key the key that maps to the property that has to be
retrieved.
@param aDefault a default value to be returned if there is no mapping
for <code>key</code>
*/
public String getProperty(String key, String aDefault) {
String val = myToolkit.getProperty(key, aDefault);
if (val == null || val.equals(aDefault)) {
// Try among the System properties
String sval = System.getProperty(key);
if (sval != null) {
val = sval;
}
}
return val;
}
//#MIDP_EXCLUDE_BEGIN
/**
* Return the configuration properties exactly as they were passed to the Profile before
* starting the local JADE container.
*/
public Properties getBootProperties() {
return myToolkit.getBootProperties();
}
//#MIDP_EXCLUDE_END
/**
This method is used to interrupt the agent's thread.
In J2SE/PJAVA it just calls myThread.interrupt(). In MIDP,
where interrupt() is not supported the thread interruption is
simulated as described below.
The agent thread can be in one of the following three states:
1) Running a behaviour.
2) Sleeping on msgQueue due to a doWait()
3) Sleeping on myScheduler due to a schedule() with no active behaviours
Note that in MIDP the suspended state is not supported
The idea is: set the 'isInterrupted' flag, then wake up the
thread wherever it may be
*/
private void interruptThread() {
//#MIDP_EXCLUDE_BEGIN
myThread.interrupt();
//#MIDP_EXCLUDE_END
/*#MIDP_INCLUDE_BEGIN
synchronized (this) {
isInterrupted = true;
// case 1: Nothing to do.
// case 2: Signal on msgQueue.
synchronized (msgQueue) {msgQueue.notifyAll();}
// case 3: Signal on the Scheduler
synchronized (myScheduler) {myScheduler.notifyAll();}
}
#MIDP_INCLUDE_END*/
}
/**
Since in MIDP Thread.interrupt() does not exist and a simulated
interruption is used to "interrupt" the agent's thread, we must
check whether the simulated interruption happened just before and
after going to sleep.
*/
void waitOn(Object lock, long millis) throws InterruptedException {
/*#MIDP_INCLUDE_BEGIN
synchronized (this) {
if (isInterrupted) {
isInterrupted = false;
throw new InterruptedException();
}
}
#MIDP_INCLUDE_END*/
lock.wait(millis);
/*#MIDP_INCLUDE_BEGIN
synchronized (this) {
if (isInterrupted) {
isInterrupted = false;
throw new InterruptedException();
}
}
#MIDP_INCLUDE_END*/
}
//#J2ME_EXCLUDE_BEGIN
// For persistence service -- Hibernate needs java.util collections
private java.util.Set getBehaviours() {
Behaviour[] behaviours = myScheduler.getBehaviours();
java.util.Set result = new java.util.HashSet();
result.addAll(java.util.Arrays.asList(behaviours));
return result;
}
// For persistence service -- Hibernate needs java.util collections
private void setBehaviours(java.util.Set behaviours) {
Behaviour[] arr = new Behaviour[behaviours.size()];
arr = (Behaviour[])behaviours.toArray(arr);
// Reconnect all the behaviour -> agent pointers
for(int i = 0; i < arr.length; i++) {
arr[i].setAgent(this);
}
myScheduler.setBehaviours(arr);
}
// For persistence service -- Hibernate needs java.util collections
private transient java.util.Set persistentPendingTimers = new java.util.HashSet();
// For persistence service -- Hibernate needs java.util collections
private java.util.Set getPendingTimers() {
return persistentPendingTimers;
}
// For persistence service -- Hibernate needs java.util collections
private void setPendingTimers(java.util.Set timers) {
if(!persistentPendingTimers.equals(timers)) {
// Clear the timers table, and install the new timers.
pendingTimers.clear();
java.util.Iterator it = timers.iterator();
while(it.hasNext()) {
TBPair pair = (TBPair)it.next();
pendingTimers.addPair(pair);
}
}
persistentPendingTimers = timers;
}
//#J2ME_EXCLUDE_END
}