bdi4jade.core.Capability.java Source code

Java tutorial

Introduction

Here is the source code for bdi4jade.core.Capability.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 jade.lang.acl.ACLMessage;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.security.acl.Owner;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import bdi4jade.belief.Belief;
import bdi4jade.belief.BeliefBase;
import bdi4jade.belief.TransientBelief;
import bdi4jade.belief.TransientBeliefSet;
import bdi4jade.goal.Goal;
import bdi4jade.plan.Plan;
import bdi4jade.plan.PlanLibrary;
import bdi4jade.reasoning.BeliefRevisionStrategy;
import bdi4jade.reasoning.DefaultBeliefRevisionStrategy;
import bdi4jade.reasoning.DefaultDeliberationFunction;
import bdi4jade.reasoning.DefaultOptionGenerationFunction;
import bdi4jade.reasoning.DefaultPlanSelectionStrategy;
import bdi4jade.reasoning.DeliberationFunction;
import bdi4jade.reasoning.OptionGenerationFunction;
import bdi4jade.reasoning.PlanSelectionStrategy;
import bdi4jade.util.ReflectionUtils;

/**
 * This capability represents a component that aggregates the mental attitudes
 * defined by the BDI architecture. It has a belief base with the associated
 * beliefs (knowledge) and a plan library.
 * 
 * @author Ingrid Nunes
 */
public class Capability implements Serializable {

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

    private final Set<Capability> associationSources;
    private final Set<Capability> associationTargets;
    protected final BeliefBase beliefBase;
    private BeliefRevisionStrategy beliefRevisionStrategy;
    private DeliberationFunction deliberationFunction;
    private Map<Class<? extends Capability>, Set<Capability>> fullAccessOwnersMap;
    protected final String id;
    private final Collection<Intention> intentions;
    private BDIAgent myAgent;
    private OptionGenerationFunction optionGenerationFunction;
    private final List<Class<? extends Capability>> parentCapabilities;
    private final Set<Capability> partCapabilities;
    protected final PlanLibrary planLibrary;
    private PlanSelectionStrategy planSelectionStrategy;
    private Map<Class<? extends Capability>, Set<Capability>> restrictedAccessOwnersMap;
    private boolean started;
    private Capability wholeCapability;

    /**
     * Creates a new capability with a generated id. It uses {@link BeliefBase}
     * and {@link PlanLibrary} as belief base and plan library respectively.
     */
    public Capability() {
        this(null, null, null, null, null);
    }

    /**
     * Creates a new capability with a generated id. It uses {@link BeliefBase}
     * and {@link PlanLibrary} as belief base and plan library respectively, and
     * adds initial beliefs and plans.
     * 
     * @param initialBeliefs
     *            the initial set of beliefs to be added to the belief base of
     *            this capability.
     * @param initialPlans
     *            the initial set of plans to be added to the plan library of
     *            this capability.
     */
    public Capability(Set<Belief<?, ?>> initialBeliefs, Set<Plan> initialPlans) {
        this(null, null, initialBeliefs, null, initialPlans);
    }

    /**
     * Creates a new capability with the given id. It uses {@link BeliefBase}
     * and {@link PlanLibrary} as belief base and plan library respectively.
     * 
     * @param id
     *            the capability id. If it is null, the class name is going to
     *            be used.
     */
    public Capability(String id) {
        this(id, null, null, null, null);
    }

    /**
     * Creates a new capability with the given id, or a generated one if it is
     * null. It also sets the belief base and plan library, and adds initial
     * beliefs and plans.
     * 
     * @param id
     *            the capability id. If it is null, the class name is going to
     *            be used.
     * @param beliefBase
     *            the belief base.
     * @param initialBeliefs
     *            the initial set of beliefs to be added to the belief base of
     *            this capability.
     * @param planLibrary
     *            the plan library.
     * @param initialPlans
     *            the initial set of plans to be added to the plan library of
     *            this capability.
     */
    protected Capability(String id, BeliefBase beliefBase, Set<Belief<?, ?>> initialBeliefs,
            PlanLibrary planLibrary, Set<Plan> initialPlans) {
        this.intentions = new LinkedList<>();
        this.parentCapabilities = generateParentCapabilities();
        log.debug("Parent capabilities: " + parentCapabilities);
        this.started = false;

        // Id initialization
        if (id == null) {
            if (this.getClass().getCanonicalName() == null || Capability.class.equals(this.getClass())) {
                this.id = Capability.class.getName() + System.currentTimeMillis();
            } else {
                this.id = this.getClass().getName();
            }
        } else {
            this.id = id;
        }

        // Initializing belief base and plan library
        this.beliefBase = beliefBase == null ? new BeliefBase(this, initialBeliefs) : beliefBase;
        this.planLibrary = planLibrary == null ? new PlanLibrary(this, initialPlans) : planLibrary;

        // Initializing associations
        this.wholeCapability = null;
        this.partCapabilities = new HashSet<>();
        this.associationSources = new HashSet<>();
        this.associationTargets = new HashSet<>();

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

        computeGoalOwnersMap();
    }

