bdi4jade.core.AbstractBDIAgent.java Source code

Java tutorial

Introduction

Here is the source code for bdi4jade.core.AbstractBDIAgent.java

Source

//----------------------------------------------------------------------------
// 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.core;

import bdi4jade.belief.Belief;
import bdi4jade.core.GoalUpdateSet.GoalDescription;
import bdi4jade.event.GoalEvent;
import bdi4jade.event.GoalListener;
import bdi4jade.goal.Goal;
import bdi4jade.goal.GoalStatus;
import bdi4jade.goal.Softgoal;
import bdi4jade.message.BDIAgentMsgReceiver;
import bdi4jade.reasoning.*;
import bdi4jade.util.ReflectionUtils;
import jade.core.Agent;
import jade.core.behaviours.CyclicBehaviour;
import jade.lang.acl.ACLMessage;
import jade.proto.states.MsgReceiver;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.*;

/**
 * This class is an abstract implementation of the {@link BDIAgent} interface.
 * It is an extension of {@link Agent}. It also has a set of {@link Capability}
 * - an agent is an aggregation of capabilities, and a {@link MsgReceiver}
 * behavior to receive all messages that the agent current plans can process.
 * 
 * @author Ingrid Nunes
 */
public abstract class AbstractBDIAgent extends Agent implements BDIAgent {

    /**
     * This class is a {@link CyclicBehaviour} that runs during all the
     * {@link BDIAgent} life in order to provide the reasoning engine.
     * 
     * @author Ingrid Nunes
     */
    class BDIInterpreter extends CyclicBehaviour {

        private static final long serialVersionUID = -6991759791322598475L;

        private BDIInterpreter(BDIAgent bdiAgent) {
            super((Agent) bdiAgent);
        }

        /**
         * This method is a variation of the BDI-interpreter cycle of the BDI
         * architecture. It first reviews the beliefs of this agent, by invoking
         * the {@link AgentBeliefRevisionStrategy#reviewBeliefs()} method.
         * 
         * After it removes from the intention set the ones that are finished,
         * i.e. associated with goals with status achieved, no longer desired or
         * unachievable, and notifies goal listeners about this event. This is
         * performed using the {@link #processIntentions(Collection)} method.
         * 
         * Then, it generate a an updated set of goals, dropping existing ones
         * that are no longer desired and also creating new ones. This updated
         * set of goals is given by the
         * {@link AgentOptionGenerationFunction#generateGoals(GoalUpdateSet, Map)}
         * method.
         * 
         * Finally, from the set of current goals, they are now filtered, by
         * invoking the {@link AgentDeliberationFunction#filter(Set, Map)}
         * method, to select the current agent intentions. The non-selected
         * goals will be set to wait ({@link Intention#doWait()}) and the
         * selected ones will be tried to be achieved (
         * {@link Intention#tryToAchive()}).
         * 
         * @see jade.core.behaviours.Behaviour#run()
         */
        @Override
        public void run() {
            log.trace("Beginning BDI-interpreter cycle.");

            log.trace("Reviewing beliefs.");
            beliefRevisionStrategy.reviewBeliefs();

            synchronized (allIntentions) {
                // Removing finished goals and generate appropriate goal events
                GoalUpdateSet agentGoalUpdateSet = processIntentions(agentIntentions);
                Map<Capability, GoalUpdateSet> capabilityGoalUpdateSets = new HashMap<>();
                for (Capability capability : capabilities) {
                    GoalUpdateSet capabilityGoalUpdateSet = processIntentions(capability.getIntentions());
                    capabilityGoalUpdateSets.put(capability, capabilityGoalUpdateSet);
                }

                // Generating new goals and choosing goals to drop
                optionGenerationFunction.generateGoals(agentGoalUpdateSet, capabilityGoalUpdateSets);

                // Adding generated goals
                for (GoalDescription goal : agentGoalUpdateSet.getGeneratedGoals()) {
                    try {
                        Intention intention = addIntention(goal.getDispatcher(), goal.getGoal(),
                                goal.getListener());
                        if (intention != null)
                            agentGoalUpdateSet.addIntention(intention);
                    } catch (IllegalAccessException exc) {
                        log.error(exc);
                    }
                }
                for (GoalUpdateSet goalUpdateSet : capabilityGoalUpdateSets.values()) {
                    for (GoalDescription goal : goalUpdateSet.getGeneratedGoals()) {
                        try {
                            Intention intention = addIntention(goal.getDispatcher(), goal.getGoal(),
                                    goal.getListener());
                            if (intention != null)
                                goalUpdateSet.addIntention(intention);
                        } catch (IllegalAccessException exc) {
                            log.error(exc);
                        }
                    }
                }

                // Removing dropped goals
                for (GoalDescription goal : agentGoalUpdateSet.getDroppedGoals()) {
                    goal.getIntention().noLongerDesire();
                    fireGoalEvent(goal.getIntention());
                    agentIntentions.remove(goal.getIntention());
                    allIntentions.remove(goal.getGoal());
                    agentGoalUpdateSet.removeIntention(goal);
                }
                for (GoalUpdateSet goalUpdateSet : capabilityGoalUpdateSets.values()) {
                    for (GoalDescription goal : goalUpdateSet.getDroppedGoals()) {
                        goal.getIntention().noLongerDesire();
                        fireGoalEvent(goal.getIntention());
                        goal.getDispatcher().removeIntention(goal.getIntention());
                        allIntentions.remove(goal.getGoal());
                        goalUpdateSet.removeIntention(goal);
                    }
                }

                // Filtering options
                Map<Capability, Set<GoalDescription>> capabilityGoals = new HashMap<>();
                for (Capability capability : capabilityGoalUpdateSets.keySet()) {
                    capabilityGoals.put(capability, capabilityGoalUpdateSets.get(capability).getCurrentGoals());
                }
                Set<Goal> selectedGoals = deliberationFunction.filter(agentGoalUpdateSet.getCurrentGoals(),
                        capabilityGoals);

                log.trace("Selected goals to be intentions: " + selectedGoals.size());
                for (Intention intention : allIntentions.values()) {
                    if (selectedGoals.contains(intention.getGoal())) {
                        intention.tryToAchive();
                    } else {
                        intention.doWait();
                    }
                }

                if (allIntentions.isEmpty()) {
                    log.trace("No goals or intentions: blocking cycle.");
                    this.block();
                }
            }

            log.trace("BDI-interpreter cycle finished.");
        }

