PlanInstance.java

343 lines | 10.285 kB Blame History Raw Download
//----------------------------------------------------------------------------
// 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/~ingridnunes/bdi4jade/
//
//----------------------------------------------------------------------------

package bdi4jade.plan;

import jade.core.behaviours.Behaviour;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import bdi4jade.core.BeliefBase;
import bdi4jade.core.Intention;
import bdi4jade.event.GoalEvent;
import bdi4jade.event.GoalFinishedEvent;
import bdi4jade.event.GoalListener;
import bdi4jade.exception.PlanInstantiationException;
import bdi4jade.goal.Goal;

/**
 * This class represents a plan that has been instantiated to be executed, with
 * an associated plan body (a behavior).
 * 
 * @author ingrid
 */
public class PlanInstance implements GoalListener {

	/**
	 * This enumuration represents the possible end states of a plan execution.
	 * 
	 * @author ingrid
	 */
	public enum EndState {
		FAILED, SUCCESSFUL;
	}

	private final Behaviour behaviour;
	private EndState endState;
	private final List<GoalFinishedEvent> goalEventQueue;
	private final Intention intention;
	private final Plan plan;
	private final List<Goal> subgoals;

	/**
	 * Creates a new plan instance. It is associated with a plan definition (
	 * {@link Plan}) and an {@link Intention}. It creates an instance of the
	 * plan body defined in the plan and throws an exception if an error occurs
	 * in this process.
	 * 
	 * @param plan
	 *            the plan associated this this plan instance.
	 * @param intention
	 *            the intention that this plan instance have to achieve.
	 * @throws PlanBodyInstantiationException
	 *             in an error occurred during the instantiation.
	 */
	public PlanInstance(Plan plan, Intention intention)
			throws PlanInstantiationException {
		this.plan = plan;
		this.intention = intention;
		this.subgoals = new ArrayList<Goal>();
		this.behaviour = plan.createPlanBody();
		if (!(behaviour instanceof PlanBody)) {
			throw new PlanInstantiationException(
					"instantiateBehavior() does not return a behaviour that implements PlanBody");
		}
		PlanBody planBody = (PlanBody) behaviour;
		planBody.init(this);
		this.endState = null;
		this.goalEventQueue = new LinkedList<GoalFinishedEvent>();
	}

	/**
	 * Dispatches a goal to be achieved, using the capability (or its children
	 * capabilities) associated with the plan.
	 * 
	 * @param goal
	 *            the goal to be dispatched.
	 */
	public void dispatchProtectedGoal(Goal goal) {
		this.intention.getMyAgent().addGoal(
				this.plan.getPlanLibrary().getCapability(), goal);
	}

	/**
	 * Dispatches a subgoal to be achieved, using the capability (or its
	 * children capabilities) associated with the plan.
	 * 
	 * @param subgoal
	 *            the subgoal to be dispatched.
	 */
	public void dispatchProtectedSubgoal(Goal subgoal) {
		this.intention.getMyAgent().addGoal(
				this.plan.getPlanLibrary().getCapability(), subgoal);
		synchronized (subgoals) {
			this.subgoals.add(subgoal);
		}
	}

	/**
	 * Dispatches a subgoal to be achieved, using the capability (or its
	 * children capabilities) associated with the plan, and registers itself as
	 * a listener to receive a notification of the end of execution of the goal.
	 * 
	 * @param subgoal
	 *            the subgoal to be dispatched.
	 */
	public void dispatchProtectedSubgoalAndListen(Goal subgoal) {
		this.intention.getMyAgent().addGoal(
				this.plan.getPlanLibrary().getCapability(), subgoal, this);
		synchronized (subgoals) {
			this.subgoals.add(subgoal);
		}
	}

	/**
	 * Dispatches a goal to be achieved.
	 * 
	 * @param goal
	 *            the goal to be dispatched.
	 */
	public void dispatchGoal(Goal goal) {
		this.intention.getMyAgent().addGoal(goal);
	}

	/**
	 * Dispatches a subgoal to be achieved.
	 * 
	 * @param subgoal
	 *            the subgoal to be dispatched.
	 */
	public void dispatchSubgoal(Goal subgoal) {
		this.intention.getMyAgent().addGoal(subgoal);
		synchronized (subgoals) {
			this.subgoals.add(subgoal);
		}
	}

	/**
	 * Dispatches a subgoal to be achieved and registers itself as a listener to
	 * receive a notification of the end of execution of the goal.
	 * 
	 * @param subgoal
	 *            the subgoal to be dispatched.
	 */
	public void dispatchSubgoalAndListen(Goal subgoal) {
		this.intention.getMyAgent().addGoal(subgoal, this);
		synchronized (subgoals) {
			this.subgoals.add(subgoal);
		}
	}