    /**
     * Creates a new capability with the given id. It uses {@link BeliefBase}
     * and {@link PlanLibrary} as belief base and plan library respectively, and
     * adds initial beliefs and plans.
     * 
     * @param id
     *            the capability id. If it is null, the class name is going to
     *            be used.
     * @param initialBeliefs
     *            the initial set of beliefs to be added to the belief base of
     *            this capability.
     * @param initialPlans
     *            the initial set of plans to be added to the plan library of
     *            this capability.
     */
    public Capability(String id, Set<Belief<?, ?>> initialBeliefs, Set<Plan> initialPlans) {
        this(id, null, initialBeliefs, null, initialPlans);
    }

    /**
     * Adds by reflection capability components, such as beliefs and plans,
     * according to annotated fields. This method is invoked by for capability
     * class, and all parent classes.
     * 
     * @param capabilityClass
     *            the capability class of which fields should me added to this
     *            capability.
     */
    protected void addAnnotatedFields(Class<? extends Capability> capabilityClass) {
        for (Field field : capabilityClass.getDeclaredFields()) {
            boolean b = field.isAccessible();
            field.setAccessible(true);
            try {
                if (field.isAnnotationPresent(bdi4jade.annotation.Belief.class)) {
                    if (Belief.class.isAssignableFrom(field.getType())) {
                        Belief<?, ?> belief = (Belief<?, ?>) field.get(this);
                        this.getBeliefBase().addBelief(belief);
                    } else {
                        throw new ClassCastException("Field " + field.getName() + " should be a Belief");
                    }
                } else if (field.isAnnotationPresent(bdi4jade.annotation.TransientBelief.class)) {
                    bdi4jade.annotation.TransientBelief annotation = field
                            .getAnnotation(bdi4jade.annotation.TransientBelief.class);
                    String name = "".equals(annotation.name()) ? field.getName() : annotation.name();
                    Object value = field.get(this);
                    this.getBeliefBase().addBelief(new TransientBelief(name, value));
                } else if (field.isAnnotationPresent(bdi4jade.annotation.TransientBeliefSet.class)) {
                    bdi4jade.annotation.TransientBeliefSet annotation = field
                            .getAnnotation(bdi4jade.annotation.TransientBeliefSet.class);
                    String name = "".equals(annotation.name()) ? field.getName() : annotation.name();
                    Object value = field.get(this);
                    if (Set.class.isAssignableFrom(field.getType())) {
                        this.getBeliefBase().addBelief(new TransientBeliefSet(name, (Set) value));
                    }
                } else if (field.isAnnotationPresent(bdi4jade.annotation.Plan.class)) {
                    if (Plan.class.isAssignableFrom(field.getType())) {
                        Plan plan = (Plan) field.get(this);
                        this.getPlanLibrary().addPlan(plan);
                    } else {
                        throw new ClassCastException("Field " + field.getName() + " should be a Plan");
                    }
                } else if (field.isAnnotationPresent(bdi4jade.annotation.AssociatedCapability.class)) {
                    if (Capability.class.isAssignableFrom(field.getType())) {
                        Capability capability = (Capability) field.get(this);
                        this.addAssociatedCapability(capability);
                    } else {
                        throw new ClassCastException("Field " + field.getName() + " should be a Capability");
                    }
                } else if (field.isAnnotationPresent(bdi4jade.annotation.PartCapability.class)) {
                    if (Capability.class.isAssignableFrom(field.getType())) {
                        Capability capability = (Capability) field.get(this);
                        this.addPartCapability(capability);
                    } else {
                        throw new ClassCastException("Field " + field.getName() + " should be a Capability");
                    }
                }
            } catch (Exception exc) {
                log.warn(exc);
                exc.printStackTrace();
            }
            field.setAccessible(b);
        }
    }

    /**
     * Associates a capability to this capability.
     * 
     * @param capability
     *            the capability to be associated.
     */
    public final void addAssociatedCapability(Capability capability) {
        this.associationTargets.add(capability);
        capability.associationSources.add(this);
        this.computeGoalOwnersMap();
        capability.computeGoalOwnersMap();
        resetAgentCapabilities();
    }