        /**
         * Processes all intentions of the given collection. Intentions
         * associated with goals that finished are removed and goal listeners (
         * {@link GoalListener}) are notified. Goal listeners are also notified
         * if a plan failed while trying to achieve a goal (intentions with
         * {@link GoalStatus#PLAN_FAILED}).
         * 
         * @param intentions
         *            the collection of intentions to be processed.
         * @return the {@link GoalUpdateSet} with current goals initialized with
         *         current intentions.
         */
        private GoalUpdateSet processIntentions(Collection<Intention> intentions) {
            GoalUpdateSet goalUpdateSet = new GoalUpdateSet();
            List<Intention> intentionsList = new ArrayList<>(intentions);
            for (Intention intention : intentionsList) {
                GoalStatus status = intention.getStatus();
                if (status.isFinished()) {
                    fireGoalEvent(intention);
                    intentions.remove(intention);
                    allIntentions.remove(intention.getGoal());
                } else {
                    if (GoalStatus.PLAN_FAILED.equals(status)) {
                        fireGoalEvent(intention);
                    }
                    goalUpdateSet.addIntention(intention);
                }
            }
            return goalUpdateSet;
        }

    }

    private static final Log log = LogFactory.getLog(AbstractBDIAgent.class);
    private static final long serialVersionUID = -841774495336214256L;

    private final Collection<Intention> agentIntentions;
    private final Set<Capability> aggregatedCapabilities;
    private final Map<Goal, Intention> allIntentions;
    private final BDIInterpreter bdiInterpreter;
    private AgentBeliefRevisionStrategy beliefRevisionStrategy;
    private Set<Capability> capabilities;
    private AgentDeliberationFunction deliberationFunction;
    protected final List<GoalListener> goalListeners;
    private AgentOptionGenerationFunction optionGenerationFunction;
    private AgentPlanSelectionStrategy planSelectionStrategy;
    private Map<Class<? extends Capability>, Set<Capability>> restrictedAccessOwnersMap;
    private final Set<Softgoal> softgoals;

