//----------------------------------------------------------------------------
// Copyright (C) 2018 João Faccin
//
// 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.extension.undo;
import java.util.HashMap;
import java.util.Map;
import bdi4jade.belief.PredicateBelief;
import bdi4jade.core.BDIAgent;
import bdi4jade.core.Capability;
import bdi4jade.core.Intention;
import bdi4jade.event.BeliefEvent;
import bdi4jade.event.BeliefEvent.Action;
import bdi4jade.event.BeliefListener;
import bdi4jade.event.GoalEvent;
import bdi4jade.event.GoalListener;
import bdi4jade.extension.remediation.logics.Predicate;
import bdi4jade.extension.undo.reasoning.RevertingOptionGenerationFunction;
import bdi4jade.goal.Goal;
import bdi4jade.goal.GoalStatus;
import bdi4jade.goal.PredicateGoal;
/**
* This class is an extension of {@link Capability}. It implements the
* {@link BeliefListener} and {@link GoalListener} interfaces and has the
* {@link RevertingOptionGenerationFunction} as its default option generation
* function. This capability provides agents with the ability to revert actions
* performed with the aim of achieving particular goals. For this purpose, it
* stores a mapping between goals and their corresponding achievement metadata,
* as well as a mapping between child and parent goals.
*
* @author João Faccin
*/
public class RevertingCapability extends Capability implements BeliefListener, GoalListener {
private static final long serialVersionUID = -5556551069331273755L;
private final Map<Goal, GoalAchievementMetadata> gams;
protected Map<Goal, Goal> parentGoals;
/**
* Creates a new capability and sets {@link RevertingOptionGenerationFunction}
* as its default option generation function.
*/
public RevertingCapability(BDIAgent agent) {
this.gams = new HashMap<>();
this.parentGoals = new HashMap<>();
setOptionGenerationFunction(new RevertingOptionGenerationFunction(this));
agent.addGoalListener(this);
this.getBeliefBase().addBeliefListener(this);
}
/**
* Adds a mapping between the goal and the goal achievement metadata to the
* existing map.
*
* @param goal
* the goal to be associated with the goal achievement metadata.
* @param gam
* the goal achievement metadata to which the goal will be
* associated.
*/
public void addGoalAchievementMetadata(Goal goal, GoalAchievementMetadata gam) {
this.gams.put(goal, gam);
}
/**
* Adds a mapping between the child goal and its parent to the existing map.
*
* @param child
* the child goal to be associated with the parent.
* @param parent
* the parent goal to which the child will be associated.
*/
public void addParentGoal(Goal child, Goal parent) {
this.parentGoals.put(child, parent);
}
/**
* Creates a new {@link GoalAchievementMetadata} with its related goal and
* intention, and adds the mapping between the goal and the goal achievement
* metadata to the existing map.
*
* @param goal
* the goal related to the goal achievement metadata.
* @param intention
* the intention related to the goal achievement metadata.
* @return the created goal achievement metadata.
*/
public GoalAchievementMetadata createGoalAchievementMetadata(PredicateGoal<?> goal, Intention intention) {
GoalAchievementMetadata gam = new GoalAchievementMetadata(goal, intention);
addGoalAchievementMetadata(goal, gam);
return gam;
}
/**
* Creates a new {@link GoalAchievementMetadata} with its related goal,
* intention, and rollback flag. It also adds the mapping between the goal and
* the goal achievement metadata to the existing map.
*
* @param goal
* the goal related to the goal achievement metadata
* @param intention
* the intention related to the goal achievement metadata.
* @param rollback
* a flag that indicates the desire of a rollback in case of a plan
* failure.
* @return the created goal achievement metadata.
*/
public GoalAchievementMetadata createGoalAchievementMetadata(PredicateGoal<?> goal, Intention intention,
Boolean rollback) {
GoalAchievementMetadata gam = new GoalAchievementMetadata(goal, intention, rollback);
addGoalAchievementMetadata(goal, gam);
return gam;
}
/**
* Creates a new {@link GoalAchievementMetadata} with its related goal,
* intention, reversion trigger, rollback flag and discarding conditions. It
* also adds the mapping between the goal and the goal achievement metadata to
* the existing map.
*
* @param goal
* the goal related to the goal achievement metadata
* @param intention
* the intention related to the goal achievement metadata.
* @param rollback
* a flag that indicates the desire of a rollback in case of a plan
* failure.
* @param reversionTrigger
* a list of {@link Predicate} that specifies the condition in which
* a reversion will be activated.
* @param maxExecutedPlans
* the number of plans that can be executed after achieving the
* related goal before this goal achievement metadata is discarded.
* @param maxTime
* the time that can pass after achieving the related goal before
* this goal achievement metadata is discarded.
* @return the created goal achievement metadata.
*/
public GoalAchievementMetadata createGoalAchievementMetadata(PredicateGoal<?> goal, Intention intention,
Boolean rollback, Map<Predicate, Boolean> reversionTrigger, Integer maxExecutedPlans, Integer maxTime) {
GoalAchievementMetadata gam = new GoalAchievementMetadata((PredicateGoal<?>) goal, intention, rollback,
reversionTrigger, maxExecutedPlans, maxTime);
addGoalAchievementMetadata(goal, gam);
return gam;
}
/*
* (non-Javadoc)
*
* @see bdi4jade.event.BeliefListener#eventOccurred(bdi4jade.event.BeliefEvent)
*/
@Override
public void eventOccurred(BeliefEvent beliefEvent) {
if (beliefEvent.getAction().equals(Action.BELIEF_UPDATED) && beliefEvent.getArgs() != null) {
Object args = beliefEvent.getArgs();
if (args instanceof PredicateGoal<?>) {
PredicateGoal<?> goal = (PredicateGoal<?>) args;
if (beliefEvent.getBelief() instanceof PredicateBelief<?>) {
PredicateBelief<?> belief = (PredicateBelief<?>) beliefEvent.getBelief();
Goal oldestParent = getOldestReversibleParent(goal);
if (oldestParent != null) {
this.gams.get(oldestParent).trackBeliefChange((Predicate) belief.getName(), belief.getValue());
}
}
}
}
}
/**
* Returns the mapping between goals and their corresponding goal achievement
* metadata.
*
* @return the goal achievement metadata map.
*/
public Map<Goal, GoalAchievementMetadata> getGoalAchievementMetadata() {
return this.gams;
}
/**
* Returns the oldest parent of goal with a mapping in the goal achievement map.
* If there is no such parent and the goal is in the mapping, returns the goal
* itself, otherwise, returns null.
*
* @param goal
* the goal whose oldest reversible parent is to be found.
* @return the oldest reversible parent of goal if it exists, or null,
* otherwise.
*/
private Goal getOldestReversibleParent(Goal goal) {
Goal parent = null;
Goal aux = goal;
if (this.gams.containsKey(goal)) {
parent = goal;
}
while (this.parentGoals.containsKey(aux)) {
aux = this.parentGoals.get(aux);
if (this.gams.containsKey(aux)) {
parent = aux;
}
}
return parent;
}
/**
* Returns the mapping between child goals and their parents.
*
* @return the parentGols map.
*/
public Map<Goal, Goal> getParentGoals() {
return parentGoals;
}
/*
* (non-Javadoc)
*
* @see bdi4jade.event.GoalListener#goalPerformed(bdi4jade.event.GoalEvent)
*/
@Override
public void goalPerformed(GoalEvent event) {
if (!event.isGoalAdded()) {
for (GoalAchievementMetadata gam : this.gams.values()) {
if (gam.getIntention().getStatus().equals(GoalStatus.ACHIEVED) && !event.getGoal().equals(gam.getGoal())
&& event.getStatus().equals(GoalStatus.ACHIEVED)) {
gam.incrementPlanCounter();
}
}
}
}
}