diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/common/Lookup.java b/core/src/main/java/ai/timefold/solver/core/api/domain/common/Lookup.java
new file mode 100644
index 00000000000..f9c9d3692fc
--- /dev/null
+++ b/core/src/main/java/ai/timefold/solver/core/api/domain/common/Lookup.java
@@ -0,0 +1,32 @@
+package ai.timefold.solver.core.api.domain.common;
+
+import ai.timefold.solver.core.api.solver.change.ProblemChange;
+import ai.timefold.solver.core.preview.api.move.Move;
+
+import org.jspecify.annotations.Nullable;
+
+/**
+ * Allows to transfer an entity or fact instance (often from another {@link Thread})
+ * to another working solution.
+ */
+public interface Lookup {
+
+ /**
+ * Translates an entity or fact instance (often from another {@link Thread})
+ * to another working solution.
+ * Useful for {@link Move#rebase(Lookup) move rebasing}
+ * and in a {@link ProblemChange} and for multi-threaded solving.
+ *
+ * Matching uses {@link PlanningId}.
+ *
+ * @param problemFactOrPlanningEntity The fact or entity to rebase.
+ * @return null if problemFactOrPlanningEntity is null
+ * @throws IllegalArgumentException if there is no working object for the fact or entity,
+ * if it cannot be looked up,
+ * or if its class is not supported.
+ * @throws IllegalStateException if it cannot be looked up
+ * @param the object type
+ */
+ @Nullable T lookUpWorkingObject(@Nullable T problemFactOrPlanningEntity);
+
+}
diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/common/PlanningId.java b/core/src/main/java/ai/timefold/solver/core/api/domain/common/PlanningId.java
index 0dc1e6db10c..48185df2a9e 100644
--- a/core/src/main/java/ai/timefold/solver/core/api/domain/common/PlanningId.java
+++ b/core/src/main/java/ai/timefold/solver/core/api/domain/common/PlanningId.java
@@ -10,13 +10,12 @@
import ai.timefold.solver.core.api.domain.entity.PlanningEntity;
import ai.timefold.solver.core.api.domain.solution.ProblemFactCollectionProperty;
import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.api.solver.change.ProblemChange;
import ai.timefold.solver.core.preview.api.move.Move;
/**
* Specifies that a bean property (or a field) is the id to match
- * when {@link ScoreDirector#lookUpWorkingObject(Object) locating}
+ * when {@link Lookup#lookUpWorkingObject(Object) looking up}
* an externalObject (often from another {@link Thread} or JVM).
* Used during {@link Move} rebasing and in a {@link ProblemChange}.
*
diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/solution/PlanningEntityCollectionProperty.java b/core/src/main/java/ai/timefold/solver/core/api/domain/solution/PlanningEntityCollectionProperty.java
index 2fb7294fda4..7ba77a36d82 100644
--- a/core/src/main/java/ai/timefold/solver/core/api/domain/solution/PlanningEntityCollectionProperty.java
+++ b/core/src/main/java/ai/timefold/solver/core/api/domain/solution/PlanningEntityCollectionProperty.java
@@ -12,13 +12,12 @@
import java.util.SortedSet;
import ai.timefold.solver.core.api.domain.entity.PlanningEntity;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
/**
* Specifies that a property (or a field) on a {@link PlanningSolution} class is a {@link Collection} of planning entities.
*
* Every element in the planning entity collection should have the {@link PlanningEntity} annotation.
- * Every element in the planning entity collection will be added to the {@link ScoreDirector}.
+ * Every element in the planning entity collection will be registered with the solver.
*
* For solver reproducibility, the collection must have a deterministic, stable iteration order.
* It is recommended to use a {@link List}, {@link LinkedHashSet} or {@link SortedSet}.
diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/solution/PlanningEntityProperty.java b/core/src/main/java/ai/timefold/solver/core/api/domain/solution/PlanningEntityProperty.java
index e7a6779138a..fc5ba1bf6ef 100644
--- a/core/src/main/java/ai/timefold/solver/core/api/domain/solution/PlanningEntityProperty.java
+++ b/core/src/main/java/ai/timefold/solver/core/api/domain/solution/PlanningEntityProperty.java
@@ -8,13 +8,12 @@
import java.lang.annotation.Target;
import ai.timefold.solver.core.api.domain.entity.PlanningEntity;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
/**
* Specifies that a property (or a field) on a {@link PlanningSolution} class is a planning entity.
*
* The planning entity should have the {@link PlanningEntity} annotation.
- * The planning entity will be added to the {@link ScoreDirector}.
+ * The planning entity will be registered with the solver.
*/
@Target({ METHOD, FIELD })
@Retention(RUNTIME)
diff --git a/core/src/main/java/ai/timefold/solver/core/api/score/director/ScoreDirector.java b/core/src/main/java/ai/timefold/solver/core/api/score/director/ScoreDirector.java
deleted file mode 100644
index 24f17aa8041..00000000000
--- a/core/src/main/java/ai/timefold/solver/core/api/score/director/ScoreDirector.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package ai.timefold.solver.core.api.score.director;
-
-import ai.timefold.solver.core.api.domain.common.PlanningId;
-import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
-import ai.timefold.solver.core.api.score.Score;
-import ai.timefold.solver.core.api.solver.change.ProblemChange;
-
-import org.jspecify.annotations.NullMarked;
-import org.jspecify.annotations.Nullable;
-
-/**
- * The ScoreDirector holds the {@link PlanningSolution working solution}
- * and calculates the {@link Score} for it.
- *
- * @param the solution type, the class with the {@link PlanningSolution} annotation
- */
-@NullMarked
-public interface ScoreDirector {
-
- /**
- * The {@link PlanningSolution} that is used to calculate the {@link Score}.
- *
- * Because a {@link Score} is best calculated incrementally (by deltas),
- * the {@link ScoreDirector} needs to be notified when its {@link PlanningSolution working solution} changes.
- */
- Solution_ getWorkingSolution();
-
- void beforeVariableChanged(Object entity, String variableName);
-
- void afterVariableChanged(Object entity, String variableName);
-
- void beforeListVariableElementAssigned(Object entity, String variableName, Object element);
-
- void afterListVariableElementAssigned(Object entity, String variableName, Object element);
-
- void beforeListVariableElementUnassigned(Object entity, String variableName, Object element);
-
- void afterListVariableElementUnassigned(Object entity, String variableName, Object element);
-
- void beforeListVariableChanged(Object entity, String variableName, int fromIndex, int toIndex);
-
- void afterListVariableChanged(Object entity, String variableName, int fromIndex, int toIndex);
-
- void triggerVariableListeners();
-
- /**
- * Translates an entity or fact instance (often from another {@link Thread} or JVM)
- * to this {@link ScoreDirector}'s internal working instance.
- * Useful for move rebasing and in a {@link ProblemChange}.
- *
- * Matching uses {@link PlanningId}.
- *
- * @return null if externalObject is null
- * @throws IllegalArgumentException if there is no workingObject for externalObject, if it cannot be looked up
- * or if the externalObject's class is not supported
- * @throws IllegalStateException if it cannot be looked up
- * @param the object type
- */
- @Nullable E lookUpWorkingObject(@Nullable E externalObject);
-
- /**
- * As defined by {@link #lookUpWorkingObject(Object)},
- * but doesn't fail fast if no workingObject was ever added for the externalObject.
- * It's recommended to use {@link #lookUpWorkingObject(Object)} instead,
- * especially in move rebasing code.
- *
- * @return null if externalObject is null, or if there is no workingObject for externalObject
- * @throws IllegalArgumentException if it cannot be looked up or if the externalObject's class is not supported
- * @throws IllegalStateException if it cannot be looked up
- * @param the object type
- */
- @Nullable E lookUpWorkingObjectOrReturnNull(@Nullable E externalObject);
-
-}
diff --git a/core/src/main/java/ai/timefold/solver/core/api/solver/change/ProblemChangeDirector.java b/core/src/main/java/ai/timefold/solver/core/api/solver/change/ProblemChangeDirector.java
index 1072d299070..89d224a6a71 100644
--- a/core/src/main/java/ai/timefold/solver/core/api/solver/change/ProblemChangeDirector.java
+++ b/core/src/main/java/ai/timefold/solver/core/api/solver/change/ProblemChangeDirector.java
@@ -1,9 +1,8 @@
package ai.timefold.solver.core.api.solver.change;
-import java.util.Optional;
import java.util.function.Consumer;
-import ai.timefold.solver.core.api.domain.common.PlanningId;
+import ai.timefold.solver.core.api.domain.common.Lookup;
import ai.timefold.solver.core.api.domain.entity.PlanningEntity;
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
import ai.timefold.solver.core.api.domain.variable.PlanningVariable;
@@ -21,7 +20,8 @@
* To see an example implementation, please refer to the {@link ProblemChange} Javadoc.
*/
@NullMarked
-public interface ProblemChangeDirector {
+public interface ProblemChangeDirector
+ extends Lookup {
/**
* Add a new {@link PlanningEntity} instance into the {@link PlanningSolution working solution}.
@@ -35,7 +35,7 @@ public interface ProblemChangeDirector {
/**
* Remove an existing {@link PlanningEntity} instance from the {@link PlanningSolution working solution}.
* Translates the entity to a working planning entity by performing a lookup as defined by
- * {@link #lookUpWorkingObjectOrFail(Object)}.
+ * {@link #lookUpWorkingObject(Object)}.
*
* @param entity the {@link PlanningEntity} instance
* @param entityConsumer removes the working entity from the {@link PlanningSolution working solution}
@@ -45,7 +45,7 @@ public interface ProblemChangeDirector {
/**
* Change a {@link PlanningVariable} value of a {@link PlanningEntity}. Translates the entity to a working
- * planning entity by performing a lookup as defined by {@link #lookUpWorkingObjectOrFail(Object)}.
+ * planning entity by performing a lookup as defined by {@link #lookUpWorkingObject(Object)}.
*
* @param entity the {@link PlanningEntity} instance
* @param variableName name of the {@link PlanningVariable}
@@ -66,7 +66,7 @@ public interface ProblemChangeDirector {
/**
* Remove an existing problem fact from the {@link PlanningSolution working solution}. Translates the problem fact
- * to a working problem fact by performing a lookup as defined by {@link #lookUpWorkingObjectOrFail(Object)}.
+ * to a working problem fact by performing a lookup as defined by {@link #lookUpWorkingObject(Object)}.
*
* @param problemFact the problem fact instance
* @param problemFactConsumer removes the working problem fact from the
@@ -78,7 +78,7 @@ public interface ProblemChangeDirector {
/**
* Change a property of either a {@link PlanningEntity} or a problem fact. Translates the entity or the problem fact
* to its {@link PlanningSolution working solution} counterpart by performing a lookup as defined by
- * {@link #lookUpWorkingObjectOrFail(Object)}.
+ * {@link #lookUpWorkingObject(Object)}.
*
* @param problemFactOrEntity the {@link PlanningEntity} or the problem fact instance
* @param problemFactOrEntityConsumer updates the property of the {@link PlanningEntity}
@@ -88,31 +88,8 @@ public interface ProblemChangeDirector {
void changeProblemProperty(EntityOrProblemFact problemFactOrEntity,
Consumer problemFactOrEntityConsumer);
- /**
- * Translate an entity or fact instance (often from another {@link Thread} or JVM)
- * to this {@link ProblemChangeDirector}'s internal working instance.
- *
- * Matching uses {@link PlanningId}.
- *
- * @return null if externalObject is null
- * @throws IllegalArgumentException if there is no workingObject for externalObject, if it cannot be looked up
- * or if the externalObject's class is not supported
- * @throws IllegalStateException if it cannot be looked up
- * @param the object type
- */
- @Nullable EntityOrProblemFact lookUpWorkingObjectOrFail(@Nullable EntityOrProblemFact externalObject);
-
- /**
- * As defined by {@link #lookUpWorkingObjectOrFail(Object)},
- * but doesn't fail fast if no workingObject was ever added for the externalObject.
- * It's recommended to use {@link #lookUpWorkingObjectOrFail(Object)} instead.
- *
- * @return {@link Optional#empty()} if there is no workingObject for externalObject, or if externalObject is null
- * @throws IllegalArgumentException if it cannot be looked up or if the externalObject's class is not supported
- * @throws IllegalStateException if it cannot be looked up
- * @param the object type
- */
- Optional lookUpWorkingObject(@Nullable EntityOrProblemFact externalObject);
+ @Override
+ @Nullable EntityOrProblemFact lookUpWorkingObject(@Nullable EntityOrProblemFact externalObject);
/**
* Calls variable listeners on the external changes submitted so far.
diff --git a/core/src/main/java/ai/timefold/solver/core/api/solver/phase/PhaseCommand.java b/core/src/main/java/ai/timefold/solver/core/api/solver/phase/PhaseCommand.java
index a34b92adb81..78f0e1fbc20 100644
--- a/core/src/main/java/ai/timefold/solver/core/api/solver/phase/PhaseCommand.java
+++ b/core/src/main/java/ai/timefold/solver/core/api/solver/phase/PhaseCommand.java
@@ -1,22 +1,16 @@
package ai.timefold.solver.core.api.solver.phase;
-import java.util.function.BooleanSupplier;
-
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
-import ai.timefold.solver.core.api.score.Score;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.api.solver.Solver;
import ai.timefold.solver.core.api.solver.change.ProblemChange;
import ai.timefold.solver.core.impl.phase.Phase;
-import ai.timefold.solver.core.impl.score.director.InnerScoreDirector;
+import ai.timefold.solver.core.preview.api.move.Move;
import org.jspecify.annotations.NullMarked;
/**
* Runs a custom algorithm as a {@link Phase} of the {@link Solver} that changes the planning variables.
- * To change problem facts, use {@link Solver#addProblemChange(ProblemChange)} instead.
- *
- * To add custom properties, configure custom properties and add public setters for them.
+ * To change problem facts and to add or remove entities, use {@link Solver#addProblemChange(ProblemChange)} instead.
*
* @param the solution type, the class with the {@link PlanningSolution} annotation
*/
@@ -24,23 +18,17 @@
public interface PhaseCommand {
/**
- * Changes {@link PlanningSolution working solution} of {@link ScoreDirector#getWorkingSolution()}.
- * When the {@link PlanningSolution working solution} is modified,
- * the {@link ScoreDirector} must be correctly notified
- * (through {@link ScoreDirector#beforeVariableChanged(Object, String)} and
- * {@link ScoreDirector#afterVariableChanged(Object, String)}),
- * otherwise calculated {@link Score}s will be corrupted.
+ * Changes the current {@link PhaseCommandContext#getWorkingSolution() working solution}.
+ * The solver is notified of the changes through {@link PhaseCommandContext},
+ * specifically through {@link PhaseCommandContext#executeAndCalculateScore(Move)}.
+ * Any other modifications to the working solution are strictly forbidden
+ * and will likely cause the solver to be in an inconsistent state and throw an exception later on.
*
- * Don't forget to call {@link ScoreDirector#triggerVariableListeners()} after each set of changes
- * (especially before every {@link InnerScoreDirector#calculateScore()} call)
- * to ensure all shadow variables are updated.
+ * Don't forget to check {@link PhaseCommandContext#isPhaseTerminated() termination status} frequently
+ * to allow the solver to gracefully terminate when necessary.
*
- * @param scoreDirector the {@link ScoreDirector} that needs to get notified of the changes.
- * @param isPhaseTerminated long-running command implementations should check this periodically
- * and terminate early if it returns true.
- * Otherwise the terminations configured by the user will have no effect,
- * as the solver can only terminate itself when a command has ended.
+ * @param context the context of the command, providing access to the working solution and allowing move execution
*/
- void changeWorkingSolution(ScoreDirector scoreDirector, BooleanSupplier isPhaseTerminated);
+ void changeWorkingSolution(PhaseCommandContext context);
}
diff --git a/core/src/main/java/ai/timefold/solver/core/api/solver/phase/PhaseCommandContext.java b/core/src/main/java/ai/timefold/solver/core/api/solver/phase/PhaseCommandContext.java
new file mode 100644
index 00000000000..2d3d388e31d
--- /dev/null
+++ b/core/src/main/java/ai/timefold/solver/core/api/solver/phase/PhaseCommandContext.java
@@ -0,0 +1,96 @@
+package ai.timefold.solver.core.api.solver.phase;
+
+import java.util.function.Function;
+
+import ai.timefold.solver.core.api.domain.common.Lookup;
+import ai.timefold.solver.core.api.score.Score;
+import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningSolutionMetaModel;
+import ai.timefold.solver.core.preview.api.move.Move;
+
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
+
+/**
+ * The context of a command that is executed during a custom phase.
+ * It provides access to the working solution and allows executing moves.
+ *
+ * @param the type of the solution
+ * @see PhaseCommand
+ */
+@NullMarked
+public interface PhaseCommandContext
+ extends Lookup {
+
+ /**
+ * Returns the meta-model of the {@link #getWorkingSolution() working solution}.
+ *
+ * @return the meta-model of the working solution
+ */
+ PlanningSolutionMetaModel getSolutionMetaModel();
+
+ /**
+ * Returns the current working solution.
+ * It must not be modified directly,
+ * but only through {@link #executeAndCalculateScore(Move)} or {@link #executeTemporarily(Move, Function)}.
+ * Direct modifications will cause the solver to be in an inconsistent state and likely throw an exception later on.
+ *
+ * @return the current working solution
+ */
+ Solution_ getWorkingSolution();
+
+ /**
+ * Long-running command implementations should check this periodically and terminate early if it returns true.
+ * Otherwise the terminations configured by the user will have no effect,
+ * as the solver can only terminate itself when a command has ended.
+ *
+ * @return true if the solver has requested the phase to terminate,
+ * for example because the time limit has been reached.
+ */
+ boolean isPhaseTerminated();
+
+ /**
+ * Executes the given move and updates the working solution
+ * without recalculating the score for performance reasons.
+ *
+ * @param move the move to execute
+ */
+ void execute(Move move);
+
+ /**
+ * Executes the given move and updates the working solution,
+ * and returns the new score of the working solution.
+ *
+ * @param move the move to execute
+ * @return the new score of the working solution after executing the move
+ */
+ > Score_ executeAndCalculateScore(Move move);
+
+ /**
+ * As defined by {@link #executeTemporarily(Move, Function, boolean)},
+ * with the guarantee of a fresh score.
+ */
+ default @Nullable Result_ executeTemporarily(Move move,
+ Function temporarySolutionConsumer) {
+ return executeTemporarily(move, temporarySolutionConsumer, true);
+ }
+
+ /**
+ * Executes the given move temporarily and returns the result of the given consumer.
+ * The working solution is reverted to its original state after the consumer has been executed,
+ * optionally without recalculating the score for performance reasons.
+ *
+ * @param move the move to execute temporarily
+ * @param temporarySolutionConsumer the consumer to execute with the temporarily modified solution;
+ * this solution must not be modified any further.
+ * @param guaranteeFreshScore if true, the score of {@link #getWorkingSolution()} after this method returns
+ * is guaranteed to be up-to-date;
+ * otherwise it may be stale as the solver will skip recalculating it for performance reasons.
+ * @return the result of the consumer
+ */
+ @Nullable Result_ executeTemporarily(Move move,
+ Function temporarySolutionConsumer, boolean guaranteeFreshScore);
+
+ @Override
+ @Nullable T lookUpWorkingObject(@Nullable T problemFactOrPlanningEntity);
+
+}
diff --git a/core/src/main/java/ai/timefold/solver/core/config/partitionedsearch/PartitionedSearchPhaseConfig.java b/core/src/main/java/ai/timefold/solver/core/config/partitionedsearch/PartitionedSearchPhaseConfig.java
index 12eabde6774..c3cb27d9bb3 100644
--- a/core/src/main/java/ai/timefold/solver/core/config/partitionedsearch/PartitionedSearchPhaseConfig.java
+++ b/core/src/main/java/ai/timefold/solver/core/config/partitionedsearch/PartitionedSearchPhaseConfig.java
@@ -9,7 +9,6 @@
import jakarta.xml.bind.annotation.XmlType;
import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.api.solver.Solver;
import ai.timefold.solver.core.config.constructionheuristic.ConstructionHeuristicPhaseConfig;
import ai.timefold.solver.core.config.exhaustivesearch.ExhaustiveSearchPhaseConfig;
@@ -81,7 +80,7 @@ public void setSolutionPartitionerCustomProperties(@Nullable Map
*
*
* The number of {@link Thread}s is always equal to the number of partitions returned by
- * {@link SolutionPartitioner#splitWorkingSolution(ScoreDirector, Integer)},
+ * {@link SolutionPartitioner#splitWorkingSolution(Object, Integer)},
* because otherwise some partitions would never run (especially with {@link Solver#terminateEarly() asynchronous
* termination}).
* If this limit (or {@link Runtime#availableProcessors()}) is lower than the number of partitions,
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/lookup/ImmutableLookUpStrategy.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/ImmutableLookupStrategy.java
similarity index 63%
rename from core/src/main/java/ai/timefold/solver/core/impl/domain/lookup/ImmutableLookUpStrategy.java
rename to core/src/main/java/ai/timefold/solver/core/impl/domain/common/ImmutableLookupStrategy.java
index bf3bafaa18c..c17aaa6b90c 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/lookup/ImmutableLookUpStrategy.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/ImmutableLookupStrategy.java
@@ -1,11 +1,11 @@
-package ai.timefold.solver.core.impl.domain.lookup;
+package ai.timefold.solver.core.impl.domain.common;
import java.util.Map;
import org.jspecify.annotations.NullMarked;
@NullMarked
-final class ImmutableLookUpStrategy implements LookUpStrategy {
+final class ImmutableLookupStrategy implements LookupStrategy {
@Override
public void addWorkingObject(Map idToWorkingObjectMap, Object workingObject) {
@@ -23,10 +23,4 @@ public E lookUpWorkingObject(Map idToWorkingObjectMap, E ext
return externalObject;
}
- @Override
- public E lookUpWorkingObjectIfExists(Map idToWorkingObjectMap, E externalObject) {
- // Because it is immutable, we can use the same one.
- return externalObject;
- }
-
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/lookup/LookUpStrategyType.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/LookUpStrategyType.java
similarity index 90%
rename from core/src/main/java/ai/timefold/solver/core/impl/domain/lookup/LookUpStrategyType.java
rename to core/src/main/java/ai/timefold/solver/core/impl/domain/common/LookUpStrategyType.java
index 92a33b6c092..6de52c8a9c5 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/lookup/LookUpStrategyType.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/LookUpStrategyType.java
@@ -1,9 +1,9 @@
-package ai.timefold.solver.core.impl.domain.lookup;
+package ai.timefold.solver.core.impl.domain.common;
import ai.timefold.solver.core.api.domain.common.PlanningId;
import ai.timefold.solver.core.api.domain.entity.PlanningEntity;
import ai.timefold.solver.core.api.domain.solution.ProblemFactCollectionProperty;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
/**
* Determines how {@link ScoreDirector#lookUpWorkingObject(Object)} maps
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/common/LookupManager.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/LookupManager.java
new file mode 100644
index 00000000000..9ca5f2b765b
--- /dev/null
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/LookupManager.java
@@ -0,0 +1,50 @@
+package ai.timefold.solver.core.impl.domain.common;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import ai.timefold.solver.core.api.domain.common.Lookup;
+import ai.timefold.solver.core.api.domain.common.PlanningId;
+
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
+
+/**
+ * @see PlanningId
+ * @see Lookup
+ */
+@NullMarked
+public final class LookupManager
+ implements Lookup {
+
+ private final LookupStrategyResolver lookupStrategyResolver;
+ private final Map idToWorkingObjectMap = new HashMap<>();
+
+ public LookupManager(LookupStrategyResolver lookupStrategyResolver) {
+ this.lookupStrategyResolver = lookupStrategyResolver;
+ }
+
+ public void reset() {
+ idToWorkingObjectMap.clear();
+ }
+
+ public void addWorkingObject(Object workingObject) {
+ var lookupStrategy = lookupStrategyResolver.determineLookUpStrategy(workingObject);
+ lookupStrategy.addWorkingObject(idToWorkingObjectMap, workingObject);
+ }
+
+ public void removeWorkingObject(Object workingObject) {
+ var lookupStrategy = lookupStrategyResolver.determineLookUpStrategy(workingObject);
+ lookupStrategy.removeWorkingObject(idToWorkingObjectMap, workingObject);
+ }
+
+ @Override
+ public @Nullable E lookUpWorkingObject(@Nullable E externalObject) {
+ if (externalObject == null) {
+ return null;
+ }
+ var lookupStrategy = lookupStrategyResolver.determineLookUpStrategy(externalObject);
+ return lookupStrategy.lookUpWorkingObject(idToWorkingObjectMap, externalObject);
+ }
+
+}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/lookup/LookUpStrategy.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/LookupStrategy.java
similarity index 52%
rename from core/src/main/java/ai/timefold/solver/core/impl/domain/lookup/LookUpStrategy.java
rename to core/src/main/java/ai/timefold/solver/core/impl/domain/common/LookupStrategy.java
index 8672b61f152..f6237205d3a 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/lookup/LookUpStrategy.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/LookupStrategy.java
@@ -1,13 +1,12 @@
-package ai.timefold.solver.core.impl.domain.lookup;
+package ai.timefold.solver.core.impl.domain.common;
import java.util.Map;
import org.jspecify.annotations.NullMarked;
-import org.jspecify.annotations.Nullable;
@NullMarked
-public sealed interface LookUpStrategy
- permits ImmutableLookUpStrategy, NoneLookUpStrategy, PlanningIdLookUpStrategy {
+public sealed interface LookupStrategy
+ permits ImmutableLookupStrategy, NoneLookupStrategy, PlanningIdLookupStrategy {
void addWorkingObject(Map idToWorkingObjectMap, Object workingObject);
@@ -15,6 +14,4 @@ public sealed interface LookUpStrategy
E lookUpWorkingObject(Map idToWorkingObjectMap, E externalObject);
- @Nullable E lookUpWorkingObjectIfExists(Map idToWorkingObjectMap, E externalObject);
-
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/lookup/LookUpStrategyResolver.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/LookupStrategyResolver.java
similarity index 80%
rename from core/src/main/java/ai/timefold/solver/core/impl/domain/lookup/LookUpStrategyResolver.java
rename to core/src/main/java/ai/timefold/solver/core/impl/domain/common/LookupStrategyResolver.java
index 0060cadb565..946feae8f0b 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/lookup/LookUpStrategyResolver.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/LookupStrategyResolver.java
@@ -1,11 +1,10 @@
-package ai.timefold.solver.core.impl.domain.lookup;
+package ai.timefold.solver.core.impl.domain.common;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import ai.timefold.solver.core.api.domain.common.PlanningId;
import ai.timefold.solver.core.config.util.ConfigUtils;
-import ai.timefold.solver.core.impl.domain.common.DomainAccessType;
import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorFactory;
import ai.timefold.solver.core.impl.domain.policy.DescriptorPolicy;
import ai.timefold.solver.core.impl.domain.solution.cloner.DeepCloningUtils;
@@ -16,18 +15,18 @@
* This class is thread-safe.
*/
@NullMarked
-public final class LookUpStrategyResolver {
+public final class LookupStrategyResolver {
private final LookUpStrategyType lookUpStrategyType;
private final DomainAccessType domainAccessType;
private final MemberAccessorFactory memberAccessorFactory;
- private final Map, LookUpStrategy> decisionCache = new ConcurrentHashMap<>();
+ private final Map, LookupStrategy> decisionCache = new ConcurrentHashMap<>();
- public LookUpStrategyResolver(DescriptorPolicy descriptorPolicy) {
+ public LookupStrategyResolver(DescriptorPolicy descriptorPolicy) {
this(descriptorPolicy, LookUpStrategyType.PLANNING_ID_OR_NONE);
}
- LookUpStrategyResolver(DescriptorPolicy descriptorPolicy, LookUpStrategyType lookUpStrategyType) {
+ LookupStrategyResolver(DescriptorPolicy descriptorPolicy, LookUpStrategyType lookUpStrategyType) {
this.lookUpStrategyType = lookUpStrategyType;
this.domainAccessType = descriptorPolicy.getDomainAccessType();
this.memberAccessorFactory = descriptorPolicy.getMemberAccessorFactory();
@@ -41,22 +40,22 @@ public LookUpStrategyResolver(DescriptorPolicy descriptorPolicy) {
* @param object never null
* @return never null
*/
- public LookUpStrategy determineLookUpStrategy(Object object) {
+ public LookupStrategy determineLookUpStrategy(Object object) {
var objectClass = object.getClass();
var decision = decisionCache.get(objectClass);
if (decision == null) { // Simulate computeIfAbsent, avoiding creating a lambda on the hot path.
if (DeepCloningUtils.isImmutable(objectClass)) {
- decision = new ImmutableLookUpStrategy();
+ decision = new ImmutableLookupStrategy();
} else {
decision = switch (lookUpStrategyType) {
- case NONE -> new NoneLookUpStrategy();
+ case NONE -> new NoneLookupStrategy();
case PLANNING_ID_OR_NONE -> {
var memberAccessor =
ConfigUtils.findPlanningIdMemberAccessor(objectClass, memberAccessorFactory, domainAccessType);
if (memberAccessor == null) {
- yield new NoneLookUpStrategy();
+ yield new NoneLookupStrategy();
}
- yield new PlanningIdLookUpStrategy(memberAccessor);
+ yield new PlanningIdLookupStrategy(memberAccessor);
}
case PLANNING_ID_OR_FAIL_FAST -> {
var memberAccessor =
@@ -68,7 +67,7 @@ The class (%s) does not have a @%s annotation, but the lookUpStrategyType (%s) r
.formatted(objectClass, PlanningId.class.getSimpleName(), lookUpStrategyType,
PlanningId.class.getSimpleName()));
}
- yield new PlanningIdLookUpStrategy(memberAccessor);
+ yield new PlanningIdLookupStrategy(memberAccessor);
}
};
decisionCache.put(objectClass, decision);
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/lookup/NoneLookUpStrategy.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/NoneLookupStrategy.java
similarity index 75%
rename from core/src/main/java/ai/timefold/solver/core/impl/domain/lookup/NoneLookUpStrategy.java
rename to core/src/main/java/ai/timefold/solver/core/impl/domain/common/NoneLookupStrategy.java
index a1028e946c9..7d60d231b2b 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/lookup/NoneLookUpStrategy.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/NoneLookupStrategy.java
@@ -1,4 +1,4 @@
-package ai.timefold.solver.core.impl.domain.lookup;
+package ai.timefold.solver.core.impl.domain.common;
import java.util.Map;
@@ -7,7 +7,7 @@
import org.jspecify.annotations.NullMarked;
@NullMarked
-final class NoneLookUpStrategy implements LookUpStrategy {
+final class NoneLookupStrategy implements LookupStrategy {
@Override
public void addWorkingObject(Map idToWorkingObjectMap, Object workingObject) {
@@ -28,9 +28,4 @@ The externalObject (%s) cannot be looked up.
.formatted(externalObject, PlanningId.class.getSimpleName(), externalObject.getClass()));
}
- @Override
- public E lookUpWorkingObjectIfExists(Map idToWorkingObjectMap, E externalObject) {
- return lookUpWorkingObject(idToWorkingObjectMap, externalObject);
- }
-
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/lookup/PlanningIdLookUpStrategy.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/PlanningIdLookupStrategy.java
similarity index 86%
rename from core/src/main/java/ai/timefold/solver/core/impl/domain/lookup/PlanningIdLookUpStrategy.java
rename to core/src/main/java/ai/timefold/solver/core/impl/domain/common/PlanningIdLookupStrategy.java
index 568cd31a5c3..52930b85c06 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/lookup/PlanningIdLookUpStrategy.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/common/PlanningIdLookupStrategy.java
@@ -1,4 +1,4 @@
-package ai.timefold.solver.core.impl.domain.lookup;
+package ai.timefold.solver.core.impl.domain.common;
import java.util.Map;
@@ -7,14 +7,13 @@
import ai.timefold.solver.core.impl.util.Pair;
import org.jspecify.annotations.NullMarked;
-import org.jspecify.annotations.Nullable;
@NullMarked
-final class PlanningIdLookUpStrategy implements LookUpStrategy {
+final class PlanningIdLookupStrategy implements LookupStrategy {
private final MemberAccessor planningIdMemberAccessor;
- public PlanningIdLookUpStrategy(MemberAccessor planningIdMemberAccessor) {
+ public PlanningIdLookupStrategy(MemberAccessor planningIdMemberAccessor) {
this.planningIdMemberAccessor = planningIdMemberAccessor;
}
@@ -56,13 +55,6 @@ The externalObject (%s) with planningId (%s) has no known workingObject (%s).
return (E) workingObject;
}
- @SuppressWarnings("unchecked")
- @Override
- public @Nullable E lookUpWorkingObjectIfExists(Map idToWorkingObjectMap, E externalObject) {
- var planningId = extractPlanningId(externalObject);
- return (E) idToWorkingObjectMap.get(planningId);
- }
-
private Pair, Object> extractPlanningId(Object externalObject) {
var planningId = planningIdMemberAccessor.executeGetter(externalObject);
if (planningId == null) {
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/entity/descriptor/EntityDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/entity/descriptor/EntityDescriptor.java
index 6a79286449b..babedd13e5b 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/entity/descriptor/EntityDescriptor.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/entity/descriptor/EntityDescriptor.java
@@ -21,7 +21,6 @@
import java.util.Map;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
-import java.util.function.Predicate;
import ai.timefold.solver.core.api.domain.entity.PlanningEntity;
import ai.timefold.solver.core.api.domain.entity.PlanningPin;
@@ -64,10 +63,7 @@
import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningEntityMetaModel;
import ai.timefold.solver.core.preview.api.neighborhood.stream.function.UniNeighborhoodsPredicate;
-import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
/**
* @param the solution type, the class with the {@link PlanningSolution} annotation
@@ -86,13 +82,10 @@ public class EntityDescriptor {
ShadowVariablesInconsistent.class
};
- private static final Logger LOGGER = LoggerFactory.getLogger(EntityDescriptor.class);
-
private final int ordinal;
private final SolutionDescriptor solutionDescriptor;
private final Class> entityClass;
private final List> declaredInheritedEntityClassList = new ArrayList<>();
- private final Predicate isInitializedPredicate;
private final List declaredPlanningPinIndexMemberAccessorList = new ArrayList<>();
@Nullable
private ShadowVariablesInconsistentVariableDescriptor shadowVariablesInconsistentDescriptor;
@@ -142,17 +135,12 @@ public EntityDescriptor(int ordinal, SolutionDescriptor solutionDescr
this.solutionDescriptor = solutionDescriptor;
this.entityClass = entityClass;
this.declaredInheritedEntityClassList.addAll(extractInheritedClasses(entityClass));
- isInitializedPredicate = this::isInitialized;
if (entityClass.getPackage() == null) {
- LOGGER.warn("The entityClass ({}) should be in a proper java package.", entityClass);
+ throw new IllegalStateException("The entityClass (%s) must not sit in the unnamed package."
+ .formatted(entityClass.getCanonicalName()));
}
}
- @SuppressWarnings("unchecked")
- public static Collection> getVariableAnnotationClasses() {
- return List.of(VARIABLE_ANNOTATION_CLASSES);
- }
-
/**
* A number unique within a {@link SolutionDescriptor}, increasing sequentially from zero.
* Used for indexing in arrays to avoid object hash lookups in maps.
@@ -163,18 +151,6 @@ public int getOrdinal() {
return ordinal;
}
- /**
- * Using entityDescriptor::isInitialized directly breaks node sharing
- * because it creates multiple instances of this {@link Predicate}.
- *
- * @deprecated Prefer {@link #getEntityForEachFilter()} ()}.
- * @return never null, always the same {@link Predicate} instance to {@link #isInitialized(Object)}
- */
- @Deprecated(forRemoval = true)
- public Predicate getIsInitializedPredicate() {
- return isInitializedPredicate;
- }
-
public EntityForEachFilter getEntityForEachFilter() {
if (entityForEachFilter == null) {
entityForEachFilter = new EntityForEachFilter(this);
@@ -206,7 +182,7 @@ public void processAnnotations(DescriptorPolicy descriptorPolicy) {
&& declaredInheritedEntityClassList.isEmpty()) {
throw new IllegalStateException(
"The entityClass (%s) should have at least 1 getter method or 1 field with a %s annotation or a shadow variable annotation."
- .formatted(entityClass, PlanningVariable.class.getSimpleName()));
+ .formatted(entityClass.getCanonicalName(), PlanningVariable.class.getSimpleName()));
}
processVariableAnnotations(descriptorPolicy);
}
@@ -219,7 +195,7 @@ private void processEntityAnnotations() {
if (entityAnnotation == null && declaredInheritedEntityClassList.isEmpty()) {
throw new IllegalStateException(
"The entityClass (%s) has been specified as a planning entity in the configuration, but does not have a @%s annotation."
- .formatted(entityClass, PlanningEntity.class.getSimpleName()));
+ .formatted(entityClass.getCanonicalName(), PlanningEntity.class.getSimpleName()));
}
// We use the parent class of the entity as the base annotation
if (entityAnnotation == null) {
@@ -250,7 +226,8 @@ private void processSorting(PlanningEntity entityAnnotation) {
if (comparatorClass != null && comparatorFactoryClass != null) {
throw new IllegalStateException(
"The entityClass (%s) cannot have a comparatorClass (%s) and a comparatorFactoryClass (%s) at the same time."
- .formatted(entityClass, comparatorClass.getName(), comparatorFactoryClass.getName()));
+ .formatted(entityClass.getCanonicalName(), comparatorClass.getName(),
+ comparatorFactoryClass.getName()));
}
if (comparatorClass != null) {
var comparator = ConfigUtils.newInstance(this::toString, "comparatorClass", comparatorClass);
@@ -319,12 +296,13 @@ private void registerVariableAccessor(int nextVariableDescriptorOrdinal,
throw new IllegalStateException("""
The entityClass (%s) has a @%s annotated member (%s), duplicated by member for variableDescriptor (%s).
Maybe the annotation is defined on both the field and its getter."""
- .formatted(entityClass, variableAnnotationClass.getSimpleName(), memberAccessor, duplicate));
+ .formatted(entityClass.getCanonicalName(), variableAnnotationClass.getSimpleName(), memberAccessor,
+ duplicate));
} else if (variableAnnotationClass.equals(PlanningVariable.class)) {
var type = memberAccessor.getType();
if (type.isArray()) {
throw new IllegalStateException("The entityClass (%s) has a @%s annotated member (%s) that is of an array type."
- .formatted(entityClass, PlanningVariable.class.getSimpleName(), memberAccessor));
+ .formatted(entityClass.getCanonicalName(), PlanningVariable.class.getSimpleName(), memberAccessor));
}
var variableDescriptor = new BasicVariableDescriptor<>(nextVariableDescriptorOrdinal, this, memberAccessor);
declaredGenuineVariableDescriptorMap.put(memberName, variableDescriptor);
@@ -336,7 +314,7 @@ The entityClass (%s) has a @%s annotated member (%s), duplicated by member for v
throw new IllegalStateException("""
The entityClass (%s) has a @%s annotated member (%s) that has an unsupported type (%s).
Maybe use %s."""
- .formatted(entityClass, PlanningListVariable.class.getSimpleName(), memberAccessor,
+ .formatted(entityClass.getCanonicalName(), PlanningListVariable.class.getSimpleName(), memberAccessor,
memberAccessor.getType(), List.class.getCanonicalName()));
}
} else if (variableAnnotationClass.equals(InverseRelationShadowVariable.class)) {
@@ -392,7 +370,7 @@ private void processPlanningPinAnnotation(DescriptorPolicy descriptorPolicy, Mem
if (!Boolean.TYPE.isAssignableFrom(type) && !Boolean.class.isAssignableFrom(type)) {
throw new IllegalStateException(
"The entityClass (%s) has a %s annotated member (%s) that is not a boolean or Boolean."
- .formatted(entityClass, PlanningPin.class.getSimpleName(), member));
+ .formatted(entityClass.getCanonicalName(), PlanningPin.class.getSimpleName(), member));
}
declaredPinEntityFilterList.add(new PinEntityFilter<>(memberAccessor));
}
@@ -404,7 +382,7 @@ private void processPlanningPinIndexAnnotation(DescriptorPolicy descriptorPolicy
if (!hasAnyGenuineListVariables()) {
throw new IllegalStateException(
"The entityClass (%s) has a %s annotated member (%s) but no %s annotated member."
- .formatted(entityClass, PlanningPinToIndex.class.getSimpleName(), member,
+ .formatted(entityClass.getCanonicalName(), PlanningPinToIndex.class.getSimpleName(), member,
PlanningListVariable.class.getSimpleName()));
}
var memberAccessor = descriptorPolicy.getMemberAccessorFactory().buildAndCacheMemberAccessor(member,
@@ -413,7 +391,7 @@ private void processPlanningPinIndexAnnotation(DescriptorPolicy descriptorPolicy
if (!Integer.TYPE.isAssignableFrom(type)) {
throw new IllegalStateException(
"The entityClass (%s) has a %s annotated member (%s) that is not a primitive int."
- .formatted(entityClass, PlanningPinToIndex.class.getSimpleName(), member));
+ .formatted(entityClass.getCanonicalName(), PlanningPinToIndex.class.getSimpleName(), member));
}
declaredPlanningPinIndexMemberAccessorList.add(memberAccessor);
}
@@ -469,10 +447,9 @@ private void createEffectiveVariableDescriptorMaps() {
.toList();
if (!redefinedGenuineVariables.isEmpty()) {
throw new IllegalStateException("""
- The class (%s) redefines the genuine variables (%s), which is not permitted.
- Maybe remove the variables (%s) from the class (%s).""".formatted(entityClass,
- redefinedGenuineVariables, String.join(", ", redefinedGenuineVariables),
- entityClass));
+ The entityClass (%s) redefines the genuine variables (%s), which is not permitted.
+ Maybe remove the variables (%s) from the class.""".formatted(entityClass.getCanonicalName(),
+ redefinedGenuineVariables, String.join(", ", redefinedGenuineVariables)));
}
effectiveGenuineVariableDescriptorMap.putAll(declaredGenuineVariableDescriptorMap);
var redefinedShadowVariables = declaredShadowVariableDescriptorMap.keySet().stream()
@@ -480,9 +457,9 @@ Maybe remove the variables (%s) from the class (%s).""".formatted(entityClass,
.toList();
if (!redefinedShadowVariables.isEmpty()) {
throw new IllegalStateException("""
- The class (%s) redefines the shadow variables (%s), which is not permitted.
- Maybe remove the variables (%s) from the class (%s).""".formatted(entityClass,
- redefinedShadowVariables, redefinedShadowVariables, entityClass));
+ The entityClass (%s) redefines the shadow variables (%s), which is not permitted.
+ Maybe remove the variables (%s) from the class.""".formatted(entityClass.getCanonicalName(),
+ redefinedShadowVariables, redefinedShadowVariables));
}
effectiveShadowVariableDescriptorMap.putAll(declaredShadowVariableDescriptorMap);
@@ -536,7 +513,7 @@ private void createEffectivePlanningPinIndexReader() {
}
default -> throw new IllegalStateException(
"The entityClass (%s) has (%d) @%s-annotated members (%s), where it should only have one."
- .formatted(entityClass, planningPinIndexMemberAccessorList.size(),
+ .formatted(entityClass.getCanonicalName(), planningPinIndexMemberAccessorList.size(),
PlanningPinToIndex.class.getSimpleName(), planningPinIndexMemberAccessorList));
}
}
@@ -690,18 +667,6 @@ public boolean hasVariableDescriptor(String variableName) {
return effectiveVariableDescriptorMap.get(variableName);
}
- public @NonNull VariableDescriptor getVariableDescriptorOrFail(String variableName) {
- var variableDescriptor = effectiveVariableDescriptorMap.get(variableName);
- if (variableDescriptor == null) {
- throw new IllegalArgumentException("""
- Entity class %s does not hava a "%s" genuine or shadow variable.
- Maybe you meant one of %s?"""
- .formatted(entityClass.getSimpleName(),
- variableName, effectiveVariableDescriptorMap.keySet()));
- }
- return variableDescriptor;
- }
-
public boolean hasAnyDeclaredGenuineVariableDescriptor() {
return !declaredGenuineVariableDescriptorMap.isEmpty();
}
@@ -734,7 +699,7 @@ public String buildInvalidVariableNameExceptionMessage(String variableName) {
"""
The variableName (%s) for entityClass (%s) does not exist as a getter or field on that class.
Check the spelling of the variableName (%s)."""
- .formatted(variableName, entityClass, variableName);
+ .formatted(variableName, entityClass.getCanonicalName(), variableName);
if (variableName.length() >= 2
&& !Character.isUpperCase(variableName.charAt(0))
&& Character.isUpperCase(variableName.charAt(1))) {
@@ -748,7 +713,7 @@ Check the spelling of the variableName (%s)."""
return """
The variableName (%s) for entityClass (%s) exists as a getter or field on that class, but isn't in the planning variables (%s).
%sMaybe your planning entity's getter or field lacks a @%s annotation or a shadow variable annotation."""
- .formatted(variableName, entityClass, effectiveVariableDescriptorMap.keySet(),
+ .formatted(variableName, entityClass.getCanonicalName(), effectiveVariableDescriptorMap.keySet(),
Character.isUpperCase(variableName.charAt(0))
? "Maybe the variableName (%s) should start with a lowercase.%n".formatted(variableName)
: "",
@@ -856,7 +821,7 @@ public boolean isMovable(Solution_ workingSolution, Object entity) {
@Override
public String toString() {
- return "%s(%s)".formatted(getClass().getSimpleName(), entityClass.getName());
+ return "%s(%s)".formatted(getClass().getSimpleName(), entityClass.getCanonicalName());
}
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/lookup/LookUpManager.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/lookup/LookUpManager.java
deleted file mode 100644
index 83ded2e7e5c..00000000000
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/lookup/LookUpManager.java
+++ /dev/null
@@ -1,73 +0,0 @@
-package ai.timefold.solver.core.impl.domain.lookup;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import ai.timefold.solver.core.api.domain.common.PlanningId;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
-
-import org.jspecify.annotations.NullMarked;
-import org.jspecify.annotations.Nullable;
-
-/**
- * @see PlanningId
- * @see ScoreDirector#lookUpWorkingObject(Object)
- */
-@NullMarked
-public final class LookUpManager {
-
- private final LookUpStrategyResolver lookUpStrategyResolver;
- private final Map idToWorkingObjectMap = new HashMap<>();
-
- public LookUpManager(LookUpStrategyResolver lookUpStrategyResolver) {
- this.lookUpStrategyResolver = lookUpStrategyResolver;
- }
-
- public void reset() {
- idToWorkingObjectMap.clear();
- }
-
- public void addWorkingObject(Object workingObject) {
- var lookUpStrategy = lookUpStrategyResolver.determineLookUpStrategy(workingObject);
- lookUpStrategy.addWorkingObject(idToWorkingObjectMap, workingObject);
- }
-
- public void removeWorkingObject(Object workingObject) {
- var lookUpStrategy = lookUpStrategyResolver.determineLookUpStrategy(workingObject);
- lookUpStrategy.removeWorkingObject(idToWorkingObjectMap, workingObject);
- }
-
- /**
- * As defined by {@link ScoreDirector#lookUpWorkingObject(Object)}.
- *
- * @return null if externalObject is null
- * @throws IllegalArgumentException if there is no workingObject for externalObject, if it cannot be looked up
- * or if the externalObject's class is not supported
- * @throws IllegalStateException if it cannot be looked up
- * @param the object type
- */
- public @Nullable E lookUpWorkingObject(@Nullable E externalObject) {
- if (externalObject == null) {
- return null;
- }
- var lookUpStrategy = lookUpStrategyResolver.determineLookUpStrategy(externalObject);
- return lookUpStrategy.lookUpWorkingObject(idToWorkingObjectMap, externalObject);
- }
-
- /**
- * As defined by {@link ScoreDirector#lookUpWorkingObjectOrReturnNull(Object)}.
- *
- * @return null if externalObject is null, or if there is no workingObject for externalObject
- * @throws IllegalArgumentException if it cannot be looked up or if the externalObject's class is not supported
- * @throws IllegalStateException if it cannot be looked up
- * @param the object type
- */
- public @Nullable E lookUpWorkingObjectOrReturnNull(@Nullable E externalObject) {
- if (externalObject == null) {
- return null;
- }
- var lookUpStrategy = lookUpStrategyResolver.determineLookUpStrategy(externalObject);
- return lookUpStrategy.lookUpWorkingObjectIfExists(idToWorkingObjectMap, externalObject);
- }
-
-}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/descriptor/SolutionDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/descriptor/SolutionDescriptor.java
index f850e794e04..6dbf93ce360 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/descriptor/SolutionDescriptor.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/descriptor/SolutionDescriptor.java
@@ -44,16 +44,15 @@
import ai.timefold.solver.core.api.domain.solution.cloner.SolutionCloner;
import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider;
import ai.timefold.solver.core.api.score.Score;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.config.solver.PreviewFeature;
import ai.timefold.solver.core.config.util.ConfigUtils;
import ai.timefold.solver.core.impl.domain.common.DomainAccessType;
+import ai.timefold.solver.core.impl.domain.common.LookupStrategyResolver;
import ai.timefold.solver.core.impl.domain.common.ReflectionHelper;
import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessor;
import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorFactory;
import ai.timefold.solver.core.impl.domain.common.accessor.ReflectionFieldMemberAccessor;
import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor;
-import ai.timefold.solver.core.impl.domain.lookup.LookUpStrategyResolver;
import ai.timefold.solver.core.impl.domain.policy.DescriptorPolicy;
import ai.timefold.solver.core.impl.domain.score.descriptor.ScoreDescriptor;
import ai.timefold.solver.core.impl.domain.solution.ConstraintWeightSupplier;
@@ -68,6 +67,7 @@
import ai.timefold.solver.core.impl.domain.variable.descriptor.ShadowVariableDescriptor;
import ai.timefold.solver.core.impl.domain.variable.descriptor.VariableDescriptor;
import ai.timefold.solver.core.impl.score.definition.ScoreDefinition;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.util.MutableInt;
import ai.timefold.solver.core.impl.util.MutableLong;
import ai.timefold.solver.core.impl.util.MutablePair;
@@ -257,7 +257,7 @@ private static boolean hasAnyAnnotatedMembers(Class> solutionClass) {
private final MemberAccessorFactory memberAccessorFactory;
private DomainAccessType domainAccessType;
- private LookUpStrategyResolver lookUpStrategyResolver;
+ private LookupStrategyResolver lookUpStrategyResolver;
private final Map problemFactMemberAccessorMap = new LinkedHashMap<>();
private final Map problemFactCollectionMemberAccessorMap = new LinkedHashMap<>();
@@ -383,7 +383,7 @@ private void processSolutionAnnotations(DescriptorPolicy descriptorPolicy) {
if (solutionClonerClass != PlanningSolution.NullSolutionCloner.class) {
solutionCloner = ConfigUtils.newInstance(this::toString, "solutionClonerClass", solutionClonerClass);
}
- lookUpStrategyResolver = new LookUpStrategyResolver(descriptorPolicy);
+ lookUpStrategyResolver = new LookupStrategyResolver(descriptorPolicy);
}
private @NonNull PlanningSolution extractMostRelevantPlanningSolutionAnnotation() {
@@ -876,7 +876,7 @@ public VariableDescriptor findVariableDescriptorOrFail(Object entity,
// Look up methods
// ************************************************************************
- public LookUpStrategyResolver getLookUpStrategyResolver() {
+ public LookupStrategyResolver getLookUpStrategyResolver() {
return lookUpStrategyResolver;
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/cascade/CascadingUpdateShadowVariableDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/cascade/CascadingUpdateShadowVariableDescriptor.java
index 1c2f9be3924..c65e0a9b136 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/cascade/CascadingUpdateShadowVariableDescriptor.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/cascade/CascadingUpdateShadowVariableDescriptor.java
@@ -11,7 +11,6 @@
import java.util.Objects;
import ai.timefold.solver.core.api.domain.variable.CascadingUpdateShadowVariable;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.config.util.ConfigUtils;
import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessor;
import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessorType;
@@ -22,6 +21,7 @@
import ai.timefold.solver.core.impl.domain.variable.listener.VariableListenerWithSources;
import ai.timefold.solver.core.impl.domain.variable.supply.Demand;
import ai.timefold.solver.core.impl.domain.variable.supply.SupplyManager;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
public final class CascadingUpdateShadowVariableDescriptor extends ShadowVariableDescriptor {
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java
index 3f6bbbb460f..e393cc73c34 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java
@@ -60,7 +60,7 @@ protected void processValueRangeRefs(DescriptorPolicy descriptorPolicy, String[]
throw new IllegalArgumentException("""
The entityClass (%s) has a @%s annotated property (%s) that has no valueRangeProviderRefs (%s) \
and no matching anonymous value range providers were found."""
- .formatted(entityDescriptor.getEntityClass().getSimpleName(),
+ .formatted(entityDescriptor.getEntityClass().getCanonicalName(),
PlanningVariable.class.getSimpleName(),
variableMemberAccessor.getName(),
Arrays.toString(valueRangeProviderRefs)));
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/listener/support/VariableListenerSupport.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/listener/support/VariableListenerSupport.java
index 824ae3cd3a5..b814ea054a5 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/listener/support/VariableListenerSupport.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/listener/support/VariableListenerSupport.java
@@ -18,7 +18,6 @@
import java.util.function.IntFunction;
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.enterprise.TimefoldSolverEnterpriseService;
import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor;
import ai.timefold.solver.core.impl.domain.variable.ListVariableStateSupply;
@@ -41,6 +40,7 @@
import ai.timefold.solver.core.impl.domain.variable.supply.Supply;
import ai.timefold.solver.core.impl.domain.variable.supply.SupplyManager;
import ai.timefold.solver.core.impl.score.director.InnerScoreDirector;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.util.LinkedIdentityHashSet;
import org.jspecify.annotations.NonNull;
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/node/ExhaustiveSearchNode.java b/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/node/ExhaustiveSearchNode.java
index 6126ccefcce..7d83591c272 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/node/ExhaustiveSearchNode.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/node/ExhaustiveSearchNode.java
@@ -1,9 +1,9 @@
package ai.timefold.solver.core.impl.exhaustivesearch.node;
import ai.timefold.solver.core.api.score.Score;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.exhaustivesearch.node.bounder.ScoreBounder;
import ai.timefold.solver.core.impl.score.director.InnerScore;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
import ai.timefold.solver.core.preview.api.move.Move;
public class ExhaustiveSearchNode {
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/node/bounder/ScoreBounder.java b/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/node/bounder/ScoreBounder.java
index b555de7ab6a..1747fdd5bf7 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/node/bounder/ScoreBounder.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/node/bounder/ScoreBounder.java
@@ -2,9 +2,9 @@
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
import ai.timefold.solver.core.api.score.Score;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.score.definition.ScoreDefinition;
import ai.timefold.solver.core.impl.score.director.InnerScore;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.score.trend.InitializingScoreTrend;
public interface ScoreBounder> {
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/node/bounder/TrendBasedScoreBounder.java b/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/node/bounder/TrendBasedScoreBounder.java
index a48933a20f2..285056bdf3c 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/node/bounder/TrendBasedScoreBounder.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/node/bounder/TrendBasedScoreBounder.java
@@ -1,9 +1,9 @@
package ai.timefold.solver.core.impl.exhaustivesearch.node.bounder;
import ai.timefold.solver.core.api.score.Score;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.score.definition.ScoreDefinition;
import ai.timefold.solver.core.impl.score.director.InnerScore;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.score.trend.InitializingScoreTrend;
public final class TrendBasedScoreBounder> implements ScoreBounder {
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/node/comparator/BreadthFirstNodeComparator.java b/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/node/comparator/BreadthFirstNodeComparator.java
index 3075a67dc3f..e257b9e77cc 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/node/comparator/BreadthFirstNodeComparator.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/node/comparator/BreadthFirstNodeComparator.java
@@ -3,10 +3,10 @@
import java.util.Comparator;
import ai.timefold.solver.core.api.score.Score;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.exhaustivesearch.node.ExhaustiveSearchNode;
import ai.timefold.solver.core.impl.exhaustivesearch.node.bounder.ScoreBounder;
import ai.timefold.solver.core.impl.score.director.InnerScore;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
/**
* Investigate nodes layer by layer: investigate shallower nodes first.
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/AbstractSelectorBasedMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/AbstractSelectorBasedMove.java
index d4a960e0b69..6e28c485364 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/AbstractSelectorBasedMove.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/AbstractSelectorBasedMove.java
@@ -6,17 +6,17 @@
import java.util.SequencedSet;
import java.util.Set;
+import ai.timefold.solver.core.api.domain.common.Lookup;
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
import ai.timefold.solver.core.api.domain.valuerange.ValueRange;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.domain.valuerange.descriptor.AbstractValueRangeDescriptor;
import ai.timefold.solver.core.impl.heuristic.selector.move.generic.SelectorBasedChangeMove;
import ai.timefold.solver.core.impl.heuristic.selector.move.generic.SelectorBasedSwapMove;
import ai.timefold.solver.core.impl.move.MoveDirector;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector;
import ai.timefold.solver.core.preview.api.move.Move;
import ai.timefold.solver.core.preview.api.move.MutableSolutionView;
-import ai.timefold.solver.core.preview.api.move.Rebaser;
import ai.timefold.solver.core.preview.api.neighborhood.Neighborhood;
import org.jspecify.annotations.NullMarked;
@@ -82,18 +82,18 @@ protected ValueRange extractValueRangeFromEntity(ScoreDirector<
// Util methods
// ************************************************************************
- public static List rebaseList(List externalObjectList, Rebaser rebaser) {
+ public static List rebaseList(List externalObjectList, Lookup lookup) {
var rebasedObjectList = new ArrayList(externalObjectList.size());
for (var entity : externalObjectList) {
- rebasedObjectList.add(rebaser.rebase(entity));
+ rebasedObjectList.add(lookup.lookUpWorkingObject(entity));
}
return rebasedObjectList;
}
- public static SequencedSet rebaseSet(Set externalObjectSet, Rebaser rebaser) {
+ public static SequencedSet rebaseSet(Set externalObjectSet, Lookup lookup) {
var rebasedObjectSet = LinkedHashSet. newLinkedHashSet(externalObjectSet.size());
for (var entity : externalObjectSet) {
- rebasedObjectSet.add(rebaser.rebase(entity));
+ rebasedObjectSet.add(lookup.lookUpWorkingObject(entity));
}
return rebasedObjectSet;
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/MoveAdapters.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/MoveAdapters.java
index e3e8f86c347..e5d7374e94b 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/MoveAdapters.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/MoveAdapters.java
@@ -2,8 +2,8 @@
import java.util.function.Predicate;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.move.MoveDirector;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
import ai.timefold.solver.core.preview.api.move.Move;
import org.jspecify.annotations.NullMarked;
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedCompositeMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedCompositeMove.java
index b16acf45ce6..509f93eb9b2 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedCompositeMove.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedCompositeMove.java
@@ -6,12 +6,12 @@
import java.util.SequencedCollection;
import java.util.stream.Collectors;
+import ai.timefold.solver.core.api.domain.common.Lookup;
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.score.director.InnerScoreDirector;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector;
import ai.timefold.solver.core.preview.api.move.Move;
-import ai.timefold.solver.core.preview.api.move.Rebaser;
import org.jspecify.annotations.NullMarked;
@@ -93,10 +93,10 @@ protected void execute(VariableDescriptorAwareScoreDirector scoreDire
@SuppressWarnings("unchecked")
@Override
- public SelectorBasedCompositeMove rebase(Rebaser rebaser) {
+ public SelectorBasedCompositeMove rebase(Lookup lookup) {
var rebasedMoves = new Move[moves.length];
for (var i = 0; i < moves.length; i++) {
- rebasedMoves[i] = moves[i].rebase(rebaser);
+ rebasedMoves[i] = moves[i].rebase(lookup);
}
return new SelectorBasedCompositeMove(rebasedMoves);
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedNoChangeMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedNoChangeMove.java
index 332c116f192..ce9bf81b177 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedNoChangeMove.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedNoChangeMove.java
@@ -3,11 +3,11 @@
import java.util.Collections;
import java.util.SequencedCollection;
+import ai.timefold.solver.core.api.domain.common.Lookup;
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector;
import ai.timefold.solver.core.preview.api.move.Move;
-import ai.timefold.solver.core.preview.api.move.Rebaser;
import org.jspecify.annotations.NullMarked;
@@ -41,7 +41,7 @@ protected void execute(VariableDescriptorAwareScoreDirector scoreDire
}
@Override
- public Move rebase(Rebaser rebaser) {
+ public Move rebase(Lookup lookup) {
return getInstance();
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/CompositeSelectionFilter.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/CompositeSelectionFilter.java
index fa5bc856252..912dafa5519 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/CompositeSelectionFilter.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/CompositeSelectionFilter.java
@@ -2,7 +2,7 @@
import java.util.Arrays;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
/**
* Combines several {@link SelectionFilter}s into one.
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FairSelectorProbabilityWeightFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FairSelectorProbabilityWeightFactory.java
index 0cc3c5d2f45..f544d5b8032 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FairSelectorProbabilityWeightFactory.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/FairSelectorProbabilityWeightFactory.java
@@ -1,7 +1,7 @@
package ai.timefold.solver.core.impl.heuristic.selector.common.decorator;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.heuristic.selector.IterableSelector;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
public class FairSelectorProbabilityWeightFactory
implements SelectionProbabilityWeightFactory {
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionFilter.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionFilter.java
index 603f269f737..46207579c07 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionFilter.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionFilter.java
@@ -6,8 +6,8 @@
import ai.timefold.solver.core.api.domain.entity.PlanningEntity;
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.heuristic.selector.Selector;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
import ai.timefold.solver.core.preview.api.move.Move;
/**
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionProbabilityWeightFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionProbabilityWeightFactory.java
index 6cb1c6ade3f..381e7f5cd4c 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionProbabilityWeightFactory.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionProbabilityWeightFactory.java
@@ -2,8 +2,8 @@
import ai.timefold.solver.core.api.domain.entity.PlanningEntity;
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.heuristic.selector.Selector;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
import ai.timefold.solver.core.preview.api.move.Move;
/**
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java
deleted file mode 100644
index 47a74e6f310..00000000000
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package ai.timefold.solver.core.impl.heuristic.selector.common.decorator;
-
-import java.util.Comparator;
-
-import ai.timefold.solver.core.api.domain.common.ComparatorFactory;
-import ai.timefold.solver.core.api.domain.entity.PlanningEntity;
-import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
-import ai.timefold.solver.core.impl.heuristic.selector.Selector;
-import ai.timefold.solver.core.preview.api.move.Move;
-
-/**
- * Creates a weight to decide the order of a collections of selections
- * (a selection is a {@link PlanningEntity}, a planningValue, a {@link Move} or a {@link Selector}).
- * The selections are then sorted by their weight,
- * normally ascending unless it's configured descending.
- *
- *
- * Implementations are expected to be stateless.
- * The solver may choose to reuse instances.
- *
- * @deprecated Deprecated in favor of {@link ComparatorFactory}.
- *
- * @param the solution type, the class with the {@link PlanningSolution} annotation
- * @param the selection type
- */
-@Deprecated(forRemoval = true, since = "1.28.0")
-@FunctionalInterface
-public interface SelectionSorterWeightFactory extends ComparatorFactory {
-
- /**
- * @param solution never null, the {@link PlanningSolution} to which the selection belongs or applies to
- * @param selection never null, a {@link PlanningEntity}, a planningValue, a {@link Move} or a {@link Selector}
- * @return never null, for example a {@link Integer}, {@link Double} or a more complex {@link Comparable}
- */
- Comparable createSorterWeight(Solution_ solution, T selection);
-
- /**
- * Default implementation for enabling interconnection between the two comparator contracts.
- */
- @Override
- default Comparator createComparator(Solution_ solution) {
- return (v1, v2) -> createSorterWeight(solution, v1).compareTo(createSorterWeight(solution, v2));
- }
-}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/decorator/FilteringEntitySelector.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/decorator/FilteringEntitySelector.java
index 1dec1ed217f..e499ba50f83 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/decorator/FilteringEntitySelector.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/decorator/FilteringEntitySelector.java
@@ -4,7 +4,6 @@
import java.util.ListIterator;
import java.util.Objects;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor;
import ai.timefold.solver.core.impl.heuristic.selector.AbstractDemandEnabledSelector;
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter;
@@ -12,6 +11,7 @@
import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.UpcomingSelectionListIterator;
import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelector;
import ai.timefold.solver.core.impl.phase.scope.AbstractPhaseScope;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
public final class FilteringEntitySelector
extends AbstractDemandEnabledSelector
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/decorator/ProbabilityEntitySelector.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/decorator/ProbabilityEntitySelector.java
index 1ebe2a20c1a..b2bcb8d78e5 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/decorator/ProbabilityEntitySelector.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/decorator/ProbabilityEntitySelector.java
@@ -6,7 +6,6 @@
import java.util.Objects;
import java.util.TreeMap;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType;
import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor;
import ai.timefold.solver.core.impl.heuristic.selector.AbstractDemandEnabledSelector;
@@ -14,6 +13,7 @@
import ai.timefold.solver.core.impl.heuristic.selector.common.SelectionCacheLifecycleListener;
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory;
import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelector;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.solver.random.RandomUtils;
import ai.timefold.solver.core.impl.solver.scope.SolverScope;
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/SubList.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/SubList.java
index 0bcda6e705b..bd3dee83e5b 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/SubList.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/SubList.java
@@ -1,15 +1,18 @@
package ai.timefold.solver.core.impl.heuristic.selector.list;
-import ai.timefold.solver.core.preview.api.move.Rebaser;
+import ai.timefold.solver.core.api.domain.common.Lookup;
+import org.jspecify.annotations.NullMarked;
+
+@NullMarked
public record SubList(Object entity, int fromIndex, int length) {
public int getToIndex() {
return fromIndex + length;
}
- public SubList rebase(Rebaser rebaser) {
- return new SubList(rebaser.rebase(entity), fromIndex, length);
+ public SubList rebase(Lookup lookup) {
+ return new SubList(lookup.lookUpWorkingObject(entity), fromIndex, length);
}
@Override
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/DoableMoveSelectionFilter.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/DoableMoveSelectionFilter.java
index bdac8d5c7ad..5f1ca928bff 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/DoableMoveSelectionFilter.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/DoableMoveSelectionFilter.java
@@ -1,8 +1,8 @@
package ai.timefold.solver.core.impl.heuristic.selector.move;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove;
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
import ai.timefold.solver.core.preview.api.move.Move;
final class DoableMoveSelectionFilter implements SelectionFilter> {
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/FixedSelectorProbabilityWeightFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/FixedSelectorProbabilityWeightFactory.java
index dc906e91b98..79eca50a5ec 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/FixedSelectorProbabilityWeightFactory.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/FixedSelectorProbabilityWeightFactory.java
@@ -2,9 +2,9 @@
import java.util.Map;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.heuristic.selector.Selector;
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
final class FixedSelectorProbabilityWeightFactory
implements SelectionProbabilityWeightFactory {
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/UnionMoveSelector.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/UnionMoveSelector.java
index 86139b80950..aa208854e93 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/UnionMoveSelector.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/UnionMoveSelector.java
@@ -5,10 +5,10 @@
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory;
import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector;
import ai.timefold.solver.core.impl.phase.scope.AbstractStepScope;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
import ai.timefold.solver.core.preview.api.move.Move;
/**
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/FilteringMoveSelector.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/FilteringMoveSelector.java
index bd9eb98c9d8..3f142e1352d 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/FilteringMoveSelector.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/FilteringMoveSelector.java
@@ -2,12 +2,12 @@
import java.util.Iterator;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter;
import ai.timefold.solver.core.impl.heuristic.selector.common.iterator.UpcomingSelectionIterator;
import ai.timefold.solver.core.impl.heuristic.selector.move.AbstractMoveSelector;
import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector;
import ai.timefold.solver.core.impl.phase.scope.AbstractPhaseScope;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.solver.termination.PhaseTermination;
import ai.timefold.solver.core.preview.api.move.Move;
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/ProbabilityMoveSelector.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/ProbabilityMoveSelector.java
index 337ad9f041f..e181debfc2d 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/ProbabilityMoveSelector.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/ProbabilityMoveSelector.java
@@ -4,13 +4,13 @@
import java.util.NavigableMap;
import java.util.TreeMap;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType;
import ai.timefold.solver.core.impl.heuristic.selector.common.SelectionCacheLifecycleBridge;
import ai.timefold.solver.core.impl.heuristic.selector.common.SelectionCacheLifecycleListener;
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory;
import ai.timefold.solver.core.impl.heuristic.selector.move.AbstractMoveSelector;
import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.solver.random.RandomUtils;
import ai.timefold.solver.core.impl.solver.scope.SolverScope;
import ai.timefold.solver.core.preview.api.move.Move;
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/factory/MoveIteratorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/factory/MoveIteratorFactory.java
index bbe6ef0cf98..558a3ee8902 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/factory/MoveIteratorFactory.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/factory/MoveIteratorFactory.java
@@ -4,9 +4,9 @@
import java.util.random.RandomGenerator;
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType;
import ai.timefold.solver.core.config.solver.EnvironmentMode;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
import ai.timefold.solver.core.preview.api.move.Move;
/**
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/factory/MoveIteratorFactoryToMoveSelectorBridge.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/factory/MoveIteratorFactoryToMoveSelectorBridge.java
index 263ddc66715..b8aad4e16ed 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/factory/MoveIteratorFactoryToMoveSelectorBridge.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/factory/MoveIteratorFactoryToMoveSelectorBridge.java
@@ -2,10 +2,10 @@
import java.util.Iterator;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.heuristic.selector.move.AbstractMoveSelector;
import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector;
import ai.timefold.solver.core.impl.phase.scope.AbstractPhaseScope;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
import ai.timefold.solver.core.preview.api.move.Move;
/**
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedChangeMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedChangeMove.java
index 5cba9b9ff25..cdd88ee4b50 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedChangeMove.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedChangeMove.java
@@ -4,12 +4,12 @@
import java.util.Objects;
import java.util.SequencedCollection;
+import ai.timefold.solver.core.api.domain.common.Lookup;
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.domain.variable.descriptor.GenuineVariableDescriptor;
import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector;
-import ai.timefold.solver.core.preview.api.move.Rebaser;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@@ -60,9 +60,9 @@ protected void execute(VariableDescriptorAwareScoreDirector scoreDire
}
@Override
- public SelectorBasedChangeMove rebase(Rebaser rebaser) {
- return new SelectorBasedChangeMove<>(variableDescriptor, Objects.requireNonNull(rebaser.rebase(entity)),
- rebaser.rebase(toPlanningValue));
+ public SelectorBasedChangeMove rebase(Lookup lookup) {
+ return new SelectorBasedChangeMove<>(variableDescriptor, lookup.lookUpWorkingObject(entity),
+ lookup.lookUpWorkingObject(toPlanningValue));
}
@Override
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedPillarChangeMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedPillarChangeMove.java
index 040a16aa3ef..e3f9e921e3c 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedPillarChangeMove.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedPillarChangeMove.java
@@ -5,13 +5,13 @@
import java.util.Objects;
import java.util.SequencedCollection;
+import ai.timefold.solver.core.api.domain.common.Lookup;
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.domain.variable.descriptor.GenuineVariableDescriptor;
import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector;
import ai.timefold.solver.core.preview.api.move.Move;
-import ai.timefold.solver.core.preview.api.move.Rebaser;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@@ -78,9 +78,9 @@ protected void execute(VariableDescriptorAwareScoreDirector scoreDire
}
@Override
- public SelectorBasedPillarChangeMove rebase(Rebaser rebaser) {
- return new SelectorBasedPillarChangeMove<>(rebaseList(pillar, rebaser), variableDescriptor,
- rebaser.rebase(toPlanningValue));
+ public SelectorBasedPillarChangeMove rebase(Lookup lookup) {
+ return new SelectorBasedPillarChangeMove<>(rebaseList(pillar, lookup), variableDescriptor,
+ lookup.lookUpWorkingObject(toPlanningValue));
}
@Override
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedPillarSwapMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedPillarSwapMove.java
index 3c82bad5843..a76d48a5bea 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedPillarSwapMove.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedPillarSwapMove.java
@@ -5,14 +5,14 @@
import java.util.Objects;
import java.util.SequencedCollection;
+import ai.timefold.solver.core.api.domain.common.Lookup;
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.domain.variable.descriptor.GenuineVariableDescriptor;
import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector;
import ai.timefold.solver.core.impl.util.CollectionUtils;
import ai.timefold.solver.core.preview.api.move.Move;
-import ai.timefold.solver.core.preview.api.move.Rebaser;
import org.jspecify.annotations.NullMarked;
@@ -103,9 +103,9 @@ protected void execute(VariableDescriptorAwareScoreDirector scoreDire
}
@Override
- public SelectorBasedPillarSwapMove rebase(Rebaser rebaser) {
- return new SelectorBasedPillarSwapMove<>(variableDescriptorList, rebaseList(leftPillar, rebaser),
- rebaseList(rightPillar, rebaser));
+ public SelectorBasedPillarSwapMove rebase(Lookup lookup) {
+ return new SelectorBasedPillarSwapMove<>(variableDescriptorList, rebaseList(leftPillar, lookup),
+ rebaseList(rightPillar, lookup));
}
@Override
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedRuinRecreateMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedRuinRecreateMove.java
index 58ff2bb3e46..5e51df403e0 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedRuinRecreateMove.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedRuinRecreateMove.java
@@ -6,6 +6,7 @@
import java.util.SequencedCollection;
import java.util.SequencedSet;
+import ai.timefold.solver.core.api.domain.common.Lookup;
import ai.timefold.solver.core.impl.domain.variable.descriptor.GenuineVariableDescriptor;
import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove;
import ai.timefold.solver.core.impl.move.VariableChangeRecordingScoreDirector;
@@ -13,7 +14,6 @@
import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector;
import ai.timefold.solver.core.impl.solver.scope.SolverScope;
import ai.timefold.solver.core.preview.api.move.Move;
-import ai.timefold.solver.core.preview.api.move.Rebaser;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@@ -86,9 +86,9 @@ public SequencedCollection getPlanningValues() {
}
@Override
- public Move rebase(Rebaser rebaser) {
- var rebasedRuinedEntityList = rebaseList(ruinedEntityList, rebaser);
- var rebasedAffectedValueSet = rebaseSet(affectedValueSet, rebaser);
+ public Move rebase(Lookup lookup) {
+ var rebasedRuinedEntityList = rebaseList(ruinedEntityList, lookup);
+ var rebasedAffectedValueSet = rebaseSet(affectedValueSet, lookup);
return new SelectorBasedRuinRecreateMove<>(genuineVariableDescriptor, constructionHeuristicPhaseBuilder, solverScope,
rebasedRuinedEntityList, rebasedAffectedValueSet);
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedSwapMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedSwapMove.java
index 985b9aff6b0..6e9605271e4 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedSwapMove.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedSwapMove.java
@@ -5,12 +5,12 @@
import java.util.Objects;
import java.util.SequencedCollection;
+import ai.timefold.solver.core.api.domain.common.Lookup;
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.domain.variable.descriptor.GenuineVariableDescriptor;
import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector;
-import ai.timefold.solver.core.preview.api.move.Rebaser;
import org.jspecify.annotations.NullMarked;
@@ -72,9 +72,9 @@ protected void execute(VariableDescriptorAwareScoreDirector scoreDire
}
@Override
- public SelectorBasedSwapMove rebase(Rebaser rebaser) {
- return new SelectorBasedSwapMove<>(variableDescriptorList, Objects.requireNonNull(rebaser.rebase(leftEntity)),
- Objects.requireNonNull(rebaser.rebase(rightEntity)));
+ public SelectorBasedSwapMove rebase(Lookup lookup) {
+ return new SelectorBasedSwapMove<>(variableDescriptorList, lookup.lookUpWorkingObject(leftEntity),
+ lookup.lookUpWorkingObject(rightEntity));
}
@Override
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListAssignMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListAssignMove.java
index 58e80f995ae..bb8f7cdea70 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListAssignMove.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListAssignMove.java
@@ -4,11 +4,11 @@
import java.util.Objects;
import java.util.SequencedCollection;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
+import ai.timefold.solver.core.api.domain.common.Lookup;
import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor;
import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector;
-import ai.timefold.solver.core.preview.api.move.Rebaser;
import org.jspecify.annotations.NullMarked;
@@ -67,9 +67,9 @@ protected void execute(VariableDescriptorAwareScoreDirector scoreDire
}
@Override
- public SelectorBasedListAssignMove rebase(Rebaser rebaser) {
- return new SelectorBasedListAssignMove<>(variableDescriptor, Objects.requireNonNull(rebaser.rebase(planningValue)),
- Objects.requireNonNull(rebaser.rebase(destinationEntity)), destinationIndex);
+ public SelectorBasedListAssignMove rebase(Lookup lookup) {
+ return new SelectorBasedListAssignMove<>(variableDescriptor, lookup.lookUpWorkingObject(planningValue),
+ lookup.lookUpWorkingObject(destinationEntity), destinationIndex);
}
@Override
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListChangeMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListChangeMove.java
index 0192cf87b78..59a95361506 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListChangeMove.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListChangeMove.java
@@ -5,13 +5,13 @@
import java.util.Objects;
import java.util.SequencedCollection;
+import ai.timefold.solver.core.api.domain.common.Lookup;
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
import ai.timefold.solver.core.api.domain.variable.PlanningListVariable;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor;
import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector;
-import ai.timefold.solver.core.preview.api.move.Rebaser;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@@ -159,9 +159,9 @@ protected void execute(VariableDescriptorAwareScoreDirector scoreDire
}
@Override
- public SelectorBasedListChangeMove rebase(Rebaser rebaser) {
- return new SelectorBasedListChangeMove<>(variableDescriptor, Objects.requireNonNull(rebaser.rebase(sourceEntity)),
- sourceIndex, Objects.requireNonNull(rebaser.rebase(destinationEntity)), destinationIndex);
+ public SelectorBasedListChangeMove rebase(Lookup lookup) {
+ return new SelectorBasedListChangeMove<>(variableDescriptor, lookup.lookUpWorkingObject(sourceEntity), sourceIndex,
+ lookup.lookUpWorkingObject(destinationEntity), destinationIndex);
}
@Override
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListSwapMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListSwapMove.java
index 47a9ad0b456..a29782e9e75 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListSwapMove.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListSwapMove.java
@@ -5,13 +5,13 @@
import java.util.Objects;
import java.util.SequencedCollection;
+import ai.timefold.solver.core.api.domain.common.Lookup;
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
import ai.timefold.solver.core.api.domain.variable.PlanningListVariable;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor;
import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector;
-import ai.timefold.solver.core.preview.api.move.Rebaser;
import org.jspecify.annotations.NullMarked;
@@ -141,9 +141,9 @@ protected void execute(VariableDescriptorAwareScoreDirector scoreDire
}
@Override
- public SelectorBasedListSwapMove rebase(Rebaser rebaser) {
- return new SelectorBasedListSwapMove<>(variableDescriptor, Objects.requireNonNull(rebaser.rebase(leftEntity)),
- leftIndex, Objects.requireNonNull(rebaser.rebase(rightEntity)), rightIndex);
+ public SelectorBasedListSwapMove rebase(Lookup lookup) {
+ return new SelectorBasedListSwapMove<>(variableDescriptor, lookup.lookUpWorkingObject(leftEntity), leftIndex,
+ lookup.lookUpWorkingObject(rightEntity), rightIndex);
}
@Override
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListUnassignMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListUnassignMove.java
index 1cbe82e20b2..b2a6d0df76b 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListUnassignMove.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListUnassignMove.java
@@ -4,11 +4,11 @@
import java.util.Objects;
import java.util.SequencedCollection;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
+import ai.timefold.solver.core.api.domain.common.Lookup;
import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor;
import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector;
-import ai.timefold.solver.core.preview.api.move.Rebaser;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@@ -72,9 +72,8 @@ protected void execute(VariableDescriptorAwareScoreDirector scoreDire
}
@Override
- public SelectorBasedListUnassignMove rebase(Rebaser rebaser) {
- return new SelectorBasedListUnassignMove<>(variableDescriptor, Objects.requireNonNull(rebaser.rebase(sourceEntity)),
- sourceIndex);
+ public SelectorBasedListUnassignMove rebase(Lookup lookup) {
+ return new SelectorBasedListUnassignMove<>(variableDescriptor, lookup.lookUpWorkingObject(sourceEntity), sourceIndex);
}
@Override
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedSubListChangeMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedSubListChangeMove.java
index 2cbb60b7521..6b38136a021 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedSubListChangeMove.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedSubListChangeMove.java
@@ -4,14 +4,14 @@
import java.util.Objects;
import java.util.SequencedCollection;
+import ai.timefold.solver.core.api.domain.common.Lookup;
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor;
import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove;
import ai.timefold.solver.core.impl.heuristic.selector.list.SubList;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector;
import ai.timefold.solver.core.impl.util.CollectionUtils;
-import ai.timefold.solver.core.preview.api.move.Rebaser;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@@ -129,10 +129,9 @@ protected void execute(VariableDescriptorAwareScoreDirector scoreDire
}
@Override
- public SelectorBasedSubListChangeMove rebase(Rebaser rebaser) {
- return new SelectorBasedSubListChangeMove<>(variableDescriptor,
- Objects.requireNonNull(rebaser.rebase(sourceEntity)), sourceIndex, length,
- Objects.requireNonNull(rebaser.rebase(destinationEntity)), destinationIndex, reversing);
+ public SelectorBasedSubListChangeMove rebase(Lookup lookup) {
+ return new SelectorBasedSubListChangeMove<>(variableDescriptor, lookup.lookUpWorkingObject(sourceEntity), sourceIndex,
+ length, lookup.lookUpWorkingObject(destinationEntity), destinationIndex, reversing);
}
@Override
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedSubListSwapMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedSubListSwapMove.java
index 5ab60c04d69..15c6055172c 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedSubListSwapMove.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedSubListSwapMove.java
@@ -5,14 +5,14 @@
import java.util.Objects;
import java.util.SequencedCollection;
+import ai.timefold.solver.core.api.domain.common.Lookup;
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor;
import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove;
import ai.timefold.solver.core.impl.heuristic.selector.list.SubList;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector;
import ai.timefold.solver.core.impl.util.CollectionUtils;
-import ai.timefold.solver.core.preview.api.move.Rebaser;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@@ -130,8 +130,8 @@ protected void execute(VariableDescriptorAwareScoreDirector scoreDire
}
@Override
- public SelectorBasedSubListSwapMove rebase(Rebaser rebaser) {
- return new SelectorBasedSubListSwapMove<>(variableDescriptor, leftSubList.rebase(rebaser), rightSubList.rebase(rebaser),
+ public SelectorBasedSubListSwapMove rebase(Lookup lookup) {
+ return new SelectorBasedSubListSwapMove<>(variableDescriptor, leftSubList.rebase(lookup), rightSubList.rebase(lookup),
reversing);
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedSubListUnassignMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedSubListUnassignMove.java
index de072505cd2..74c6cd36b88 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedSubListUnassignMove.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedSubListUnassignMove.java
@@ -4,14 +4,14 @@
import java.util.Objects;
import java.util.SequencedCollection;
+import ai.timefold.solver.core.api.domain.common.Lookup;
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor;
import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove;
import ai.timefold.solver.core.impl.heuristic.selector.list.SubList;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector;
import ai.timefold.solver.core.preview.api.move.Move;
-import ai.timefold.solver.core.preview.api.move.Rebaser;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@@ -82,9 +82,9 @@ protected void execute(VariableDescriptorAwareScoreDirector scoreDire
}
@Override
- public Move rebase(Rebaser rebaser) {
- return new SelectorBasedSubListUnassignMove<>(variableDescriptor, Objects.requireNonNull(rebaser.rebase(sourceEntity)),
- sourceIndex, length);
+ public Move rebase(Lookup lookup) {
+ return new SelectorBasedSubListUnassignMove<>(variableDescriptor, lookup.lookUpWorkingObject(sourceEntity), sourceIndex,
+ length);
}
@Override
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/SelectorBasedKOptListMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/SelectorBasedKOptListMove.java
index 6a2c928ec17..13a836d4ce1 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/SelectorBasedKOptListMove.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/SelectorBasedKOptListMove.java
@@ -5,13 +5,13 @@
import java.util.List;
import java.util.SequencedCollection;
+import ai.timefold.solver.core.api.domain.common.Lookup;
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor;
import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.score.director.ValueRangeManager;
import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector;
-import ai.timefold.solver.core.preview.api.move.Rebaser;
import org.jspecify.annotations.NullMarked;
@@ -119,12 +119,12 @@ public boolean isMoveDoable(ScoreDirector scoreDirector) {
}
@Override
- public SelectorBasedKOptListMove rebase(Rebaser rebaser) {
+ public SelectorBasedKOptListMove rebase(Lookup lookup) {
var rebasedEquivalent2Opts = new ArrayList(equivalent2Opts.size());
var newEntities = new Object[originalEntities.length];
for (var i = 0; i < newEntities.length; i++) {
- newEntities[i] = rebaser.rebase(originalEntities[i]);
+ newEntities[i] = lookup.lookUpWorkingObject(originalEntities[i]);
}
for (var twoOpt : equivalent2Opts) {
rebasedEquivalent2Opts.add(twoOpt.rebase());
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/SelectorBasedTwoOptListMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/SelectorBasedTwoOptListMove.java
index 03ac5b01dbd..11a48169763 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/SelectorBasedTwoOptListMove.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/kopt/SelectorBasedTwoOptListMove.java
@@ -3,16 +3,15 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.Objects;
import java.util.SequencedCollection;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
+import ai.timefold.solver.core.api.domain.common.Lookup;
import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor;
import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.score.director.ValueRangeManager;
import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector;
import ai.timefold.solver.core.impl.util.CollectionUtils;
-import ai.timefold.solver.core.preview.api.move.Rebaser;
import org.jspecify.annotations.NullMarked;
@@ -225,10 +224,10 @@ public boolean isMoveDoable(ScoreDirector scoreDirector) {
}
@Override
- public SelectorBasedTwoOptListMove rebase(Rebaser rebaser) {
- return new SelectorBasedTwoOptListMove<>(variableDescriptor, Objects.requireNonNull(rebaser.rebase(firstEntity)),
- Objects.requireNonNull(rebaser.rebase(secondEntity)), firstEdgeEndpoint, secondEdgeEndpoint,
- entityFirstUnpinnedIndex, shift);
+ public SelectorBasedTwoOptListMove rebase(Lookup lookup) {
+ return new SelectorBasedTwoOptListMove<>(variableDescriptor, lookup.lookUpWorkingObject(firstEntity),
+ lookup.lookUpWorkingObject(secondEntity), firstEdgeEndpoint, secondEdgeEndpoint, entityFirstUnpinnedIndex,
+ shift);
}
@Override
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ruin/SelectorBasedListRuinRecreateMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ruin/SelectorBasedListRuinRecreateMove.java
index b3e1c49da52..4c2974d5251 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ruin/SelectorBasedListRuinRecreateMove.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ruin/SelectorBasedListRuinRecreateMove.java
@@ -10,6 +10,7 @@
import java.util.SequencedSet;
import java.util.TreeSet;
+import ai.timefold.solver.core.api.domain.common.Lookup;
import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor;
import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove;
import ai.timefold.solver.core.impl.heuristic.selector.move.generic.RuinRecreateConstructionHeuristicPhase;
@@ -19,7 +20,6 @@
import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector;
import ai.timefold.solver.core.impl.solver.scope.SolverScope;
import ai.timefold.solver.core.preview.api.move.Move;
-import ai.timefold.solver.core.preview.api.move.Rebaser;
import org.jspecify.annotations.NullMarked;
@@ -162,10 +162,10 @@ public SequencedCollection getPlanningValues() {
}
@Override
- public Move rebase(Rebaser rebaser) {
- var rebasedRuinedValueList = AbstractSelectorBasedMove.rebaseList(ruinedValueList, rebaser);
- var rebasedAffectedEntitySet = AbstractSelectorBasedMove.rebaseSet(affectedEntitySet, rebaser);
- var rebasedListVariableDescriptor = ((MoveDirector) rebaser)
+ public Move rebase(Lookup lookup) {
+ var rebasedRuinedValueList = AbstractSelectorBasedMove.rebaseList(ruinedValueList, lookup);
+ var rebasedAffectedEntitySet = AbstractSelectorBasedMove.rebaseSet(affectedEntitySet, lookup);
+ var rebasedListVariableDescriptor = ((MoveDirector) lookup)
.getScoreDirector()
.getSolutionDescriptor()
.getListVariableDescriptor();
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/decorator/FilteringValueSelector.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/decorator/FilteringValueSelector.java
index aa3a6590085..47be0edf4c6 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/decorator/FilteringValueSelector.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/decorator/FilteringValueSelector.java
@@ -4,7 +4,6 @@
import java.util.Objects;
import java.util.function.Supplier;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.domain.variable.ListVariableStateSupply;
import ai.timefold.solver.core.impl.domain.variable.descriptor.GenuineVariableDescriptor;
import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor;
@@ -14,6 +13,7 @@
import ai.timefold.solver.core.impl.heuristic.selector.value.IterableValueSelector;
import ai.timefold.solver.core.impl.heuristic.selector.value.ValueSelector;
import ai.timefold.solver.core.impl.phase.scope.AbstractPhaseScope;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
public class FilteringValueSelector
extends AbstractDemandEnabledSelector
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/decorator/ProbabilityValueSelector.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/decorator/ProbabilityValueSelector.java
index ab375af0d4a..5ebf0863f67 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/decorator/ProbabilityValueSelector.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/decorator/ProbabilityValueSelector.java
@@ -5,7 +5,6 @@
import java.util.Objects;
import java.util.TreeMap;
-import ai.timefold.solver.core.api.score.director.ScoreDirector;
import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType;
import ai.timefold.solver.core.impl.domain.variable.descriptor.GenuineVariableDescriptor;
import ai.timefold.solver.core.impl.heuristic.selector.AbstractDemandEnabledSelector;
@@ -13,6 +12,7 @@
import ai.timefold.solver.core.impl.heuristic.selector.common.SelectionCacheLifecycleListener;
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory;
import ai.timefold.solver.core.impl.heuristic.selector.value.IterableValueSelector;
+import ai.timefold.solver.core.impl.score.director.ScoreDirector;
import ai.timefold.solver.core.impl.solver.random.RandomUtils;
import ai.timefold.solver.core.impl.solver.scope.SolverScope;
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/AbstractMove.java b/core/src/main/java/ai/timefold/solver/core/impl/move/AbstractMove.java
index 82c60c8d94c..e79a62cd9ed 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/move/AbstractMove.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/move/AbstractMove.java
@@ -27,7 +27,7 @@ public final String describe() {
var metaModels = variableMetaModels();
var substring = switch (metaModels.size()) {
case 0 -> "";
- case 1 -> OPENING_PARENTHESES + getVariableDescriptor(metaModels.get(0)).getSimpleEntityAndVariableName()
+ case 1 -> OPENING_PARENTHESES + getVariableDescriptor(metaModels.getFirst()).getSimpleEntityAndVariableName()
+ CLOSING_PARENTHESES;
default -> {
var stringBuilder = new StringBuilder()
@@ -37,7 +37,7 @@ public final String describe() {
if (first) {
first = false;
} else {
- stringBuilder.append(", ");
+ stringBuilder.append(",");
}
stringBuilder.append(getVariableDescriptor(variableMetaModel).getSimpleEntityAndVariableName());
}
@@ -48,8 +48,6 @@ public final String describe() {
return getClass().getSimpleName() + substring;
}
- public abstract String toString();
-
public abstract List extends GenuineVariableMetaModel> variableMetaModels();
@SuppressWarnings("unchecked")
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/ChangeAction.java b/core/src/main/java/ai/timefold/solver/core/impl/move/ChangeAction.java
index 5afbd45df41..27ab389af85 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/move/ChangeAction.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/move/ChangeAction.java
@@ -1,7 +1,7 @@
package ai.timefold.solver.core.impl.move;
+import ai.timefold.solver.core.api.domain.common.Lookup;
import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector;
-import ai.timefold.solver.core.preview.api.move.Rebaser;
public sealed interface ChangeAction
permits ListVariableAfterAssignmentAction, ListVariableAfterChangeAction, ListVariableAfterUnassignmentAction,
@@ -10,6 +10,6 @@ public sealed interface ChangeAction
void undo(VariableDescriptorAwareScoreDirector scoreDirector);
- ChangeAction rebase(Rebaser rebaser);
+ ChangeAction rebase(Lookup lookup);
}
\ No newline at end of file
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/ListVariableAfterAssignmentAction.java b/core/src/main/java/ai/timefold/solver/core/impl/move/ListVariableAfterAssignmentAction.java
index 432b82867e6..fce125ebc70 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/move/ListVariableAfterAssignmentAction.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/move/ListVariableAfterAssignmentAction.java
@@ -1,8 +1,8 @@
package ai.timefold.solver.core.impl.move;
+import ai.timefold.solver.core.api.domain.common.Lookup;
import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor;
import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector;
-import ai.timefold.solver.core.preview.api.move.Rebaser;
record ListVariableAfterAssignmentAction(Object element,
ListVariableDescriptor variableDescriptor) implements ChangeAction {
@@ -13,8 +13,8 @@ public void undo(VariableDescriptorAwareScoreDirector scoreDirector)
}
@Override
- public ChangeAction rebase(Rebaser rebaser) {
- return new ListVariableAfterAssignmentAction<>(rebaser.rebase(element), variableDescriptor);
+ public ChangeAction rebase(Lookup lookup) {
+ return new ListVariableAfterAssignmentAction<>(lookup.lookUpWorkingObject(element), variableDescriptor);
}
}
\ No newline at end of file
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/ListVariableAfterChangeAction.java b/core/src/main/java/ai/timefold/solver/core/impl/move/ListVariableAfterChangeAction.java
index 3ecbbd2647e..4f59aab7ed2 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/move/ListVariableAfterChangeAction.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/move/ListVariableAfterChangeAction.java
@@ -2,9 +2,9 @@
import java.util.List;
+import ai.timefold.solver.core.api.domain.common.Lookup;
import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor;
import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector;
-import ai.timefold.solver.core.preview.api.move.Rebaser;
record ListVariableAfterChangeAction(Entity_ entity, int fromIndex, int toIndex,
ListVariableDescriptor variableDescriptor) implements ChangeAction {
@@ -18,8 +18,9 @@ public void undo(VariableDescriptorAwareScoreDirector scoreDirector)
}
@Override
- public ChangeAction rebase(Rebaser rebaser) {
- return new ListVariableAfterChangeAction<>(rebaser.rebase(entity), fromIndex, toIndex, variableDescriptor);
+ public ChangeAction rebase(Lookup lookup) {
+ return new ListVariableAfterChangeAction<>(lookup.lookUpWorkingObject(entity), fromIndex, toIndex,
+ variableDescriptor);
}
}
\ No newline at end of file
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/ListVariableAfterUnassignmentAction.java b/core/src/main/java/ai/timefold/solver/core/impl/move/ListVariableAfterUnassignmentAction.java
index 78fac725a9c..8bfeb221e8f 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/move/ListVariableAfterUnassignmentAction.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/move/ListVariableAfterUnassignmentAction.java
@@ -1,8 +1,8 @@
package ai.timefold.solver.core.impl.move;
+import ai.timefold.solver.core.api.domain.common.Lookup;
import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor;
import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector;
-import ai.timefold.solver.core.preview.api.move.Rebaser;
record ListVariableAfterUnassignmentAction(Object element,
ListVariableDescriptor variableDescriptor) implements ChangeAction {
@@ -13,8 +13,8 @@ public void undo(VariableDescriptorAwareScoreDirector scoreDirector)
}
@Override
- public ChangeAction rebase(Rebaser rebaser) {
- return new ListVariableAfterUnassignmentAction<>(rebaser.rebase(element), variableDescriptor);
+ public ChangeAction rebase(Lookup lookup) {
+ return new ListVariableAfterUnassignmentAction<>(lookup.lookUpWorkingObject(element), variableDescriptor);
}
}
\ No newline at end of file
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/ListVariableBeforeAssignmentAction.java b/core/src/main/java/ai/timefold/solver/core/impl/move/ListVariableBeforeAssignmentAction.java
index d4f9ab51d46..4bb0faee2f5 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/move/ListVariableBeforeAssignmentAction.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/move/ListVariableBeforeAssignmentAction.java
@@ -1,8 +1,8 @@
package ai.timefold.solver.core.impl.move;
+import ai.timefold.solver.core.api.domain.common.Lookup;
import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor;
import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector;
-import ai.timefold.solver.core.preview.api.move.Rebaser;
record ListVariableBeforeAssignmentAction(Object element,
ListVariableDescriptor variableDescriptor) implements ChangeAction {
@@ -13,8 +13,8 @@ public void undo(VariableDescriptorAwareScoreDirector scoreDirector)
}
@Override
- public ChangeAction rebase(Rebaser rebaser) {
- return new ListVariableBeforeAssignmentAction<>(rebaser.rebase(element), variableDescriptor);
+ public ChangeAction rebase(Lookup lookup) {
+ return new ListVariableBeforeAssignmentAction<>(lookup.lookUpWorkingObject(element), variableDescriptor);
}
}
\ No newline at end of file
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/ListVariableBeforeChangeAction.java b/core/src/main/java/ai/timefold/solver/core/impl/move/ListVariableBeforeChangeAction.java
index a77c2a1a582..191e7664270 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/move/ListVariableBeforeChangeAction.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/move/ListVariableBeforeChangeAction.java
@@ -2,9 +2,9 @@
import java.util.List;
+import ai.timefold.solver.core.api.domain.common.Lookup;
import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor;
import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector;
-import ai.timefold.solver.core.preview.api.move.Rebaser;
record ListVariableBeforeChangeAction(Entity_ entity, List oldValue, int fromIndex,
int toIndex, ListVariableDescriptor variableDescriptor) implements ChangeAction {
@@ -16,9 +16,10 @@ public void undo(VariableDescriptorAwareScoreDirector scoreDirector)
}
@Override
- public ChangeAction rebase(Rebaser rebaser) {
- var rebasedValueList = oldValue().stream().map(rebaser::rebase).toList();
- return new ListVariableBeforeChangeAction<>(rebaser.rebase(entity), rebasedValueList, fromIndex, toIndex,
+ public ChangeAction rebase(Lookup lookup) {
+ var rebasedValueList = oldValue().stream().map(lookup::lookUpWorkingObject).toList();
+ return new ListVariableBeforeChangeAction<>(lookup.lookUpWorkingObject(entity), rebasedValueList, fromIndex,
+ toIndex,
variableDescriptor);
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/ListVariableBeforeUnassignmentAction.java b/core/src/main/java/ai/timefold/solver/core/impl/move/ListVariableBeforeUnassignmentAction.java
index 4dfc78c5eb1..bb36e7db689 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/move/ListVariableBeforeUnassignmentAction.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/move/ListVariableBeforeUnassignmentAction.java
@@ -1,8 +1,8 @@
package ai.timefold.solver.core.impl.move;
+import ai.timefold.solver.core.api.domain.common.Lookup;
import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor;
import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector;
-import ai.timefold.solver.core.preview.api.move.Rebaser;
record ListVariableBeforeUnassignmentAction(Object element,
ListVariableDescriptor variableDescriptor) implements ChangeAction {
@@ -13,8 +13,8 @@ public void undo(VariableDescriptorAwareScoreDirector scoreDirector)
}
@Override
- public ChangeAction rebase(Rebaser rebaser) {
- return new ListVariableBeforeUnassignmentAction<>(rebaser.rebase(element), variableDescriptor);
+ public ChangeAction rebase(Lookup lookup) {
+ return new ListVariableBeforeUnassignmentAction<>(lookup.lookUpWorkingObject(element), variableDescriptor);
}
}
\ No newline at end of file
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/MoveDirector.java b/core/src/main/java/ai/timefold/solver/core/impl/move/MoveDirector.java
index 0d1ce8a6eca..0dbb5153fe7 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/move/MoveDirector.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/move/MoveDirector.java
@@ -1,8 +1,11 @@
package ai.timefold.solver.core.impl.move;
+import java.util.List;
import java.util.Objects;
import java.util.function.BiFunction;
+import java.util.function.Function;
+import ai.timefold.solver.core.api.domain.common.Lookup;
import ai.timefold.solver.core.api.score.Score;
import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor;
import ai.timefold.solver.core.impl.domain.solution.descriptor.DefaultPlanningListVariableMetaModel;
@@ -16,17 +19,17 @@
import ai.timefold.solver.core.preview.api.domain.metamodel.ElementPosition;
import ai.timefold.solver.core.preview.api.domain.metamodel.GenuineVariableMetaModel;
import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningListVariableMetaModel;
+import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningSolutionMetaModel;
import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningVariableMetaModel;
import ai.timefold.solver.core.preview.api.domain.metamodel.UnassignedElement;
import ai.timefold.solver.core.preview.api.move.Move;
-import ai.timefold.solver.core.preview.api.move.Rebaser;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public sealed class MoveDirector>
- implements InnerMutableSolutionView, Rebaser permits EphemeralMoveDirector {
+ implements InnerMutableSolutionView, Lookup permits EphemeralMoveDirector {
protected final VariableDescriptorAwareScoreDirector externalScoreDirector;
private final InnerScoreDirector backingScoreDirector;
@@ -46,6 +49,11 @@ public MoveDirector(InnerScoreDirector scoreDirector) {
}
}
+ @Override
+ public PlanningSolutionMetaModel getSolutionMetaModel() {
+ return backingScoreDirector.getSolutionDescriptor().getMetaModel();
+ }
+
@Override
public final void assignValueAndAdd(
PlanningListVariableMetaModel variableMetaModel, Value_ planningValue,
@@ -65,6 +73,29 @@ public final void assignValueAndAdd(
externalScoreDirector.triggerVariableListeners();
}
+ @Override
+ public void assignValuesAndAdd(
+ PlanningListVariableMetaModel variableMetaModel, List values,
+ Entity_ destinationEntity, int destinationIndex) {
+ var variableDescriptor =
+ ((DefaultPlanningListVariableMetaModel) variableMetaModel).variableDescriptor();
+ for (var value : values) {
+ if (!(getPositionOf(variableMetaModel, value) instanceof UnassignedElement)) {
+ throw new IllegalStateException("Cannot assign an already assigned value (%s).".formatted(value));
+ }
+ externalScoreDirector.beforeListVariableElementAssigned(variableDescriptor, value);
+ }
+ externalScoreDirector.beforeListVariableChanged(variableDescriptor, destinationEntity, destinationIndex,
+ destinationIndex);
+ variableDescriptor.getValue(destinationEntity).addAll(destinationIndex, values);
+ externalScoreDirector.afterListVariableChanged(variableDescriptor, destinationEntity, destinationIndex,
+ destinationIndex + values.size());
+ for (var value : values) {
+ externalScoreDirector.afterListVariableElementAssigned(variableDescriptor, value);
+ }
+ externalScoreDirector.triggerVariableListeners();
+ }
+
@Override
public final void assignValueAndSet(
PlanningListVariableMetaModel variableMetaModel, Value_ planningValue,
@@ -273,10 +304,24 @@ public boolean isValueInRange(GenuineVariableMetaModel move) {
+ execute(move, false);
+ }
+
+ /**
+ * Execute a given move and make sure shadow variables are up to date after that.
+ *
+ * @param guaranteeFreshScore if true, a score calculation is forced after executing the move,
+ * to ensure the score is up to date.
+ */
+ public final void execute(Move move, boolean guaranteeFreshScore) {
move.execute(this);
externalScoreDirector.triggerVariableListeners();
+ if (guaranteeFreshScore) {
+ backingScoreDirector.calculateScore();
+ }
}
public final InnerScore executeTemporary(Move move) {
@@ -289,11 +334,22 @@ public final InnerScore executeTemporary(Move move) {
public Result_ executeTemporary(Move move,
TemporaryMovePostprocessor postprocessor) {
+ try (var ephemeralMoveDirector = ephemeral()) {
+ ephemeralMoveDirector.execute(move);
+ var score = backingScoreDirector.calculateScore();
+ return postprocessor.apply(score, ephemeralMoveDirector.createUndoMove());
+ }
+ }
+
+ public @Nullable Result_ executeTemporary(Move move,
+ Function postprocessor, boolean guaranteeFreshScore) {
var ephemeralMoveDirector = ephemeral();
- ephemeralMoveDirector.execute(move);
- var score = backingScoreDirector.calculateScore();
- var result = postprocessor.apply(score, ephemeralMoveDirector.createUndoMove());
+ ephemeralMoveDirector.execute(move, true);
+ var result = postprocessor.apply(backingScoreDirector.getWorkingSolution());
ephemeralMoveDirector.close(); // This undoes the move.
+ if (guaranteeFreshScore) {
+ backingScoreDirector.calculateScore();
+ }
return result;
}
@@ -355,7 +411,7 @@ public boolean isPinned(ListVariableDescriptor listVariableD
}
@Override
- public final @Nullable T rebase(@Nullable T problemFactOrPlanningEntity) {
+ public final @Nullable T lookUpWorkingObject(@Nullable T problemFactOrPlanningEntity) {
return externalScoreDirector.lookUpWorkingObject(problemFactOrPlanningEntity);
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/RecordedUndoMove.java b/core/src/main/java/ai/timefold/solver/core/impl/move/RecordedUndoMove.java
index 395d11ea681..49122138355 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/move/RecordedUndoMove.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/move/RecordedUndoMove.java
@@ -3,9 +3,9 @@
import java.util.List;
import java.util.Objects;
+import ai.timefold.solver.core.api.domain.common.Lookup;
import ai.timefold.solver.core.preview.api.move.Move;
import ai.timefold.solver.core.preview.api.move.MutableSolutionView;
-import ai.timefold.solver.core.preview.api.move.Rebaser;
import org.jspecify.annotations.NullMarked;
@@ -27,9 +27,9 @@ public void execute(MutableSolutionView solutionView) {
}
@Override
- public Move rebase(Rebaser rebaser) {
+ public Move rebase(Lookup lookup) {
return new RecordedUndoMove<>(variableChangeActionList.stream()
- .map(changeAction -> changeAction.rebase(rebaser))
+ .map(changeAction -> changeAction.rebase(lookup))
.toList());
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/TriggerVariableListenersAction.java b/core/src/main/java/ai/timefold/solver/core/impl/move/TriggerVariableListenersAction.java
index 888d64c5a46..0e05899bf28 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/move/TriggerVariableListenersAction.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/move/TriggerVariableListenersAction.java
@@ -1,7 +1,7 @@
package ai.timefold.solver.core.impl.move;
+import ai.timefold.solver.core.api.domain.common.Lookup;
import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector;
-import ai.timefold.solver.core.preview.api.move.Rebaser;
public final class TriggerVariableListenersAction implements ChangeAction