//----------------------------------------------------------------------------
// 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.AttributeBelief;
import bdi4jade.belief.Belief;
import bdi4jade.belief.BeliefBase;
import bdi4jade.belief.NamedBelief;
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 {
this.getBeliefBase().addBelief(
new AttributeBelief(this, field));
}
} else if (field
.isAnnotationPresent(bdi4jade.annotation.TransientBelief.class)) {
bdi4jade.annotation.TransientBelief annotation = field
.getAnnotation(bdi4jade.annotation.TransientBelief.class);
Object value = field.get(this);
if ("".equals(annotation.name())) {
this.getBeliefBase().addBelief(
new NamedBelief(field.getName(), value));
} else {
this.getBeliefBase().addBelief(
new TransientBelief(annotation.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 {
throw new ClassCastException("Field " + field.getName()
+ " should be assignable to " + Set.class.getName());
}
} 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;
}
}