Package jade.core

Source Code of jade.core.Agent

/*****************************************************************
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
}
TOP

Related Classes of jade.core.Agent

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.