    /**
     * Default constructor.
     */
    public AbstractBDIAgent() {
        this.bdiInterpreter = new BDIInterpreter(this);
        this.capabilities = new HashSet<>();
        this.restrictedAccessOwnersMap = new HashMap<>();
        this.allIntentions = new HashMap<>();
        this.aggregatedCapabilities = new HashSet<>();
        this.agentIntentions = new LinkedList<>();
        this.softgoals = new HashSet<>();
        this.goalListeners = new LinkedList<>();

        // Initializing reasoning strategies
        setBeliefRevisionStrategy(null);
        setOptionGenerationFunction(null);
        setDeliberationFunction(null);
        setPlanSelectionStrategy(null);
    }

    /**
     * Adds a capability to this agent.
     * 
     * @param capability
     *            capability to be added.
     */
    void addCapability(Capability capability) {
        synchronized (aggregatedCapabilities) {
            this.aggregatedCapabilities.add(capability);
            resetAllCapabilities();
            computeGoalOwnersMap();
        }
    }

    /**
     * @see bdi4jade.core.BDIAgent#addGoal(bdi4jade.core.Capability,
     *      bdi4jade.goal.Goal)
     */
    @Override
    public final boolean addGoal(Capability dispatcher, Goal goal) {
        try {
            addIntention(dispatcher, goal, null);
            return true;
        } catch (IllegalAccessException exc) {
            log.error(exc);
            return false;
        }
    }

    /**
     * @see bdi4jade.core.BDIAgent#addGoal(bdi4jade.core.Capability,
     *      bdi4jade.goal.Goal, bdi4jade.event.GoalListener)
     */
    @Override
    public final boolean addGoal(Capability dispatcher, Goal goal, GoalListener goalListener) {
        try {
            addIntention(dispatcher, goal, goalListener);
            return true;
        } catch (IllegalAccessException exc) {
            log.error(exc);
            return false;
        }
    }

    /**
     * @see bdi4jade.core.BDIAgent#addGoal(bdi4jade.goal.Goal)
     */
    @Override
    public final boolean addGoal(Goal goal) {
        try {
            addIntention(null, goal, null);
            return true;
        } catch (IllegalAccessException exc) {
            log.error(exc);
            return false;
        }
    }

    /**
     * @see bdi4jade.core.BDIAgent#addGoal(bdi4jade.goal.Goal,
     *      bdi4jade.event.GoalListener)
     */
    @Override
    public final boolean addGoal(Goal goal, GoalListener goalListener) {
        try {
            addIntention(null, goal, goalListener);
            return true;
        } catch (IllegalAccessException exc) {
            log.error(exc);
            return false;
        }
    }

    /**
     * @see bdi4jade.core.BDIAgent#addGoalListener(bdi4jade.event.GoalListener)
     */
    @Override
    public final void addGoalListener(GoalListener goalListener) {
        synchronized (goalListeners) {
            goalListeners.add(goalListener);
        }
    }

    /**
     * Adds a new goal to this agent to be achieved by creating an intention. It
     * also may add a listener to observe events related to this goal.
     * 
     * @param dispatcher
     *            the capability that dispatched this goal.
     * @param goal
     *            the goal to be achieved.
     * @param goalListener
     *            the listener to be notified.
     */
    private final Intention addIntention(Capability dispatcher, Goal goal, GoalListener goalListener)
            throws IllegalAccessException {
        Intention intention = null;
        synchronized (allIntentions) {
            intention = allIntentions.get(goal);
            if (intention != null) {
                log.info("This agent already has goal: " + goal);
                if (goalListener != null) {
                    intention.addGoalListener(goalListener);
                }
                return null;
            }

            intention = new Intention(goal, this, dispatcher);
            this.allIntentions.put(goal, intention);
            if (dispatcher == null) {
                agentIntentions.add(intention);
            } else {
                dispatcher.addIntention(intention);
            }
            if (goalListener != null) {
                intention.addGoalListener(goalListener);
            }
        }
        fireGoalEvent(new GoalEvent(goal));
        restart();
        return intention;
    }

