/*
* Copyright 2011 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.optaplanner.core.impl.score.director;
import java.util.Collection;
import java.util.Map;
import org.optaplanner.core.api.domain.lookup.LookUpStrategyType;
import org.optaplanner.core.api.domain.lookup.PlanningId;
import org.optaplanner.core.api.domain.solution.PlanningSolution;
import org.optaplanner.core.api.score.Score;
import org.optaplanner.core.api.score.constraint.ConstraintMatch;
import org.optaplanner.core.api.score.constraint.ConstraintMatchTotal;
import org.optaplanner.core.api.score.constraint.Indictment;
import org.optaplanner.core.api.solver.Solver;
import org.optaplanner.core.impl.domain.variable.descriptor.VariableDescriptor;
import org.optaplanner.core.impl.heuristic.move.Move;
import org.optaplanner.core.impl.solver.ProblemFactChange;
/**
* The ScoreDirector holds the {@link PlanningSolution working solution}
* and calculates the {@link Score} for it.
* @param <Solution_> the solution type, the class with the {@link PlanningSolution} annotation
*/
public interface ScoreDirector<Solution_> {
/**
* The {@link PlanningSolution} that is used to calculate the {@link Score}.
* <p>
* Because a {@link Score} is best calculated incrementally (by deltas),
* the {@link ScoreDirector} needs to be notified when its {@link PlanningSolution working solution} changes.
* <p>
* If the {@link PlanningSolution working solution} has been changed since {@link #calculateScore} was called,
* its {@link Score} won't be correct.
* @return never null
*/
Solution_ getWorkingSolution();
/**
* The {@link PlanningSolution working solution} must never be the same instance as the
* {@link PlanningSolution best solution}, it should be a (un)changed clone.
* <p>
* Only call this method on a separate {@link ScoreDirector} instance,
* built by {@link Solver#getScoreDirectorFactory()},
* not on the one used inside the {@link Solver} itself.
* @param workingSolution never null
*/
void setWorkingSolution(Solution_ workingSolution);
/**
* Calculates the {@link Score} and updates the {@link PlanningSolution working solution} accordingly.
* @return never null, the {@link Score} of the {@link PlanningSolution working solution}
*/
Score calculateScore();
/**
* @return true if {@link #getConstraintMatchTotals()} can be called
*/
boolean isConstraintMatchEnabled();
/**
* Explains the {@link Score} of {@link #calculateScore()} by splitting it up per constraint type
* (which is usually a score rule).
* <p>
* The sum of {@link ConstraintMatchTotal#getScoreTotal()} equals {@link #calculateScore()}.
* @return never null
* @throws IllegalStateException if {@link #isConstraintMatchEnabled()} returns false
*/
Collection<ConstraintMatchTotal> getConstraintMatchTotals();
/**
* Explains the impact of each planning entity or problem fact on the {@link Score}.
* An indictment is basically the inverse of {@link #getConstraintMatchTotals()}:
* it is a {@link Score} total for each justification {@link Object}
* in {@link ConstraintMatch#getJustificationList()}.
* <p>
* Warning: In practice, it often doesn't include the full impact on the {@link Score},
* for example in DRL score rules with accumulate, the accumulate elements won't be indicted.
* <p>
* The sum of {@link ConstraintMatchTotal#getScoreTotal()} differs from {@link #calculateScore()}
* because each {@link ConstraintMatch#getScore()} is counted
* for each justification in {@link ConstraintMatch#getJustificationList()}.
* @return never null
* @throws IllegalStateException if {@link #isConstraintMatchEnabled()} returns false
*/
Map<Object, Indictment> getIndictmentMap();
void beforeEntityAdded(Object entity);
void afterEntityAdded(Object entity);
void beforeVariableChanged(Object entity, String variableName);
void afterVariableChanged(Object entity, String variableName);
// TODO VariableDescriptor is not likely to go to public API
void beforeVariableChanged(VariableDescriptor variableDescriptor, Object entity);
void afterVariableChanged(VariableDescriptor variableDescriptor, Object entity);
void changeVariableFacade(VariableDescriptor variableDescriptor, Object entity, Object newValue);
void triggerVariableListeners();
void beforeEntityRemoved(Object entity);
void afterEntityRemoved(Object entity);
// TODO extract this set of methods into a separate interface, only used by ProblemFactChange
void beforeProblemFactAdded(Object problemFact);
void afterProblemFactAdded(Object problemFact);
void beforeProblemPropertyChanged(Object problemFactOrEntity);
void afterProblemPropertyChanged(Object problemFactOrEntity);
void beforeProblemFactRemoved(Object problemFact);
void afterProblemFactRemoved(Object problemFact);
/**
* Translates an entity or fact instance (often from another {@link Thread} or JVM)
* to this {@link ScoreDirector}'s internal working instance.
* Useful during {@link Move} rebasing and in a {@link ProblemFactChange}.
* <p>
* Matching is determined by the {@link LookUpStrategyType} on {@link PlanningSolution}.
* Matching uses a {@link PlanningId} by default.
* @param externalObject sometimes null
* @return null if externalObject is null or if there is no workingObject for externalObject
* @throws IllegalArgumentException if it cannot be located or if the externalObject's class is not supported
* @throws IllegalStateException if it cannot be located
* @param <E> the object type
*/
<E> E lookUpWorkingObject(E externalObject);
/**
* Needs to be called after use because some implementations needs to clean up their resources.
*/
void dispose();
}