//----------------------------------------------------------------------------
// Copyright (C) 2011 Ingrid Nunes
//
// 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; either
// version 2.1 of the License, or (at your option) any later version.
//
// 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
//
// To contact the authors:
// http://inf.ufrgs.br/prosoft/bdi4jade/
//
//----------------------------------------------------------------------------
package bdi4jade.plan.planbody;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import bdi4jade.belief.BeliefBase;
import bdi4jade.core.Capability;
import bdi4jade.core.Intention;
import bdi4jade.event.GoalEvent;
import bdi4jade.event.GoalListener;
import bdi4jade.exception.ParameterException;
import bdi4jade.exception.PlanInstantiationException;
import bdi4jade.goal.Goal;
import bdi4jade.goal.NestedGoal;
import bdi4jade.plan.Plan;
import bdi4jade.plan.Plan.EndState;
import bdi4jade.util.ReflectionUtils;
import jade.core.behaviours.Behaviour;
/**
* This class provides an almost complete implementation of the {@link PlanBody}
* interface. It represents a plan that has been instantiated to be executed.
*
* @author Ingrid Nunes
*/
public abstract class AbstractPlanBody extends Behaviour implements PlanBody {
private static final long serialVersionUID = -6488256636028800227L;
private static final Log log = LogFactory.getLog(AbstractPlanBody.class);
private EndState endState;
private final List<GoalEvent> goalEventQueue;
private Intention intention;
private Plan plan;
private final List<Goal> subgoals;
/**
* Creates a new plan body.
*/
public AbstractPlanBody() {
this.plan = null;
this.intention = null;
this.endState = null;
this.subgoals = new ArrayList<Goal>();
this.goalEventQueue = new LinkedList<>();
}
/**
* @see PlanBody#dispatchGoal(Goal)
*/
public boolean dispatchGoal(Goal goal) {
return this.intention.getMyAgent().addGoal(this.plan.getPlanLibrary().getCapability(), goal);
}
/**
* @see PlanBody#dispatchSubgoal(Goal)
*/
public boolean dispatchSubgoal(Goal subgoal) {
boolean goalAdded = this.intention.getMyAgent().addGoal(this.plan.getPlanLibrary().getCapability(), subgoal);
synchronized (subgoals) {
if (goalAdded)
this.subgoals.add(subgoal);
}
return goalAdded;
}
/**
* @see PlanBody#dispatchSubgoalAndListen(Goal)
*/
public boolean dispatchSubgoalAndListen(Goal subgoal) {
boolean goalAdded = this.intention.getMyAgent().addGoal(this.plan.getPlanLibrary().getCapability(), subgoal,
this);
synchronized (subgoals) {
if (goalAdded)
this.subgoals.add(subgoal);
}
return goalAdded;
}
/**
* Indicates to the JADE platform that this behavior/plan body finished its
* execution. If {@link #getEndState()} returns null, it returns false, as
* the plan body has not reached a final state. It returns true otherwise.
*
* @return false if {@link #getEndState()} returns null, true otherwise.
*
* @see jade.core.behaviours.Behaviour#done()
*/
@Override
public final boolean done() {
synchronized (plan) {
return getEndState() != null;
}
}
/**
* Drops all current subgoals dispatched by this plan.
*/
void dropSubgoals() {
synchronized (subgoals) {
Iterator<Goal> it = subgoals.iterator();
while (it.hasNext()) {
Goal subgoal = it.next();
this.intention.getMyAgent().dropGoal(subgoal);
it.remove();
}
}
}
/**
* @see PlanBody#getBeliefBase()
*/
public BeliefBase getBeliefBase() {
return this.plan.getPlanLibrary().getCapability().getBeliefBase();
}
/**
* @see PlanBody#getCapability()
*/
public Capability getCapability() {
return this.plan.getPlanLibrary().getCapability();
}
/**
* Returns the end state of plan. A null value means that the plan is still
* executing.
*
* @return the end state of the plan.
*/
public EndState getEndState() {
synchronized (plan) {
return endState;
}
}
/**
* Returns the goal to be achieved by this plan instance.
*
* @return the goal.
*/
public final Goal getGoal() {
if (this.intention.getGoal() instanceof NestedGoal) {
return ((NestedGoal) this.intention.getGoal()).getGoal();
} else {
return this.intention.getGoal();
}
}
/**
* Returns a goal event from the queue. If the queue is empty, the plan body
* execution is blocked.
*
* @return the goal event or null if the queue is empty.
*/
public GoalEvent getGoalEvent() {
return getGoalEvent(true, -1);
}
/**
* Returns a goal event from the queue. If the queue is empty, the plan body
* execution is blocked if the parameter passed to this method is true.
*
* @param block
* true if the plan body must be blocked if the queue is empty.
* @return the goal event or null if the queue is empty.
*/
public GoalEvent getGoalEvent(boolean block) {
return getGoalEvent(block, -1);
}
/**
* Returns a goal event from the queue. If the block parameter is true, the
* plan body execution is blocked if the queue is empty according to the
* specified milliseconds. If the time is lower then zero, the plan body is
* going to be blocked until an event happens.
*
* @param block
* true if the behavior must be blocked if the queue is empty.
* @param ms
* the maximum amount of time that the behavior must be blocked.
* @return the goal event or null if the queue is empty.
*
* @see Behaviour#block()
* @see Behaviour#block(long)
*/
private GoalEvent getGoalEvent(boolean block, long ms) {
synchronized (goalEventQueue) {
if (!this.goalEventQueue.isEmpty()) {
return this.goalEventQueue.remove(0);
} else {
if (block) {
if (ms < 0) {
block();
} else {
block(ms);
}
}
return null;
}
}
}
/**
* Returns a goal event from the queue. If the queue is empty, the plan body
* execution is blocked for the specified milliseconds.
*
* @param ms
* the maximum amount of time that the behavior must be blocked.
* @return the goal event or null if the queue is empty.
*/
public GoalEvent getGoalEvent(long ms) {
return getGoalEvent(true, ms);
}
/**
* Returns the intention associated with the goal that triggered this plan
* pdy execution.
*
* @return the intention
*/
Intention getIntention() {
return intention;
}
/**
* Returns the {@link Plan} that is associated with this plan body.
*
* @return the plan.
*/
public final Plan getPlan() {
return plan;
}
/**
* Receives the notification that a goal event has occurred. If the event
* has a finished status, it is added to the event queue, which can be
* retrieved by invoking the {@link #getGoalEvent()} method, and restarts
* the plan body execution.
*
* @see GoalListener#goalPerformed(bdi4jade.event.GoalEvent)
*/
@Override
public synchronized void goalPerformed(GoalEvent event) {
if (event.getStatus().isFinished()) {
synchronized (goalEventQueue) {
this.goalEventQueue.add(event);
restart();
}
synchronized (subgoals) {
this.subgoals.remove(event.getGoal());
}
}
}
/**
* Initializes this plan body. It associates this plan body with a plan
* definition ({@link Plan}) and an {@link Intention}. If this plan body has
* already been initialized, this method throws a
* {@link PlanInstantiationException}. It also sets up the plan input
* parameters based on the goal input parameters.
*
* @param plan
* the plan associated this this plan body.
* @param intention
* the intention that this plan instance have to achieve.
* @throws PlanInstantiationException
* if this plan body has already been initialized.
*/
public final void init(Plan plan, Intention intention) throws PlanInstantiationException {
if (this.plan != null || this.intention != null) {
throw new PlanInstantiationException("This plan body has already been initialized.");
}
this.plan = plan;
this.intention = intention;
try {
ReflectionUtils.setPlanBodyInput(this, getGoal());
ReflectionUtils.setupBeliefs(this);
} catch (ParameterException exc) {
throw new PlanInstantiationException(exc);
}
}
/**
* Sets the end state of plan. A null value means that the plan is still
* executing.
*
* If the plan body has come to an end state, it invokes the method to set
* the output parameters of the goal, in case the plan body implements the
* {@link OutputPlanBody} interface (this is invoked only once), or sets up
* the goal inputs parameters based on the plan body output parameters. If
* an error occurs during this setting process, a warn is shown, but no
* exception is thrown.
*
* If the plan body has come to an end state, it drops all subgoals, in case
* they are still trying to be achieved.
*
* @param endState
* the endState to set.
*/
protected final void setEndState(EndState endState) {
synchronized (plan) {
this.endState = endState;
if (this.endState != null) {
if (this instanceof OutputPlanBody) {
((OutputPlanBody) this).setGoalOutput(getGoal());
} else {
try {
ReflectionUtils.setPlanBodyOutput(this, getGoal());
} catch (ParameterException exc) {
log.warn("Could not set all goal outputs: " + exc);
}
}
dropSubgoals();
}
}
}
/**
* Starts the plan body, by adding it as to the agent as a {@link Behaviour}
* .
*/
public final void start() {
this.intention.getMyAgent().addBehaviour(this);
}
/**
* Stops the plan body execution. It drops all plan body subgoals. If the
* body implements the {@link DisposablePlanBody}, it invokes the method to
* about the plan body, so it can perform finalizations.
*/
public final void stop() {
dropSubgoals();
this.intention.getMyAgent().removeBehaviour(this);
if (this instanceof DisposablePlanBody) {
((DisposablePlanBody) this).onAbort();
}
}
}