    /**
     * @see bdi4jade.core.BDIAgent#addSoftgoal(bdi4jade.goal.Softgoal)
     */
    @Override
    public final void addSoftgoal(Softgoal softgoal) {
        synchronized (softgoals) {
            this.softgoals.add(softgoal);
        }
    }

    /**
     * @see bdi4jade.core.BDIAgent#canHandle(jade.lang.acl.ACLMessage)
     */
    @Override
    public boolean canHandle(ACLMessage msg) {
        synchronized (aggregatedCapabilities) {
            for (Capability capability : aggregatedCapabilities) {
                if (capability.canHandle(msg)) {
                    return true;
                }
            }
            return false;
        }
    }

    private final void computeGoalOwnersMap() {
        this.restrictedAccessOwnersMap = new HashMap<>();
        for (Capability capability : aggregatedCapabilities) {
            ReflectionUtils.addGoalOwner(restrictedAccessOwnersMap, capability);
        }
    }

    /**
     * @see bdi4jade.core.BDIAgent#dropGoal(bdi4jade.goal.Goal)
     */
    @Override
    public final void dropGoal(Goal goal) {
        synchronized (allIntentions) {
            Intention intention = allIntentions.get(goal);
            if (intention != null) {
                intention.noLongerDesire();
            }
        }
    }

    /**
     * @see bdi4jade.core.BDIAgent#dropSoftoal(bdi4jade.goal.Softgoal)
     */
    @Override
    public final void dropSoftoal(Softgoal softgoal) {
        synchronized (softgoals) {
            this.softgoals.remove(softgoal);
        }
    }

    /**
     * Notifies all listeners, if any, about a goal event.
     * 
     * @param goalEvent
     *            the event to notify.
     */
    private final void fireGoalEvent(GoalEvent goalEvent) {
        synchronized (goalListeners) {
            for (GoalListener goalListener : goalListeners) {
                goalListener.accept(goalEvent);
            }
        }
    }

    /**
     * Creates a goal event given an intention, and notifies all listeners, if
     * any, about a goal event.
     * 
     * @param intention
     *            the intention used to create the goal event.
     */
    private final void fireGoalEvent(Intention intention) {
        Goal goal = intention.getGoal();
        GoalStatus status = intention.getStatus();
        log.debug("Goal: " + goal.getClass().getSimpleName() + " (" + status + ") - " + goal);

        GoalEvent goalEvent = new GoalEvent(goal, status);
        synchronized (goalListeners) {
            for (GoalListener goalListener : goalListeners) {
                goalListener.accept(goalEvent);
            }
            for (GoalListener goalListener : intention.getGoalListeners()) {
                goalListener.accept(goalEvent);
            }
        }
    }

    /**
     * @see BDIAgent#getAllCapabilities()
     */
    @Override
    public final Collection<Capability> getAllCapabilities() {
        synchronized (aggregatedCapabilities) {
            return capabilities;
        }
    }

    /**
     * @see bdi4jade.core.BDIAgent#getBeliefRevisionStrategy()
     */
    @Override
    public final AgentBeliefRevisionStrategy getBeliefRevisionStrategy() {
        return beliefRevisionStrategy;
    }

    /**
     * @see bdi4jade.core.BDIAgent#getBeliefs()
     */
    @Override
    public final Collection<Belief<?, ?>> getBeliefs() {
        synchronized (aggregatedCapabilities) {
            Collection<Belief<?, ?>> beliefs = new LinkedList<>();
            for (Capability capability : capabilities) {
                beliefs.addAll(capability.getBeliefBase().getBeliefs());
            }
            return beliefs;
        }
    }

    /**
     * @see bdi4jade.core.BDIAgent#getCapabilities()
     */
    public final Set<Capability> getCapabilities() {
        synchronized (aggregatedCapabilities) {
            return aggregatedCapabilities;
        }
    }

