//----------------------------------------------------------------------------
// 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.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import bdi4jade.belief.BeliefBase;
import bdi4jade.core.Intention;
import bdi4jade.extension.remediation.logics.Predicate;
import bdi4jade.goal.GoalStatus;
import bdi4jade.goal.PredicateGoal;
/**
* This class stores the information regarding changes performed during the
* achievement of a related {@link Goal} in order to allow such changes to be
* later reverted.
*
* @author João Faccin
*/
public class GoalAchievementMetadata {
private final static Integer MAX_EXECUTED_PLANS = 500;
private final static Integer MAX_TIME = Integer.MAX_VALUE;
private Map<Predicate, List<Pair<Boolean, Long>>> beliefChangeTrace;
private Long createdTime;
private PredicateGoal<?> goal;
private Intention intention;
private Integer maxExecutedPlans;
private Integer maxTime;
private Integer planCounter;
private Map<Predicate, Boolean> reversionTrigger;
private Boolean rollback;
/**
* Initializes a goal achievement metadata with its related {@link Goal} and
* {@link Intention}.
*
* @param goal
* the goal related to the goal achievement metadata.
* @param intention
* the intention related to the goal achievement metadata.
*/
public GoalAchievementMetadata(PredicateGoal<?> goal, Intention intention) {
this(goal, intention, true);
}
/**
* Initializes a goal achievement metadata with its related {@link Goal} and
* {@link Intention}. It also sets the rollback flag.
*
* @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.
*/
public GoalAchievementMetadata(PredicateGoal<?> goal, Intention intention, Boolean rollback) {
this(goal, intention, rollback, null, MAX_EXECUTED_PLANS, MAX_TIME);
}
/**
* Initializes a goal achievement metadata with its related goal and intention.
* It sets a reversion trigger (if it is not null), a rollback flag, and the
* discarding conditions (maxExecutedPlans and maxTime).
*
* @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 map of {@link Predicate} and boolean values 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.
*/
public GoalAchievementMetadata(PredicateGoal<?> goal, Intention intention, Boolean rollback,
Map<Predicate, Boolean> reversionTrigger, Integer maxExecutedPlans, Integer maxTime) {
// TODO Refactor: Created time must be refactored to achieved time.
this.createdTime = System.currentTimeMillis();
this.beliefChangeTrace = new HashMap<>();
this.goal = goal;
this.intention = intention;
this.maxExecutedPlans = maxExecutedPlans;
this.maxTime = maxTime;
this.planCounter = 0;
this.reversionTrigger = new HashMap<>();
this.rollback = rollback;
if (reversionTrigger != null) {
this.reversionTrigger = reversionTrigger;
}
}
/**
* Selects stored changes that must be actually reverted. It selects only those
* changes that do not correspond to the goal itself and whose first and last
* changed values are equal to each other.
*
* @param beliefBase
* the belief base.
* @return the list of {@link Predicate} to be reverted.
*/
public ArrayList<Predicate> filterBeliefChanges(BeliefBase beliefBase) {
ArrayList<Predicate> filteredBeliefs = new ArrayList<>();
PredicateGoal<?> goal = (PredicateGoal<?>) this.goal;
for (Predicate predicate : this.getBeliefChangeTrace().keySet()) {
LinkedList<Pair<Boolean, Long>> trace = (LinkedList<Pair<Boolean, Long>>) this.getBeliefChangeTrace()
.get(predicate);
if (!predicate.equals(goal.getBeliefName()) || (predicate.equals(goal.getBeliefName())
&& !goal.getValue().equals(predicate.getValue(beliefBase)))) {
if (trace.getFirst().getKey().equals(trace.getLast().getKey())) {
filteredBeliefs.add(predicate);
}
}
}
return filteredBeliefs;
}
/**
* Returns the mapping of predicates and their corresponding list of changes.
*
* @return the beliefChangeTrace.
*/
public Map<Predicate, List<Pair<Boolean, Long>>> getBeliefChangeTrace() {
return beliefChangeTrace;
}
/**
* Returns the time in which the goal related to this goal achievement metadata
* was achieved.
*
* @return the createdTime.
*/
public Long getCreatedTime() {
return createdTime;
}
/**
* Returns the {@link PredicateGoal} related to this goal achievement metadata.
*
* @return the goal.
*/
public PredicateGoal<?> getGoal() {
return goal;
}
/**
* Returns the {@link Intention} related to this goal achievement metadata.
*
* @return the intention.
*/
public Intention getIntention() {
return intention;
}
/**
* Returns the maximum number of successfully executed plans after the
* achievement of the related goal before the reversion process becomes
* deactivated.
*
* @return the maxExecutedPlans.
*/
public Integer getMaxExecutedPlans() {
return maxExecutedPlans;
}
/**
* Returns the maximum time passed since the achievement of the related goal
* before the reversion process becomes deactivated.
*
* @return the maxTime.
*/
public Integer getMaxTime() {
return maxTime;
}
/**
* Returns the number of successfully executed plans after the achievement of
* the related goal.
*
* @return the planCounter.
*/
public Integer getPlanCounter() {
return planCounter;
}
/**
* Return a list of predicates that indicates the condition in which the
* reversion process is activated.
*
* @return the reversionTrigger.
*/
public Map<Predicate, Boolean> getReversionTrigger() {
return reversionTrigger;
}
/**
* Returns a boolean value that indicates the desire of a rollback in case of
* plan failure.
*
* @return true if the rollback is desired.
*/
public Boolean getRollback() {
return rollback;
}
/**
* Increments the counter of successfully executed plans after the achievement
* of the related goal.
*/
public void incrementPlanCounter() {
this.planCounter++;
}
/**
* Returns a boolean value that indicates if the reversion process is activated.
* It returns true if its trigger condition is achieved or a rollback is
* triggered due to a plan failure.
*
* @param beliefBase
* the {@link BeliefBase} in which reversionTrigger will be
* evaluated.
* @return true if the reversion process is activated.
*/
public Boolean isReversionActivated(BeliefBase beliefBase) {
return isReversionTriggered(beliefBase) || isRollbackTriggered();
}
/**
* Returns a boolean value that indicates if reversion is deactivated. It
* returns true if planCounter exceeds maxExecutedPlans or the time passed since
* createdTime exceeds maxTime.
*
* @return true if reversion is deactivated.
*/
public Boolean isReversionDeactivated() {
if (this.planCounter > this.maxExecutedPlans) {
return true;
}
if ((System.currentTimeMillis() - this.createdTime) > this.maxTime) {
return true;
}
return this.intention.getStatus().isFinished() && !this.intention.getStatus().equals(GoalStatus.ACHIEVED);
}
/**
* Returns a boolean value that indicates if the reversion process is activated
* due to the achievement of its trigger condition. It returns true if every
* predicate into reversionTrigger holds true in the given {@link BeliefBase}.
*
* @param beliefBase
* the {@link BeliefBase} in which reversionTrigger will be
* evaluated.
* @return true if reversionTrigger holds true in beliefBase.
*/
public Boolean isReversionTriggered(BeliefBase beliefBase) {
Boolean triggered = true;
if (!this.intention.getStatus().equals(GoalStatus.ACHIEVED) || this.reversionTrigger.isEmpty()) {
triggered = false;
} else {
for (Predicate predicate : this.reversionTrigger.keySet()) {
Boolean value = predicate.getValue(beliefBase);
if (value == null || value != this.reversionTrigger.get(predicate)) {
triggered = false;
}
}
}
return triggered;
}
/**
* Returns a boolean value that indicates if the reversion process is activated
* due to a plan failure. It returns true if a plan fails to achieve the related
* goal and a rollback is desired.
*
* @return true if a rollback is triggered.
*/
public Boolean isRollbackTriggered() {
return this.rollback && this.intention.getStatus().equals(GoalStatus.PLAN_FAILED);
}
/**
* Sets the intention related to this goal achievement metadata.
*
* @param intention
* the intention to be set.
*/
public void setIntention(Intention intention) {
this.intention = intention;
}
/**
* Sets the reversion trigger that indicates the condition in which recorded
* belief changes must be reverted.
*
* @param reversionTrigger
* the reversionTrigger to be set.
*/
public void setReversionTrigger(HashMap<Predicate, Boolean> reversionTrigger) {
this.reversionTrigger = reversionTrigger;
}
/**
* Records a change of the evaluation value of a given predicate, which was made
* during the achievement of the goal related to this goal achievement metadata.
* It also stores the time in which the change was recorded.
*
* @param predicate
* the predicate whose evaluation value changed.
* @param value
* the new evaluation value of the given predicate.
*/
public void trackBeliefChange(Predicate predicate, Boolean value) {
if (!this.beliefChangeTrace.containsKey(predicate)) {
this.beliefChangeTrace.put(predicate, new LinkedList<Pair<Boolean, Long>>());
}
LinkedList<Pair<Boolean, Long>> trace = (LinkedList<Pair<Boolean, Long>>) this.beliefChangeTrace.get(predicate);
trace.add(new Pair<Boolean, Long>(value, System.currentTimeMillis()));
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("< ").append(goal);
sb.append(", maxExecutedPlans = ").append(maxExecutedPlans);
sb.append(", maxTime = ").append(maxTime);
sb.append(", \n reversionTrigger = ").append(reversionTrigger);
sb.append(", \n beliefChangeTrace = ").append(beliefChangeTrace);
sb.append(", rollback = ").append(rollback).append(" >");
return sb.toString();
}
}