	/**
	 * Drops all current subgoals dispatched by this plan.
	 */
	private void dropSubgoals() {
		synchronized (subgoals) {
			Iterator<Goal> it = subgoals.iterator();
			while (it.hasNext()) {
				Goal subgoal = it.next();
				this.intention.getMyAgent().setNoLongerDesired(subgoal);
				it.remove();
			}
		}
	}

	/**
	 * Returns the belief base of the capability.
	 * 
	 * @return the belief base containing the beliefs.
	 */
	public BeliefBase getBeliefBase() {
		return this.plan.getPlanLibrary().getCapability().getBeliefBase();
	}

	/**
	 * Returns the end state of plan. A null value means that the plan is still
	 * in execution. 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). If the plan body has come to a failed state, it sets all of its
	 * subgoals as no longer desired.
	 * 
	 * @return the end state of the plan.
	 */
	public EndState getEndState() {
		if (this.endState == null) {
			this.endState = ((PlanBody) behaviour).getEndState();
			if (this.endState != null) {
				if (behaviour instanceof OutputPlanBody) {
					((OutputPlanBody) behaviour).setGoalOutput(getGoal());
				}
				if (EndState.FAILED.equals(this.endState)) {
					dropSubgoals();
				}
			}
		}
		return endState;
	}

	/**
	 * Returns the goal to be achieved by this plan instance.
	 * 
	 * @return the goal.
	 */
	public Goal getGoal() {
		return this.intention.getGoal();
	}

	/**
	 * Returns a goal event from the queue. If the queue is empty, the behavior
	 * associated with this plan instance is blocked.
	 * 
	 * @return the goal event or null if the queue is empty.
	 */
	public GoalFinishedEvent getGoalEvent() {
		return getGoalEvent(true, -1);
	}

	/**
	 * Returns a goal event from the queue. If the queue is empty, the behavior
	 * associated with this plan instance is going to be blocked if the
	 * parameter passed to this method is true.
	 * 
	 * @param block
	 *            true if the behavior must be blocked if the queue is empty.
	 * @return the goal event or null if the queue is empty.
	 */
	public GoalFinishedEvent getGoalEvent(boolean block) {
		return getGoalEvent(block, -1);
	}

	/**
	 * Returns a goal event from the queue. If the block parameter is true, the
	 * behavior associated with this plan instance is going to be blocked if the
	 * queue is empty according to the specified milliseconds. specified
	 * milliseconds. If the time is lower then zero, the behavior is going to be
	 * blocked until an event happens ({@link Behaviour#block()}).
	 * 
	 * @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.
	 */
	private GoalFinishedEvent getGoalEvent(boolean block, long ms) {
		synchronized (goalEventQueue) {
			if (!this.goalEventQueue.isEmpty()) {
				return this.goalEventQueue.remove(0);
			} else {
				if (this.behaviour != null && block) {
					if (ms < 0) {
						this.behaviour.block();
					} else {
						this.behaviour.block(ms);
					}
				}
				return null;
			}
		}
	}

	/**
	 * Returns a goal event from the queue. If the queue is empty, the behavior
	 * associated with this plan instance is going to be 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 GoalFinishedEvent getGoalEvent(long ms) {
		return getGoalEvent(true, ms);
	}

	/**
	 * Returns the {@link Plan} that is associated with this plan instance.
	 * 
	 * @return the plan.
	 */
	public Plan getPlan() {
		return plan;
	}

	/**
	 * @see bdi4jade.event.GoalListener#goalPerformed(bdi4jade.event.GoalEvent)
	 */
	@Override
	public synchronized void goalPerformed(GoalEvent event) {
		if (event instanceof GoalFinishedEvent) {
			synchronized (goalEventQueue) {
				this.goalEventQueue.add((GoalFinishedEvent) event);
				if (this.behaviour != null)
					this.behaviour.restart();
			}
			synchronized (subgoals) {
				this.subgoals.remove(event.getGoal());
			}
		}
	}

	/**
	 * Starts the plan body, a {@link Behaviour}, associated with this plan.
	 */
	public void startPlan() {
		this.intention.getMyAgent().addBehaviour(this.behaviour);
	}

	/**
	 * Stops the plan body, a {@link Behaviour}, associated with this plan. If
	 * the body implements the {@link DisposablePlanBody}, it invokes the method
	 * to about the plan body, so it can perform finalizations.
	 */
	public void stopPlan() {
		dropSubgoals();
		this.intention.getMyAgent().removeBehaviour(behaviour);
		if (behaviour instanceof DisposablePlanBody) {
			((DisposablePlanBody) behaviour).onAbort();
		}
	}

}