    /**
     * @see bdi4jade.core.BDIAgent#getDeliberationFunction()
     */
    @Override
    public final AgentDeliberationFunction getDeliberationFunction() {
        return deliberationFunction;
    }

    /**
     * @see bdi4jade.core.BDIAgent#getGoalListeners()
     */
    @Override
    public final List<GoalListener> getGoalListeners() {
        return goalListeners;
    }

    /**
     * Returns the capability instances that owns a dispatched goal, considering
     * the aggregated capabilities of this agent.
     * 
     * If this method returns an empty set, it means that this agent cannot add
     * a goal without the scope of a dispatcher that has access to it.
     * 
     * @param owner
     *            the capability class that is the goal owner.
     * @param internal
     *            a boolean indicated whether the goal is internal. It is true
     *            if the goal is internal, false otherwise.
     * @return the capability instances related to this capability that owns the
     *         goal, or an empty set if the agent cannot add this goal.
     */
    protected final Set<Capability> getGoalOwner(Class<? extends Capability> owner, boolean internal) {
        if (internal) {
            return new HashSet<Capability>();
        } else {
            Set<Capability> restrictedAccessOwners = restrictedAccessOwnersMap.get(owner);
            return restrictedAccessOwners == null ? new HashSet<Capability>() : restrictedAccessOwners;
        }
    }

    /**
     * @see bdi4jade.core.BDIAgent#getGoals()
     */
    @Override
    public final Set<Goal> getGoals() {
        synchronized (allIntentions) {
            return allIntentions.keySet();
        }
    }

    /**
     * @see bdi4jade.core.BDIAgent#getIntentions()
     */
    @Override
    public final Set<Intention> getIntentions() {
        synchronized (allIntentions) {
            Set<Intention> activeIntentions = new HashSet<Intention>();
            for (Intention intention : activeIntentions) {
                if (!GoalStatus.WAITING.equals(intention.getStatus()))
                    activeIntentions.add(intention);
            }
            return activeIntentions;
        }
    }

    /**
     * @see bdi4jade.core.BDIAgent#getOptionGenerationFunction()
     */
    @Override
    public final AgentOptionGenerationFunction getOptionGenerationFunction() {
        return optionGenerationFunction;
    }

    /**
     * @see bdi4jade.core.BDIAgent#getPlanSelectionStrategy()
     */
    @Override
    public final AgentPlanSelectionStrategy getPlanSelectionStrategy() {
        return planSelectionStrategy;
    }

    /**
     * @see bdi4jade.core.BDIAgent#getSoftgoals()
     */
    @Override
    public final Set<Softgoal> getSoftgoals() {
        synchronized (softgoals) {
            return this.softgoals;
        }
    }

    /**
     * @see bdi4jade.core.BDIAgent#hasGoal(bdi4jade.goal.Goal)
     */
    @Override
    public boolean hasGoal(Goal goal) {
        return allIntentions.get(goal) != null;
    }

    /**
     * This method initializes the BDI agent. It is invoked by the
     * {@link #setup()} method. This is an empty method that should be overriden
     * by subclasses.
     */
    protected void init() {

    }

    /**
     * Removes a capability from this agent.
     * 
     * @param capability
     *            capability to be removed.
     * 
     * @return true if the capability exists and was removed, false otherwise.
     */
    boolean removeCapability(Capability capability) {
        synchronized (aggregatedCapabilities) {
            boolean removed = this.aggregatedCapabilities.remove(capability);
            if (removed) {
                resetAllCapabilities();
                computeGoalOwnersMap();
            }
            return removed;
        }
    }

    /**
     * @see bdi4jade.core.BDIAgent#removeGoalListener(bdi4jade.event.GoalListener)
     */
    @Override
    public final void removeGoalListener(GoalListener goalListener) {
        synchronized (goalListeners) {
            goalListeners.remove(goalListener);
        }
    }