    /**
     * Adds the set of plans of this capability that can achieve the given goal
     * to a map of candidate plans. It checks its plan library and the part
     * capabilities, recursively.
     * 
     * @param goal
     *            the goal to be achieved.
     * @param candidatePlansMap
     *            the map to which the set of plans that can achieve the goal
     *            should be added.
     */
    public void addCandidatePlans(Goal goal, Map<Capability, Set<Plan>> candidatePlansMap) {
        Set<Plan> plans = planLibrary.getCandidatePlans(goal);
        if (!plans.isEmpty()) {
            candidatePlansMap.put(this, plans);
        }
        for (Capability part : partCapabilities) {
            part.addCandidatePlans(goal, candidatePlansMap);
        }
    }

    final void addIntention(Intention intention) {
        this.intentions.add(intention);
    }

    /**
     * Adds a capability as part of this capability, which is a
     * whole-capability.
     * 
     * @param partCapability
     *            the part capability to be added.
     */
    public final void addPartCapability(Capability partCapability) {
        if (partCapability.wholeCapability != null) {
            throw new IllegalArgumentException("Part capability already binded to another whole capability.");
        }
        partCapability.wholeCapability = this;
        this.partCapabilities.add(partCapability);

        partCapability.computeGoalOwnersMap();
        this.computeGoalOwnersMap();
        resetAgentCapabilities();
    }

    final Set<Capability> addRelatedCapabilities(Set<Capability> capabilities) {
        for (Capability part : partCapabilities) {
            if (!capabilities.contains(part)) {
                capabilities.add(part);
                part.addRelatedCapabilities(capabilities);
            }
        }
        for (Capability target : associationTargets) {
            if (!capabilities.contains(target)) {
                capabilities.add(target);
                target.addRelatedCapabilities(capabilities);
            }
        }
        return capabilities;
    }

    /**
     * Checks if this capability has a plan that can achieve the given goal. It
     * checks the plan library of this capabilities and, if cannot achieve it,
     * it checks part capabilities, recursively.
     * 
     * @param goal
     *            the goal to be checked.
     * @return true if this capability has at least a plan that can achieve the
     *         goal.
     */
    public boolean canAchieve(Goal goal) {
        if (planLibrary.canAchieve(goal)) {
            return true;
        } else {
            for (Capability part : partCapabilities) {
                if (part.canAchieve(goal)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Checks if this capability has a plan that can process the given message.
     * It checks the plan library of this capabilities and, if cannot handle it,
     * it checks part capabilities, recursively.
     * 
     * @param msg
     *            the message to be checked.
     * @return true if this capability has at least a plan that can process the
     *         message.
     */
    public boolean canHandle(ACLMessage msg) {
        if (planLibrary.canHandle(msg)) {
            return true;
        } else {
            for (Capability part : partCapabilities) {
                if (part.canHandle(msg)) {
                    return true;
                }
            }
        }
        return false;
    }

    private final void computeGoalOwnersMap() {
        this.fullAccessOwnersMap = new HashMap<>();
        ReflectionUtils.addGoalOwner(fullAccessOwnersMap, this);
        if (wholeCapability != null) {
            ReflectionUtils.addGoalOwner(fullAccessOwnersMap, wholeCapability);
        }
        this.restrictedAccessOwnersMap = new HashMap<>();
        for (Capability capability : associationTargets) {
            ReflectionUtils.addGoalOwner(restrictedAccessOwnersMap, capability);
        }
        for (Capability capability : partCapabilities) {
            ReflectionUtils.addGoalOwner(restrictedAccessOwnersMap, capability);
        }
        log.debug("Full access owners: " + fullAccessOwnersMap);
        log.debug("Restricted access owners: " + restrictedAccessOwnersMap);
    }

    /**
     * Returns true if the object given as parameter is a capability and has the
     * same full id of this capability.
     * 
     * @param obj
     *            the object to be tested as equals to this plan.
     * 
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public final boolean equals(Object obj) {
        if (!(obj instanceof Capability))
            return false;
        return getFullId().equals(((Capability) obj).getFullId());
    }

    @SuppressWarnings("unchecked")
    private final List<Class<? extends Capability>> generateParentCapabilities() {
        List<Class<? extends Capability>> parentCapabilities = new LinkedList<>();
        Class<?> currentClass = this.getClass().getSuperclass();
        while (Capability.class.isAssignableFrom(currentClass) && !Capability.class.equals(currentClass)) {
            parentCapabilities.add((Class<Capability>) currentClass);
            currentClass = currentClass.getSuperclass();
        }
        return parentCapabilities;
    }

    /**
     * Returns all capabilities with which this capability is associated.
     * 
     * @return the associated capabilities.
     */
    public final Set<Capability> getAssociatedCapabilities() {
        return associationTargets;
    }

    /**
     * Returns this capability belief base.
     * 
     * @return the beliefBase.
     */
    public final BeliefBase getBeliefBase() {
        return beliefBase;
    }

    /**
     * Returns the belief revision strategy of this capability.
     * 
     * @return the beliefRevisionStrategy.
     */
    public final BeliefRevisionStrategy getBeliefRevisionStrategy() {
        return beliefRevisionStrategy;
    }

    /**
     * Returns the deliberation function of this capability.
     * 
     * @return the deliberationFunction.
     */
    public final DeliberationFunction getDeliberationFunction() {
        return deliberationFunction;
    }

    /**
     * Returns the full id of this capability, which is its id prefixed by all
     * whole-capabilities' ids.
     * 
     * @return the full id of this capability.
     */
    public final String getFullId() {
        StringBuffer sb = new StringBuffer();
        getFullId(sb);
        return sb.toString();
    }

    private final void getFullId(StringBuffer sb) {
        if (wholeCapability != null) {
            wholeCapability.getFullId(sb);
            sb.append(".");
        }
        sb.append(id);
    }

    /**
     * Returns the capability instances that owns a dispatched goal, considering
     * the superclasses of this capability, its associations and compositions.
     * 
     * A capability may dispatch its own goals and goals of its parents. It may
     * also dispatch external goals of associated or part capabilities (and
     * their parents), and all goals of whole capabilities.
     * 
     * This method thus searches all capabilities that have a relationship with
     * this capability (either inheritance, composition or association) and
     * finds the concrete capability instances whose definition owns a goal
     * (specified with the {@link Owner} annotation in goals).
     * 
     * If this method returns an empty set, it means that this capability has no
     * access to the goal owned by capabilities of the given class.
     * 
     * @param owner
     *            the capability class that is the goal owner.
     * @param internal
     *            the boolean that indicates whether the goal is internal or
     *            external.
     * @return the capability instances related to this capability (or the
     *         capability itself) that owns the goal, or an empty set if the
     *         capability has no access to goals owned by capability of the
     *         given class.
     */
    public final Set<Capability> getGoalOwner(Class<? extends Capability> owner, boolean internal) {
        Set<Capability> owners = new HashSet<>();

        Set<Capability> fullAccessOwners = fullAccessOwnersMap.get(owner);
        if (fullAccessOwners != null)
            owners.addAll(fullAccessOwners);

        if (!internal) {
            Set<Capability> restrictedAccessOwners = restrictedAccessOwnersMap.get(owner);
            if (restrictedAccessOwners != null)
                owners.addAll(restrictedAccessOwners);
        }

        return owners;
    }

    /**
     * Returns this capability id.
     * 
     * @return the id.
     */
    public String getId() {
        return id;
    }

    final Collection<Intention> getIntentions() {
        return intentions;
    }

    /**
     * Returns the agent that this capability is associated with.
     * 
     * @return the agent.
     */
    public final BDIAgent getMyAgent() {
        return this.myAgent;
    }

    /**
     * Returns the option generation function of this capability.
     * 
     * @return the optionGenerationFunction.
     */
    public final OptionGenerationFunction getOptionGenerationFunction() {
        return optionGenerationFunction;
    }

    /**
     * Returns the classes of all parent capabilities of this capability.
     * 
     * @return the parentCapabilities.
     */
    public final List<Class<? extends Capability>> getParentCapabilities() {
        return parentCapabilities;
    }

    /**
     * Returns the parts of this capability.
     * 
     * @return the partCapabilities.
     */
    public final Set<Capability> getPartCapabilities() {
        return partCapabilities;
    }

    /**
     * Returns the plan library of this capability.
     * 
     * @return the planLibrary.
     */
    public final PlanLibrary getPlanLibrary() {
        return planLibrary;
    }

    /**
     * Returns the plan selection strategy of this capability.
     * 
     * @return the planSelectionStrategy.
     */
    public final PlanSelectionStrategy getPlanSelectionStrategy() {
        return planSelectionStrategy;
    }

    /**
     * Returns the whole-capability, if this is a part capability.
     * 
     * @return the wholeCapability.
     */
    public final Capability getWholeCapability() {
        return wholeCapability;
    }

    /**
     * @see java.lang.Object#hashCode()
     */
    @Override
    public final int hashCode() {
        return this.getFullId().hashCode();
    }

    /**
     * Dissociates a capability of this capability.
     * 
     * @param capability
     *            the capability to be dissociated.
     */
    public final void removeAssociatedCapability(Capability capability) {
        this.associationTargets.remove(capability);
        capability.associationSources.remove(this);
        computeGoalOwnersMap();
        resetAgentCapabilities();
    }

    final void removeIntention(Intention intention) {
        this.intentions.remove(intention);
    }

    /**
     * Removes a capability as part of this capability, which is a
     * whole-capability.
     * 
     * @param partCapability
     *            the part capability to be removed.
     * 
     * @return true if the capability was removed, false otherwise.
     */
    public final boolean removePartCapability(Capability partCapability) {
        boolean removed = this.partCapabilities.remove(partCapability);
        if (removed) {
            partCapability.wholeCapability = null;
            computeGoalOwnersMap();
            resetAgentCapabilities();
        }
        return removed;
    }

    private final void resetAgentCapabilities() {
        if (myAgent != null) {
            if (myAgent instanceof AbstractBDIAgent) {
                ((AbstractBDIAgent) myAgent).resetAllCapabilities();
            }
        }
    }

    /**
     * Sets the belief revision strategy of this capability.
     * 
     * @param beliefRevisionStrategy
     *            the beliefRevisionStrategy to set.
     */
    public final void setBeliefRevisionStrategy(BeliefRevisionStrategy beliefRevisionStrategy) {
        if (beliefRevisionStrategy == null) {
            this.beliefRevisionStrategy = new DefaultBeliefRevisionStrategy();
        } else {
            this.beliefRevisionStrategy.setCapability(null);
            this.beliefRevisionStrategy = beliefRevisionStrategy;
        }
        this.beliefRevisionStrategy.setCapability(this);
    }

    /**
     * Sets the deliberation function of this capability.
     * 
     * @param deliberationFunction
     *            the deliberationFunction to set.
     */
    public final void setDeliberationFunction(DeliberationFunction deliberationFunction) {
        if (deliberationFunction == null) {
            this.deliberationFunction = new DefaultDeliberationFunction();
        } else {
            this.deliberationFunction.setCapability(null);
            this.deliberationFunction = deliberationFunction;
        }
        this.deliberationFunction.setCapability(this);
    }

    /**
     * This method attaches a capability to an agent. It also sets up the
     * capability by adding all annotated fields to this capability, including
     * from its parents. It also invokes the {@link #setup()} method, so that
     * further setup customizations can be performed.
     * 
     * @param myAgent
     *            the myAgent to set
     */
    final synchronized void setMyAgent(BDIAgent myAgent) {
        if (this.myAgent != null && myAgent == null) {
            takeDown();
        }
        this.myAgent = myAgent;
        if (this.myAgent != null && !started) {
            addAnnotatedFields(this.getClass());
            for (Class<? extends Capability> parentCapabilityClass : parentCapabilities) {
                addAnnotatedFields(parentCapabilityClass);
            }
            setup();
            this.started = true;
        }
    }

    /**
     * Sets the option generation function of this capability.
     * 
     * @param optionGenerationFunction
     *            the optionGenerationFunction to set.
     */
    public final void setOptionGenerationFunction(OptionGenerationFunction optionGenerationFunction) {
        if (optionGenerationFunction == null) {
            this.optionGenerationFunction = new DefaultOptionGenerationFunction();
        } else {
            this.optionGenerationFunction.setCapability(null);
            this.optionGenerationFunction = optionGenerationFunction;
        }
        this.optionGenerationFunction.setCapability(this);
    }

    /**
     * Sets the plan selection strategy of this capability.
     * 
     * @param planSelectionStrategy
     *            the planSelectionStrategy to set.
     */
    public final void setPlanSelectionStrategy(PlanSelectionStrategy planSelectionStrategy) {
        if (planSelectionStrategy == null) {
            this.planSelectionStrategy = new DefaultPlanSelectionStrategy();
        } else {
            this.planSelectionStrategy.setCapability(null);
            this.planSelectionStrategy = planSelectionStrategy;
        }
        this.planSelectionStrategy.setCapability(this);
    }

    /**
     * This is an empty holder for being overridden by subclasses. It is used to
     * initialize the capability. This method is invoked when this capability is
     * attached to an agent for the first time. It may be used to add initial
     * plans and beliefs. The reasoning strategies of this capability are
     * initialized in the constructor with default strategies. This method may
     * also customize them.
     */
    protected void setup() {

    }

    /**
     * This is an empty holder for being overridden by subclasses. It is used to
     * clean up a capability when it is removed from an agent.
     */
    protected void takeDown() {

    }

    /**
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return id;
    }

}