    final void resetAllCapabilities() {
        synchronized (aggregatedCapabilities) {
            Set<Capability> oldCapabilities = this.capabilities;
            Set<Capability> allCapabilities = new HashSet<>();
            for (Capability capability : aggregatedCapabilities) {
                allCapabilities.add(capability);
                capability.addRelatedCapabilities(allCapabilities);
            }
            this.capabilities = allCapabilities;
            log.debug("Capabilities: " + this.capabilities);

            Set<Capability> removedCapabilities = new HashSet<>(oldCapabilities);
            removedCapabilities.removeAll(allCapabilities);
            for (Capability capability : removedCapabilities) {
                capability.setMyAgent(null);
            }

            Set<Capability> addedCapabilities = new HashSet<>(allCapabilities);
            addedCapabilities.removeAll(oldCapabilities);
            for (Capability capability : addedCapabilities) {
                if (capability.getMyAgent() != null) {
                    throw new IllegalArgumentException(
                            "Capability already binded to another agent: " + capability.getFullId());
                }
                capability.setMyAgent(this);
            }
        }
    }

    /**
     * @see BDIAgent#restart()
     */
    @Override
    public final void restart() {
        this.bdiInterpreter.restart();
    }

    /**
     * Sets the belief revision strategy of this agent.
     * 
     * @param beliefRevisionStrategy
     *            the beliefRevisionStrategy to set.
     */
    public final void setBeliefRevisionStrategy(AgentBeliefRevisionStrategy beliefRevisionStrategy) {
        if (beliefRevisionStrategy == null) {
            this.beliefRevisionStrategy = new DefaultAgentBeliefRevisionStrategy();
        } else {
            this.beliefRevisionStrategy.setAgent(null);
            this.beliefRevisionStrategy = beliefRevisionStrategy;
        }
        this.beliefRevisionStrategy.setAgent(this);
    }

    /**
     * Sets the deliberation function of this agent.
     * 
     * @param deliberationFunction
     *            the deliberationFunction to set.
     */
    public final void setDeliberationFunction(AgentDeliberationFunction deliberationFunction) {
        if (deliberationFunction == null) {
            this.deliberationFunction = new DefaultAgentDeliberationFunction();
        } else {
            this.deliberationFunction.setAgent(null);
            this.deliberationFunction = deliberationFunction;
        }
        this.deliberationFunction.setAgent(this);
    }

    /**
     * Sets the option generation function of this agent.
     * 
     * @param optionGenerationFunction
     *            the optionGenerationFunction to set.
     */
    public final void setOptionGenerationFunction(AgentOptionGenerationFunction optionGenerationFunction) {
        if (optionGenerationFunction == null) {
            this.optionGenerationFunction = new DefaultAgentOptionGenerationFunction();
        } else {
            this.optionGenerationFunction.setAgent(null);
            this.optionGenerationFunction = optionGenerationFunction;
        }
        this.optionGenerationFunction.setAgent(this);
    }

    /**
     * Sets the plan selection strategy of this agent.
     * 
     * @param planSelectionStrategy
     *            the planSelectionStrategy to set.
     */
    public final void setPlanSelectionStrategy(AgentPlanSelectionStrategy planSelectionStrategy) {
        if (planSelectionStrategy == null) {
            this.planSelectionStrategy = new DefaultAgentPlanSelectionStrategy();
        } else {
            this.planSelectionStrategy.setAgent(null);
            this.planSelectionStrategy = planSelectionStrategy;
        }
        this.planSelectionStrategy.setAgent(this);
    }

    /**
     * Initializes the BDI agent. It adds the behavior to handle message
     * received and can be processed by capabilities and the
     * {@link BDIInterpreter} behavior as well. It invokes the {@link #init()}
     * method, so that customized initializations can be perfomed by subclasses.
     * 
     * @see jade.core.Agent#setup()
     */
    @Override
    protected final void setup() {
        this.addBehaviour(new BDIAgentMsgReceiver(this));
        this.addBehaviour(bdiInterpreter);
        init();
    }

    /**
     * Removes all capabilities of this agent, before it stops its execution.
     * 
     * @see jade.core.Agent#takeDown()
     */
    @Override
    protected void takeDown() {
        synchronized (aggregatedCapabilities) {
            Iterator<Capability> iterator = aggregatedCapabilities.iterator();
            while (iterator.hasNext()) {
                removeCapability(iterator.next());
            }
        }
    }

}