From 79e005f168cad0a5ab4e8ccb20e127bbe6d9fcde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Petrovick=C3=BD?= Date: Tue, 17 Feb 2026 09:48:03 +0100 Subject: [PATCH 1/3] refactor: no score director in custom phase # Conflicts: # docs/TODO.md --- .../core/api/solver/phase/PhaseCommand.java | 34 ++--- .../api/solver/phase/PhaseCommandContext.java | 98 ++++++++++++ .../solver/core/impl/move/AbstractMove.java | 6 +- .../solver/core/impl/move/MoveDirector.java | 59 ++++++- .../impl/phase/custom/CustomPhaseCommand.java | 25 --- .../impl/phase/custom/DefaultCustomPhase.java | 4 +- .../custom/DefaultPhaseCommandContext.java | 57 +++++++ .../preview/api/move/MutableSolutionView.java | 26 ++++ .../core/preview/api/move/SolutionView.java | 12 +- .../core/api/solver/SolverManagerTest.java | 144 +++++++++--------- .../config/solver/EnvironmentModeTest.java | 28 ++-- .../phase/custom/DefaultCustomPhaseTest.java | 8 +- .../core/impl/solver/DefaultSolverTest.java | 20 ++- .../core/impl/solver/SolverMetricsIT.java | 31 ++-- .../singleentity/MixedCustomPhaseCommand.java | 14 +- .../testutil/NoChangeCustomPhaseCommand.java | 8 +- docs/TODO.md | 1 + .../optimization-algorithms/overview.adoc | 34 ++++- 18 files changed, 414 insertions(+), 195 deletions(-) create mode 100644 core/src/main/java/ai/timefold/solver/core/api/solver/phase/PhaseCommandContext.java delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/phase/custom/CustomPhaseCommand.java create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/phase/custom/DefaultPhaseCommandContext.java 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..2c81e15ea2b 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#execute(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..6b5faeeeefa --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/api/solver/phase/PhaseCommandContext.java @@ -0,0 +1,98 @@ +package ai.timefold.solver.core.api.solver.phase; + +import java.util.function.Function; + +import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningSolutionMetaModel; +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; + +/** + * 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 { + + /** + * 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 #execute(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(); + + /** + * As defined by {@link #execute(Move, boolean)}, + * but with the guarantee of a fresh score. + */ + default void execute(Move move) { + execute(move, true); + } + + /** + * Executes the given move and updates the working solution, + * optionally without recalculating the score for performance reasons. + * + * @param move the move to execute + * @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. + */ + void execute(Move move, boolean guaranteeFreshScore); + + /** + * 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); + + /** + * As defined by {@link Rebaser#rebase(Object)}, but for the working solution of this context. + */ + @Nullable T lookupWorkingObject(@Nullable T original); + +} 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> variableMetaModels(); @SuppressWarnings("unchecked") 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..62d0eae5075 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,7 +1,9 @@ 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.score.Score; import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor; @@ -16,6 +18,7 @@ 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; @@ -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,26 @@ 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, 0, 0); + variableDescriptor.getValue(destinationEntity).addAll(destinationIndex, values); + externalScoreDirector.afterListVariableChanged(variableDescriptor, destinationEntity, 0, values.size()); + for (var value : values) { + externalScoreDirector.afterListVariableElementAssigned(variableDescriptor, value); + } + } + @Override public final void assignValueAndSet( PlanningListVariableMetaModel variableMetaModel, Value_ planningValue, @@ -273,10 +301,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 +331,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; } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/phase/custom/CustomPhaseCommand.java b/core/src/main/java/ai/timefold/solver/core/impl/phase/custom/CustomPhaseCommand.java deleted file mode 100644 index eb7bcf174f4..00000000000 --- a/core/src/main/java/ai/timefold/solver/core/impl/phase/custom/CustomPhaseCommand.java +++ /dev/null @@ -1,25 +0,0 @@ -package ai.timefold.solver.core.impl.phase.custom; - -import java.util.function.BooleanSupplier; - -import ai.timefold.solver.core.api.score.director.ScoreDirector; -import ai.timefold.solver.core.api.solver.phase.PhaseCommand; - -import org.jspecify.annotations.NullMarked; - -/** - * @deprecated Use {@link PhaseCommand} instead. - */ -@FunctionalInterface -@NullMarked -@Deprecated(forRemoval = true, since = "1.20.0") -public interface CustomPhaseCommand extends PhaseCommand { - - @Override - default void changeWorkingSolution(ScoreDirector scoreDirector, BooleanSupplier isPhaseTerminated) { - changeWorkingSolution(scoreDirector); - } - - void changeWorkingSolution(ScoreDirector scoreDirector); - -} diff --git a/core/src/main/java/ai/timefold/solver/core/impl/phase/custom/DefaultCustomPhase.java b/core/src/main/java/ai/timefold/solver/core/impl/phase/custom/DefaultCustomPhase.java index 09669ca1cb3..3496d7ac328 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/phase/custom/DefaultCustomPhase.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/phase/custom/DefaultCustomPhase.java @@ -84,9 +84,9 @@ public void phaseStarted(AbstractPhaseScope phaseScope) { } private void doStep(CustomStepScope stepScope, PhaseCommand customPhaseCommand) { - var scoreDirector = stepScope.getScoreDirector(); - customPhaseCommand.changeWorkingSolution(scoreDirector, + var commandContext = new DefaultPhaseCommandContext<>(stepScope.getMoveDirector(), () -> phaseTermination.isPhaseTerminated(stepScope.getPhaseScope())); + customPhaseCommand.changeWorkingSolution(commandContext); calculateWorkingStepScore(stepScope, customPhaseCommand); var solver = stepScope.getPhaseScope().getSolverScope().getSolver(); solver.getBestSolutionRecaller().processWorkingSolutionDuringStep(stepScope); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/phase/custom/DefaultPhaseCommandContext.java b/core/src/main/java/ai/timefold/solver/core/impl/phase/custom/DefaultPhaseCommandContext.java new file mode 100644 index 00000000000..925d837e3f1 --- /dev/null +++ b/core/src/main/java/ai/timefold/solver/core/impl/phase/custom/DefaultPhaseCommandContext.java @@ -0,0 +1,57 @@ +package ai.timefold.solver.core.impl.phase.custom; + +import java.util.Objects; +import java.util.function.BooleanSupplier; +import java.util.function.Function; + +import ai.timefold.solver.core.api.solver.phase.PhaseCommandContext; +import ai.timefold.solver.core.impl.move.MoveDirector; +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; + +@NullMarked +final class DefaultPhaseCommandContext implements PhaseCommandContext { + + private final MoveDirector moveDirector; + private final BooleanSupplier isPhaseTerminated; + + public DefaultPhaseCommandContext(MoveDirector moveDirector, BooleanSupplier isPhaseTerminated) { + this.moveDirector = Objects.requireNonNull(moveDirector); + this.isPhaseTerminated = Objects.requireNonNull(isPhaseTerminated); + } + + @Override + public PlanningSolutionMetaModel getSolutionMetaModel() { + return moveDirector.getSolutionMetaModel(); + } + + @Override + public Solution_ getWorkingSolution() { + return moveDirector.getScoreDirector().getWorkingSolution(); + } + + @Override + public boolean isPhaseTerminated() { + return isPhaseTerminated.getAsBoolean(); + } + + @Override + public T lookupWorkingObject(T original) { + return moveDirector.rebase(original); + } + + @Override + public void execute(Move move, boolean guaranteeFreshScore) { + moveDirector.execute(move, guaranteeFreshScore); + } + + @Override + public @Nullable Result_ executeTemporarily(Move move, + Function temporarySolutionConsumer, boolean guaranteeFreshScore) { + return moveDirector.executeTemporary(move, temporarySolutionConsumer, guaranteeFreshScore); + } + +} diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/MutableSolutionView.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/MutableSolutionView.java index 16fb9c78ad4..42144cf5d18 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/move/MutableSolutionView.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/MutableSolutionView.java @@ -1,5 +1,6 @@ package ai.timefold.solver.core.preview.api.move; +import java.util.Collection; import java.util.List; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; @@ -69,6 +70,31 @@ default void assignValueAndAdd( assignValueAndAdd(variableMetaModel, value, destination.entity(), destination.index()); } + /** + * Puts given sequence of values at a particular index in a given entity's {@link PlanningListVariable planning list + * variable}. + * Moves all values at or after the index to the right, much like {@link List#addAll(int, Collection)}. + * + * @param variableMetaModel Describes the variable to be changed. + * @param values The sequence of values to be assigned to a list variable. + * @param destinationEntity The entity whose list variable is to be changed. + * @param destinationIndex The index in the list variable at which the value is to be assigned, + * moving the pre-existing value at that index and all subsequent values to the right. + * @throws IllegalStateException if any of the values is already assigned to a list variable + */ + void assignValuesAndAdd(PlanningListVariableMetaModel variableMetaModel, + List values, Entity_ destinationEntity, int destinationIndex); + + /** + * As defined by {@link #assignValuesAndAdd(PlanningListVariableMetaModel, List, Object, int)}, + * but using {@link PositionInList} to specify the position. + */ + default void assignValuesAndAdd( + PlanningListVariableMetaModel variableMetaModel, List values, + PositionInList destination) { + assignValuesAndAdd(variableMetaModel, values, destination.entity(), destination.index()); + } + /** * Puts a given value at a particular index in a given entity's {@link PlanningListVariable planning list variable}, * much like {@link List#set(int, Object)}. diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/SolutionView.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/SolutionView.java index 4c944a4d019..2f346c7a720 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/move/SolutionView.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/SolutionView.java @@ -9,6 +9,7 @@ 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 org.jspecify.annotations.NullMarked; @@ -28,15 +29,22 @@ * Please direct your feedback to * Timefold Solver GitHub * or to Timefold Discord. - * + * * @param */ @NullMarked public interface SolutionView { + /** + * Gets the meta-model of the solution, which provides access to the structure of the solution and its variables. + * + * @return the meta-model of the solution + */ + PlanningSolutionMetaModel getSolutionMetaModel(); + /** * Reads the value of a {@link PlanningVariable basic planning variable} of a given entity. - * + * * @param variableMetaModel Describes the variable whose value is to be read. * @param entity The entity whose variable is to be read. * @return The value of the variable on the entity. diff --git a/core/src/test/java/ai/timefold/solver/core/api/solver/SolverManagerTest.java b/core/src/test/java/ai/timefold/solver/core/api/solver/SolverManagerTest.java index 94364d93769..bf0efc614af 100644 --- a/core/src/test/java/ai/timefold/solver/core/api/solver/SolverManagerTest.java +++ b/core/src/test/java/ai/timefold/solver/core/api/solver/SolverManagerTest.java @@ -32,17 +32,16 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiConsumer; -import java.util.function.BooleanSupplier; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.IntStream; -import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.api.solver.event.EventProducerId; import ai.timefold.solver.core.api.solver.event.FinalBestSolutionEvent; import ai.timefold.solver.core.api.solver.event.FirstInitializedSolutionEvent; import ai.timefold.solver.core.api.solver.event.NewBestSolutionEvent; import ai.timefold.solver.core.api.solver.phase.PhaseCommand; +import ai.timefold.solver.core.api.solver.phase.PhaseCommandContext; import ai.timefold.solver.core.config.constructionheuristic.ConstructionHeuristicPhaseConfig; import ai.timefold.solver.core.config.localsearch.LocalSearchPhaseConfig; import ai.timefold.solver.core.config.phase.PhaseConfig; @@ -55,6 +54,7 @@ import ai.timefold.solver.core.impl.solver.scope.SolverScope; import ai.timefold.solver.core.impl.solver.termination.TerminationFactory; import ai.timefold.solver.core.impl.util.MutableReference; +import ai.timefold.solver.core.preview.api.move.builtin.Moves; import ai.timefold.solver.core.testdomain.TestdataConstraintProvider; import ai.timefold.solver.core.testdomain.TestdataEntity; import ai.timefold.solver.core.testdomain.TestdataSolution; @@ -125,7 +125,7 @@ private CustomPhaseConfig createPhaseWithConcurrentSolvingStart(int barrierParti var barrier = new CyclicBarrier(barrierPartiesCount); return new CustomPhaseConfig() .withCustomPhaseCommands( - (PhaseCommand) (scoreDirector, isPhaseTerminated) -> { + (PhaseCommand) context -> { try { barrier.await(); } catch (InterruptedException | BrokenBarrierException e) { @@ -141,7 +141,7 @@ void getSolverStatus() throws InterruptedException, BrokenBarrierException, Exec var mainThreadReadyBarrier = new CyclicBarrier(2); var solverConfig = PlannerTestUtils.buildSolverConfig(TestdataSolution.class, TestdataEntity.class) .withPhases(new CustomPhaseConfig().withCustomPhaseCommands( - (scoreDirector, isPhaseTerminated) -> { + context -> { try { solverThreadReadyBarrier.await(); } catch (InterruptedException | BrokenBarrierException e) { @@ -183,7 +183,7 @@ void getSolverStatus() throws InterruptedException, BrokenBarrierException, Exec void exceptionInSolver() { var solverConfig = PlannerTestUtils.buildSolverConfig(TestdataSolution.class, TestdataEntity.class) .withPhases(new CustomPhaseConfig().withCustomPhaseCommands( - (scoreDirector, isPhaseTerminated) -> { + context -> { throw new IllegalStateException("exceptionInSolver"); })); try (var solverManager = createSolverManagerWithOneSolver(solverConfig)) { @@ -207,7 +207,7 @@ void exceptionInSolver() { void errorThrowableInSolver() { var solverConfig = PlannerTestUtils.buildSolverConfig(TestdataSolution.class, TestdataEntity.class) .withPhases(new CustomPhaseConfig().withCustomPhaseCommands( - (scoreDirector, isPhaseTerminated) -> { + context -> { throw new OutOfMemoryError("exceptionInSolver"); })); try (var solverManager = createSolverManagerWithOneSolver(solverConfig)) { @@ -335,7 +335,7 @@ void firstInitializedSolutionConsumerWithSingleLSPhase() throws ExecutionExcepti .withBestScoreLimit("0")); try (var solverManager = createDefaultSolverManager(solverConfig)) { var initializedSolution = PlannerTestUtils.generateTestdataSolution("s1"); - initializedSolution.getEntityList().forEach(e -> e.setValue(initializedSolution.getValueList().get(0))); + initializedSolution.getEntityList().forEach(e -> e.setValue(initializedSolution.getValueList().getFirst())); var solverJob = solverManager.solveBuilder() .withProblemId(1L) @@ -483,8 +483,7 @@ void firstInitializedSolutionConsumerWithCustomAndCHAndLS() throws ExecutionExce // CS - CH - LS var solverConfig = PlannerTestUtils.buildSolverConfig(TestdataSolution.class, TestdataEntity.class) .withPhases(new CustomPhaseConfig() - .withCustomPhaseCommands((scoreDirector, - isPhaseTerminated) -> assertThat(initialSolutionEvent.isInitialized()).isFalse()), + .withCustomPhaseCommands(context -> assertThat(initialSolutionEvent.isInitialized()).isFalse()), new ConstructionHeuristicPhaseConfig(), new LocalSearchPhaseConfig()) .withTerminationConfig(new TerminationConfig() @@ -512,11 +511,8 @@ void firstInitializedSolutionConsumerWithCHAndCustomAndLS() throws ExecutionExce var solverConfig = PlannerTestUtils.buildSolverConfig(TestdataSolution.class, TestdataEntity.class) .withPhases( new ConstructionHeuristicPhaseConfig(), - new CustomPhaseConfig() - .withCustomPhaseCommands( - (scoreDirector, isPhaseTerminated) -> { - assertThat(initialSolutionEvent.isInitialized()).isFalse(); - }), + new CustomPhaseConfig().withCustomPhaseCommands( + context -> assertThat(initialSolutionEvent.isInitialized()).isFalse()), new LocalSearchPhaseConfig()) .withTerminationConfig(new TerminationConfig() .withUnimprovedMillisecondsSpentLimit(1L)); @@ -542,10 +538,10 @@ void firstInitializedSolutionConsumerWith2Custom() throws ExecutionException, In // CS (CH) - CS (LS) var solverConfig = PlannerTestUtils.buildSolverConfig(TestdataSolution.class, TestdataEntity.class) .withPhases( - new CustomPhaseConfig().withCustomPhaseCommands((scoreDirector, - isPhaseTerminated) -> assertThat(initialSolutionEvent.isInitialized()).isFalse()), - new CustomPhaseConfig().withCustomPhaseCommands((scoreDirector, - isPhaseTerminated) -> assertThat(initialSolutionEvent.isInitialized()).isFalse())) + new CustomPhaseConfig() + .withCustomPhaseCommands(context -> assertThat(initialSolutionEvent.isInitialized()).isFalse()), + new CustomPhaseConfig() + .withCustomPhaseCommands(context -> assertThat(initialSolutionEvent.isInitialized()).isFalse())) .withTerminationConfig(new TerminationConfig() .withUnimprovedMillisecondsSpentLimit(1L)); try (var solverManager = createDefaultSolverManager(solverConfig)) { @@ -722,7 +718,7 @@ void testProblemSizeStatisticsForFinishedJob() throws ExecutionException, Interr void testProblemSizeStatisticsForWaitingJob() throws InterruptedException, ExecutionException { var solvingPausedLatch = new CountDownLatch(1); var pausedPhaseConfig = new CustomPhaseConfig().withCustomPhaseCommands( - (scoreDirector, isPhaseTerminated) -> { + context -> { try { solvingPausedLatch.await(); } catch (InterruptedException e) { @@ -754,10 +750,10 @@ void testProblemSizeStatisticsForWaitingJob() throws InterruptedException, Execu assertThat(problemSizeStatistics.approximateProblemScaleAsFormattedString()).isEqualTo("256"); var futureChange = solverManager - .addProblemChange(secondProblemId, (workingSolution, problemChangeDirector) -> { - problemChangeDirector.addProblemFact(new TestdataValue("addedValue"), - workingSolution.getValueList()::add); - }); + .addProblemChange(secondProblemId, + (workingSolution, problemChangeDirector) -> problemChangeDirector.addProblemFact( + new TestdataValue("addedValue"), + workingSolution.getValueList()::add)); // The first solver can proceed. When it finishes, the second solver starts solving and picks up the change. solvingPausedLatch.countDown(); @@ -876,36 +872,40 @@ void skipAhead() throws ExecutionException, InterruptedException { var latch = new CountDownLatch(1); var solverConfig = PlannerTestUtils.buildSolverConfig(TestdataSolution.class, TestdataEntity.class) .withPhases(new CustomPhaseConfig().withCustomPhaseCommands( - (ScoreDirector scoreDirector, BooleanSupplier booleanSupplier) -> { - var solution = scoreDirector.getWorkingSolution(); - var entity = solution.getEntityList().get(0); - scoreDirector.beforeVariableChanged(entity, "value"); - entity.setValue(solution.getValueList().get(0)); - scoreDirector.afterVariableChanged(entity, "value"); - scoreDirector.triggerVariableListeners(); - }, (ScoreDirector scoreDirector, BooleanSupplier booleanSupplier) -> { - var solution = scoreDirector.getWorkingSolution(); + (PhaseCommandContext context) -> { + var variableMetaModel = context.getSolutionMetaModel() + .genuineEntity(TestdataEntity.class) + .basicVariable("value", TestdataValue.class); + var solution = context.getWorkingSolution(); + var entity = solution.getEntityList().getFirst(); + var move = Moves.change(variableMetaModel, entity, solution.getValueList().getFirst()); + context.execute(move); + }, (PhaseCommandContext context) -> { + var variableMetaModel = context.getSolutionMetaModel() + .genuineEntity(TestdataEntity.class) + .basicVariable("value", TestdataValue.class); + var solution = context.getWorkingSolution(); var entity = solution.getEntityList().get(1); - scoreDirector.beforeVariableChanged(entity, "value"); - entity.setValue(solution.getValueList().get(1)); - scoreDirector.afterVariableChanged(entity, "value"); - scoreDirector.triggerVariableListeners(); - }, (ScoreDirector scoreDirector, BooleanSupplier booleanSupplier) -> { - var solution = scoreDirector.getWorkingSolution(); + var move = Moves.change(variableMetaModel, entity, solution.getValueList().get(1)); + context.execute(move); + }, (PhaseCommandContext context) -> { + var variableMetaModel = context.getSolutionMetaModel() + .genuineEntity(TestdataEntity.class) + .basicVariable("value", TestdataValue.class); + var solution = context.getWorkingSolution(); var entity = solution.getEntityList().get(2); - scoreDirector.beforeVariableChanged(entity, "value"); - entity.setValue(solution.getValueList().get(2)); - scoreDirector.afterVariableChanged(entity, "value"); - scoreDirector.triggerVariableListeners(); - }, (ScoreDirector scoreDirector, BooleanSupplier booleanSupplier) -> { + var move = Moves.change(variableMetaModel, entity, solution.getValueList().get(2)); + context.execute(move); + }, (PhaseCommandContext context) -> { // In the next best solution event, both e1 and e2 are definitely not null (but e3 might be). latch.countDown(); - var solution = scoreDirector.getWorkingSolution(); + var variableMetaModel = context.getSolutionMetaModel() + .genuineEntity(TestdataEntity.class) + .basicVariable("value", TestdataValue.class); + var solution = context.getWorkingSolution(); var entity = solution.getEntityList().get(3); - scoreDirector.beforeVariableChanged(entity, "value"); - entity.setValue(solution.getValueList().get(3)); - scoreDirector.afterVariableChanged(entity, "value"); - scoreDirector.triggerVariableListeners(); + var move = Moves.change(variableMetaModel, entity, solution.getValueList().get(3)); + context.execute(move); })); try (var solverManager = createSolverManagerWithOneSolver(solverConfig)) { var bestSolutionCount = new AtomicInteger(); @@ -951,7 +951,7 @@ void terminateEarly() throws InterruptedException, BrokenBarrierException { var startedBarrier = new CyclicBarrier(2); var solverConfig = PlannerTestUtils.buildSolverConfig(TestdataSolution.class, TestdataEntity.class) .withTerminationConfig(new TerminationConfig()) - .withPhases(new CustomPhaseConfig().withCustomPhaseCommands((scoreDirector, isPhaseTerminated) -> { + .withPhases(new CustomPhaseConfig().withCustomPhaseCommands(context -> { try { startedBarrier.await(); } catch (InterruptedException | BrokenBarrierException e) { @@ -1027,13 +1027,14 @@ private SolverManager createSolverManagerTestableByDiffe var solverConfig = PlannerTestUtils.buildSolverConfig(TestdataSolution.class, TestdataEntity.class) .withPhases(IntStream.of(0, 1) .mapToObj(x -> new CustomPhaseConfig().withCustomPhaseCommands( - (ScoreDirector scoreDirector, BooleanSupplier booleanSupplier) -> { - var solution = scoreDirector.getWorkingSolution(); + (PhaseCommandContext context) -> { + var variableMetaModel = context.getSolutionMetaModel() + .genuineEntity(TestdataEntity.class) + .basicVariable("value", TestdataValue.class); + var solution = context.getWorkingSolution(); var entity = solution.getEntityList().get(x); - scoreDirector.beforeVariableChanged(entity, "value"); - entity.setValue(solution.getValueList().get(x)); - scoreDirector.afterVariableChanged(entity, "value"); - scoreDirector.triggerVariableListeners(); + var move = Moves.change(variableMetaModel, entity, solution.getValueList().get(x)); + context.execute(move); })) .toArray(PhaseConfig[]::new)); return createDefaultSolverManager(solverConfig); @@ -1092,7 +1093,7 @@ private void assertSolveWithConsumer(int problemCount, SolverManager> consumedSolutions) { for (var consumedSolution : consumedSolutions.values()) { assertThat(consumedSolution).hasSize(1); - assertConsumedFinalBestSolution(consumedSolution.get(0)); + assertConsumedFinalBestSolution(consumedSolution.getFirst()); } } @@ -1102,7 +1103,7 @@ private void assertConsumedSolutionsWithListeningWhileSolving(Map { - problemChangeDirector.addProblemFact(new TestdataValue("addedValue"), - workingSolution.getValueList()::add); - }); + .addProblemChange(problemId, + (workingSolution, problemChangeDirector) -> problemChangeDirector.addProblemFact( + new TestdataValue("addedValue"), + workingSolution.getValueList()::add)); futureChange.get(); assertThat(futureChange).isCompleted(); @@ -1195,7 +1196,7 @@ void addProblemChangeToNonExistingProblem_failsFast() { void addProblemChangeToWaitingSolver() throws InterruptedException, ExecutionException { var solvingPausedLatch = new CountDownLatch(1); var pausedPhaseConfig = new CustomPhaseConfig().withCustomPhaseCommands( - (ScoreDirector scoreDirector, BooleanSupplier booleanSupplier) -> { + context -> { try { solvingPausedLatch.await(); } catch (InterruptedException e) { @@ -1221,10 +1222,10 @@ void addProblemChangeToWaitingSolver() throws InterruptedException, ExecutionExc .run(); var futureChange = solverManager - .addProblemChange(secondProblemId, (workingSolution, problemChangeDirector) -> { - problemChangeDirector.addProblemFact(new TestdataValue("addedValue"), - workingSolution.getValueList()::add); - }); + .addProblemChange(secondProblemId, + (workingSolution, problemChangeDirector) -> problemChangeDirector.addProblemFact( + new TestdataValue("addedValue"), + workingSolution.getValueList()::add)); // The first solver can proceed. When it finishes, the second solver starts solving and picks up the change. solvingPausedLatch.countDown(); @@ -1239,8 +1240,7 @@ void addProblemChangeToWaitingSolver() throws InterruptedException, ExecutionExc void terminateSolverJobEarly_stillReturnsBestSolution() throws ExecutionException, InterruptedException { var solvingStartedLatch = new CountDownLatch(1); var pausedPhaseConfig = new CustomPhaseConfig() - .withCustomPhaseCommands((ScoreDirector scoreDirector, - BooleanSupplier booleanSupplier) -> solvingStartedLatch.countDown()); + .withCustomPhaseCommands(context -> solvingStartedLatch.countDown()); var solverConfig = PlannerTestUtils.buildSolverConfig(TestdataSolution.class, TestdataEntity.class) .withPhases(pausedPhaseConfig, new ConstructionHeuristicPhaseConfig()); @@ -1259,7 +1259,7 @@ void terminateSolverJobEarly_stillReturnsBestSolution() throws ExecutionExceptio void terminateScheduledSolverJobEarly_returnsInputProblem() throws ExecutionException, InterruptedException { var solvingPausedLatch = new CountDownLatch(1); var pausedPhaseConfig = new CustomPhaseConfig().withCustomPhaseCommands( - (ScoreDirector scoreDirector, BooleanSupplier booleanSupplier) -> { + context -> { try { solvingPausedLatch.await(); } catch (InterruptedException e) { @@ -1296,7 +1296,7 @@ public Thread newThread(@NonNull Runnable runnable) { @Timeout(60) void threadFactoryIsUsed() throws ExecutionException, InterruptedException { var threadCheckingPhaseConfig = new CustomPhaseConfig().withCustomPhaseCommands( - (ScoreDirector scoreDirector, BooleanSupplier booleanSupplier) -> { + context -> { if (!Thread.currentThread().getName().equals(CustomThreadFactory.CUSTOM_THREAD_NAME)) { fail("Custom thread factory not used"); } diff --git a/core/src/test/java/ai/timefold/solver/core/config/solver/EnvironmentModeTest.java b/core/src/test/java/ai/timefold/solver/core/config/solver/EnvironmentModeTest.java index 6717327c1f2..6d2a37ae3c7 100644 --- a/core/src/test/java/ai/timefold/solver/core/config/solver/EnvironmentModeTest.java +++ b/core/src/test/java/ai/timefold/solver/core/config/solver/EnvironmentModeTest.java @@ -8,15 +8,14 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.function.BooleanSupplier; import java.util.stream.IntStream; import ai.timefold.solver.core.api.score.SimpleScore; import ai.timefold.solver.core.api.score.calculator.EasyScoreCalculator; -import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.api.solver.Solver; import ai.timefold.solver.core.api.solver.SolverFactory; import ai.timefold.solver.core.api.solver.phase.PhaseCommand; +import ai.timefold.solver.core.api.solver.phase.PhaseCommandContext; import ai.timefold.solver.core.config.localsearch.LocalSearchPhaseConfig; import ai.timefold.solver.core.config.phase.custom.CustomPhaseConfig; import ai.timefold.solver.core.config.score.director.ScoreDirectorFactoryConfig; @@ -29,9 +28,9 @@ import ai.timefold.solver.core.config.solver.testutil.corruptedundoshadow.CorruptedUndoShadowValue; import ai.timefold.solver.core.impl.phase.event.PhaseLifecycleListenerAdapter; import ai.timefold.solver.core.impl.phase.scope.AbstractStepScope; -import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; import ai.timefold.solver.core.impl.solver.DefaultSolver; import ai.timefold.solver.core.impl.solver.random.RandomFactory; +import ai.timefold.solver.core.preview.api.move.builtin.Moves; import ai.timefold.solver.core.testdomain.TestdataEntity; import ai.timefold.solver.core.testdomain.TestdataSolution; import ai.timefold.solver.core.testdomain.TestdataValue; @@ -274,25 +273,16 @@ private void setSolverConfigCalculatorClass(SolverConfig solverConfig, public static class TestdataFirstValueInitializer implements PhaseCommand { @Override - public void changeWorkingSolution(ScoreDirector scoreDirector, BooleanSupplier isPhaseTerminated) { - var solution = scoreDirector.getWorkingSolution(); + public void changeWorkingSolution(PhaseCommandContext context) { + var solution = context.getWorkingSolution(); var firstValue = solution.getValueList().get(0); + var variable = context.getSolutionMetaModel() + .genuineEntity(TestdataEntity.class) + .basicVariable("value", TestdataValue.class); for (var entity : solution.getEntityList()) { - scoreDirector.beforeVariableChanged(entity, "value"); - entity.setValue(firstValue); - scoreDirector.afterVariableChanged(entity, "value"); - } - - scoreDirector.triggerVariableListeners(); - var innerScoreDirector = - (InnerScoreDirector) scoreDirector; - var score = innerScoreDirector.calculateScore(); - - if (!score.isFullyAssigned()) { - throw new IllegalStateException("The solution (" + TestdataEntity.class.getSimpleName() - + ") was not fully initialized by CustomSolverPhase: (" - + this.getClass().getCanonicalName() + ")"); + var move = Moves.change(variable, entity, firstValue); + context.execute(move); } } } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/phase/custom/DefaultCustomPhaseTest.java b/core/src/test/java/ai/timefold/solver/core/impl/phase/custom/DefaultCustomPhaseTest.java index 673f5d47f87..f8a0993ac69 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/phase/custom/DefaultCustomPhaseTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/phase/custom/DefaultCustomPhaseTest.java @@ -3,11 +3,10 @@ import static org.assertj.core.api.Assertions.assertThat; import java.time.Duration; -import java.util.function.BooleanSupplier; -import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.api.solver.SolverFactory; import ai.timefold.solver.core.api.solver.phase.PhaseCommand; +import ai.timefold.solver.core.api.solver.phase.PhaseCommandContext; import ai.timefold.solver.core.config.phase.custom.CustomPhaseConfig; import ai.timefold.solver.core.config.solver.SolverConfig; import ai.timefold.solver.core.config.solver.termination.TerminationConfig; @@ -65,14 +64,13 @@ private static Duration measure(Runnable runnable) { private static final class LoopingPhaseCommand implements PhaseCommand { @Override - public void changeWorkingSolution(ScoreDirector scoreDirector, BooleanSupplier isPhaseTerminated) { + public void changeWorkingSolution(PhaseCommandContext context) { while (true) { - if (isPhaseTerminated.getAsBoolean()) { // Terminate when signal received. + if (context.isPhaseTerminated()) { // Terminate when signal received. return; } } } - } } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/solver/DefaultSolverTest.java b/core/src/test/java/ai/timefold/solver/core/impl/solver/DefaultSolverTest.java index 04ec2fc44be..e911dfd035e 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/solver/DefaultSolverTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/solver/DefaultSolverTest.java @@ -18,7 +18,6 @@ import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.LongAdder; -import java.util.function.BooleanSupplier; import java.util.random.RandomGenerator; import java.util.stream.IntStream; @@ -33,6 +32,7 @@ import ai.timefold.solver.core.api.solver.SolutionManager; import ai.timefold.solver.core.api.solver.SolverFactory; import ai.timefold.solver.core.api.solver.phase.PhaseCommand; +import ai.timefold.solver.core.api.solver.phase.PhaseCommandContext; import ai.timefold.solver.core.config.constructionheuristic.ConstructionHeuristicPhaseConfig; import ai.timefold.solver.core.config.constructionheuristic.placer.QueuedEntityPlacerConfig; import ai.timefold.solver.core.config.constructionheuristic.placer.QueuedValuePlacerConfig; @@ -73,6 +73,7 @@ import ai.timefold.solver.core.impl.score.constraint.DefaultIndictment; import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector; import ai.timefold.solver.core.impl.util.Pair; +import ai.timefold.solver.core.preview.api.move.builtin.Moves; import ai.timefold.solver.core.preview.api.neighborhood.Neighborhood; import ai.timefold.solver.core.preview.api.neighborhood.NeighborhoodBuilder; import ai.timefold.solver.core.preview.api.neighborhood.NeighborhoodProvider; @@ -462,12 +463,14 @@ void solveEmptyEntityList() { .filter(e -> e.getValue() == null)).isEmpty(); } + @NullMarked private static final class FailCommand implements PhaseCommand { @Override - public void changeWorkingSolution(ScoreDirector scoreDirector, BooleanSupplier isPhaseTerminated) { + public void changeWorkingSolution(PhaseCommandContext context) { fail("All phases should be skipped because there are no movable entities."); } + } @Test @@ -2266,12 +2269,13 @@ public static final class TestingMixedEasyScoreCalculator public static final class InvalidCustomPhaseCommand implements PhaseCommand { @Override - public void changeWorkingSolution(ScoreDirector scoreDirector, - BooleanSupplier isPhaseTerminated) { - var entity = scoreDirector.getWorkingSolution().getEntityList().getFirst(); - scoreDirector.beforeListVariableChanged(entity, "valueList", 0, 0); - entity.getValueList().add(new TestdataListValue("bad value")); - scoreDirector.afterListVariableChanged(entity, "valueList", 0, entity.getValueList().size()); + public void changeWorkingSolution(PhaseCommandContext context) { + var variableMetaModel = context.getSolutionMetaModel() + .genuineEntity(TestdataListEntity.class) + .listVariable("valueList", TestdataListValue.class); + var entity = context.getWorkingSolution().getEntityList().getFirst(); + var move = Moves.assign(variableMetaModel, new TestdataListValue("bad value"), entity, 0); + context.execute(move); } } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/solver/SolverMetricsIT.java b/core/src/test/java/ai/timefold/solver/core/impl/solver/SolverMetricsIT.java index 5c39f18ea9c..ca48c4b25bd 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/solver/SolverMetricsIT.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/solver/SolverMetricsIT.java @@ -19,7 +19,6 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.BooleanSupplier; import ai.timefold.solver.core.api.score.HardSoftScore; import ai.timefold.solver.core.api.score.SimpleScore; @@ -28,6 +27,7 @@ import ai.timefold.solver.core.api.solver.Solver; import ai.timefold.solver.core.api.solver.SolverFactory; import ai.timefold.solver.core.api.solver.phase.PhaseCommand; +import ai.timefold.solver.core.api.solver.phase.PhaseCommandContext; import ai.timefold.solver.core.config.constructionheuristic.ConstructionHeuristicPhaseConfig; import ai.timefold.solver.core.config.constructionheuristic.ConstructionHeuristicType; import ai.timefold.solver.core.config.exhaustivesearch.ExhaustiveSearchPhaseConfig; @@ -44,6 +44,7 @@ import ai.timefold.solver.core.impl.phase.event.PhaseLifecycleListenerAdapter; import ai.timefold.solver.core.impl.phase.scope.AbstractStepScope; import ai.timefold.solver.core.impl.solver.scope.SolverScope; +import ai.timefold.solver.core.preview.api.move.builtin.Moves; import ai.timefold.solver.core.testdomain.TestdataEntity; import ai.timefold.solver.core.testdomain.TestdataSolution; import ai.timefold.solver.core.testdomain.TestdataValue; @@ -464,25 +465,19 @@ void solveBestScoreMetrics() { .isEqualTo(2); } - private static class SetTestdataEntityValueCustomPhaseCommand implements PhaseCommand { - final TestdataEntity entity; - final TestdataValue value; - - public SetTestdataEntityValueCustomPhaseCommand(TestdataEntity entity, TestdataValue value) { - this.entity = entity; - this.value = value; - } + private record SetTestdataEntityValueCustomPhaseCommand(TestdataEntity entity, TestdataValue value) + implements + PhaseCommand { @Override - public void changeWorkingSolution(ScoreDirector scoreDirector, - BooleanSupplier isPhaseTerminated) { - var workingEntity = scoreDirector.lookUpWorkingObject(entity); - var workingValue = scoreDirector.lookUpWorkingObject(value); - - scoreDirector.beforeVariableChanged(workingEntity, "value"); - workingEntity.setValue(workingValue); - scoreDirector.afterVariableChanged(workingEntity, "value"); - scoreDirector.triggerVariableListeners(); + public void changeWorkingSolution(PhaseCommandContext context) { + var workingEntity = context.lookupWorkingObject(entity); + var workingValue = context.lookupWorkingObject(value); + + var move = Moves.change(context.getSolutionMetaModel() + .genuineEntity(TestdataEntity.class) + .basicVariable("value", TestdataValue.class), workingEntity, workingValue); + context.execute(move); } } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/MixedCustomPhaseCommand.java b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/MixedCustomPhaseCommand.java index 4deb7bb0229..2a9e92cfc2d 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/MixedCustomPhaseCommand.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/MixedCustomPhaseCommand.java @@ -1,19 +1,23 @@ package ai.timefold.solver.core.testdomain.mixed.singleentity; -import java.util.function.BooleanSupplier; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.api.solver.phase.PhaseCommand; -import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; +import ai.timefold.solver.core.api.solver.phase.PhaseCommandContext; public class MixedCustomPhaseCommand implements PhaseCommand { @Override - public void changeWorkingSolution(ScoreDirector scoreDirector, BooleanSupplier isPhaseTerminated) { + public void changeWorkingSolution(PhaseCommandContext context) { + var scoreDirector = (ScoreDirector) mock(ScoreDirector.class); + when(scoreDirector.getWorkingSolution()).thenReturn(context.getWorkingSolution()); + var moveIteratorFactory = new MixedCustomMoveIteratorFactory(); var moveIterator = moveIteratorFactory.createRandomMoveIterator(scoreDirector, null); var move = moveIterator.next(); - move.execute(((InnerScoreDirector) scoreDirector).getMoveDirector()); - scoreDirector.triggerVariableListeners(); + context.execute(move); } + } diff --git a/core/src/test/java/ai/timefold/solver/core/testutil/NoChangeCustomPhaseCommand.java b/core/src/test/java/ai/timefold/solver/core/testutil/NoChangeCustomPhaseCommand.java index 0a52e49a5ac..86834029ab8 100644 --- a/core/src/test/java/ai/timefold/solver/core/testutil/NoChangeCustomPhaseCommand.java +++ b/core/src/test/java/ai/timefold/solver/core/testutil/NoChangeCustomPhaseCommand.java @@ -1,9 +1,7 @@ package ai.timefold.solver.core.testutil; -import java.util.function.BooleanSupplier; - -import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.api.solver.phase.PhaseCommand; +import ai.timefold.solver.core.api.solver.phase.PhaseCommandContext; import org.jspecify.annotations.NullMarked; @@ -11,8 +9,8 @@ public final class NoChangeCustomPhaseCommand implements PhaseCommand { @Override - public void changeWorkingSolution(ScoreDirector scoreDirector, BooleanSupplier isPhaseTerminated) { - // Do nothing + public void changeWorkingSolution(PhaseCommandContext context) { + } } diff --git a/docs/TODO.md b/docs/TODO.md index e020ae1ccaf..cb5651a07d8 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -31,6 +31,7 @@ - [ ] domain.lookup package is gone, so is lookup from PlanningSolution. - [ ] lookups no longer accept null values. - [ ] `DomainAccessType` is gone, code uses GIZMO when possible +- [ ] `PhaseCommand` was significantly refactored. - [ ] `AutoDiscoverMemberType` is gone Remove this file when done. \ No newline at end of file diff --git a/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc b/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc index 307fcbc2a56..24ae758aed5 100644 --- a/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc +++ b/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc @@ -857,12 +857,38 @@ The `PhaseCommand` interface appears as follows: ---- public interface PhaseCommand { - void changeWorkingSolution(ScoreDirector scoreDirector, BooleanSupplier isPhaseTerminated); + void changeWorkingSolution(PhaseCommandContext commandContext); } ---- -Any change on the planning entities in a `PhaseCommand` must be notified to the ``ScoreDirector``. +The `PhaseCommandContext` interface offers the following methods: + +`Object getWorkingSolution()`:: +Returns the working solution of the `Solver` that is being solved. +This must not be directly modified, as that would corrupt the `Solver`. +`void execute(Move)`:: +Executes a `Move` on the working solution. +This is the only way to change the working solution without corrupting the `Solver`. +Users can either provide a custom `Move` implementation, +or choose from the `Move` implementations xref:optimization-algorithms/neighborhoods.adoc#neighborhoodsBuiltInMoves[provided out-of-the-box]. +`getSolutionMetaModel()` method of `PhaseCommandContext` can be used to get the `SolutionMetaModel` of the working solution, +which will help with creating these `Move` instances. +`Object executeTemporarily(Move, Function)`:: +As defined above, but the executed move is immediately undone. +The provided `Function` is executed while the move is still applied, +so that the user can perform arbitrary calculations on the working solution with the move applied. +The return value of the provided `Function` is returned by this method. +`boolean isPhaseTerminated()`:: +Returns `true` if the `PhaseCommand` should terminate. +`Object lookupWorkingObject(Object externalObject)`:: +Returns the working object that corresponds to the given external object. +This is useful when the `PhaseCommand` remembers an object from some previous working solution, +and needs to find the corresponding object in the current working solution, +which may have been xref:using-timefold-solver/modeling-planning-problems.adoc#cloningASolution[planning-cloned] since. + +Any change on the planning entities in a `PhaseCommand` must be done through the `execute(Move)` method, +to avoid corrupting the `Solver`. Long-running commands may want to periodically check `isPhaseTerminated` and when it returns `true`, terminate the command by returning. @@ -870,9 +896,9 @@ The solver will only terminate after the command returns. [WARNING] ==== -Do not change any of the problem facts in a `PhaseCommand`, +Do not change any of the problem facts in a `PhaseCommand`, and add or remove entities, it will corrupt the `Solver` because any previous score or solution was for a different problem. -To change problem facts, +To change problem facts, and to add or remove entities, read about xref:responding-to-change/responding-to-change.adoc[repeated planning] and use xref:responding-to-change/responding-to-change.adoc#problemChange[ProblemChange] instead. ==== From 15e593f0e2a6fa6dcec4ef230428a3eb18415224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Petrovick=C3=BD?= Date: Tue, 17 Feb 2026 10:23:37 +0100 Subject: [PATCH 2/3] refactor: score director no longer public --- .../solver/core/api/domain/common/PlanningId.java | 4 ++-- .../solution/PlanningEntityCollectionProperty.java | 3 +-- .../core/api/domain/solution/PlanningEntityProperty.java | 3 +-- .../partitionedsearch/PartitionedSearchPhaseConfig.java | 3 +-- .../solver/core/impl/domain/lookup/LookUpManager.java | 2 +- .../core/impl/domain/lookup/LookUpStrategyType.java | 2 +- .../domain/solution/descriptor/SolutionDescriptor.java | 2 +- .../cascade/CascadingUpdateShadowVariableDescriptor.java | 2 +- .../listener/support/VariableListenerSupport.java | 2 +- .../impl/exhaustivesearch/node/ExhaustiveSearchNode.java | 2 +- .../impl/exhaustivesearch/node/bounder/ScoreBounder.java | 2 +- .../node/bounder/TrendBasedScoreBounder.java | 2 +- .../node/comparator/BreadthFirstNodeComparator.java | 2 +- .../impl/heuristic/move/AbstractSelectorBasedMove.java | 2 +- .../solver/core/impl/heuristic/move/MoveAdapters.java | 2 +- .../impl/heuristic/move/SelectorBasedCompositeMove.java | 2 +- .../impl/heuristic/move/SelectorBasedNoChangeMove.java | 2 +- .../common/decorator/CompositeSelectionFilter.java | 2 +- .../decorator/FairSelectorProbabilityWeightFactory.java | 2 +- .../selector/common/decorator/SelectionFilter.java | 2 +- .../decorator/SelectionProbabilityWeightFactory.java | 2 +- .../entity/decorator/FilteringEntitySelector.java | 2 +- .../entity/decorator/ProbabilityEntitySelector.java | 2 +- .../selector/move/DoableMoveSelectionFilter.java | 2 +- .../composite/FixedSelectorProbabilityWeightFactory.java | 2 +- .../selector/move/composite/UnionMoveSelector.java | 2 +- .../selector/move/decorator/FilteringMoveSelector.java | 2 +- .../selector/move/decorator/ProbabilityMoveSelector.java | 2 +- .../selector/move/factory/MoveIteratorFactory.java | 2 +- .../factory/MoveIteratorFactoryToMoveSelectorBridge.java | 2 +- .../selector/move/generic/SelectorBasedChangeMove.java | 2 +- .../move/generic/SelectorBasedPillarChangeMove.java | 2 +- .../move/generic/SelectorBasedPillarSwapMove.java | 2 +- .../selector/move/generic/SelectorBasedSwapMove.java | 2 +- .../move/generic/list/SelectorBasedListAssignMove.java | 2 +- .../move/generic/list/SelectorBasedListChangeMove.java | 2 +- .../move/generic/list/SelectorBasedListSwapMove.java | 2 +- .../move/generic/list/SelectorBasedListUnassignMove.java | 2 +- .../generic/list/SelectorBasedSubListChangeMove.java | 2 +- .../move/generic/list/SelectorBasedSubListSwapMove.java | 2 +- .../generic/list/SelectorBasedSubListUnassignMove.java | 2 +- .../generic/list/kopt/SelectorBasedKOptListMove.java | 2 +- .../generic/list/kopt/SelectorBasedTwoOptListMove.java | 2 +- .../selector/value/decorator/FilteringValueSelector.java | 2 +- .../value/decorator/ProbabilityValueSelector.java | 2 +- .../impl/move/VariableChangeRecordingScoreDirector.java | 2 +- .../partitioner/SolutionPartitioner.java | 6 ++---- .../core/impl/score/director/AbstractScoreDirector.java | 1 - .../score/director/AbstractScoreDirectorFactory.java | 1 - .../core/impl/score/director/InnerScoreDirector.java | 1 - .../core/{api => impl}/score/director/ScoreDirector.java | 4 +++- .../core/impl/score/director/ValueRangeManager.java | 1 - .../director/VariableDescriptorAwareScoreDirector.java | 1 - .../core/impl/score/director/easy/EasyScoreDirector.java | 2 +- .../director/incremental/IncrementalScoreDirector.java | 2 +- .../stream/BavetConstraintStreamScoreDirector.java | 2 +- .../ai/timefold/solver/core/preview/api/move/Move.java | 9 ++++----- .../timefold/solver/core/preview/api/move/Rebaser.java | 5 ++--- .../heuristic/move/SelectorBasedNotDoableDummyMove.java | 2 +- .../selector/entity/EntitySelectorFactoryTest.java | 2 +- .../heuristic/selector/move/MoveSelectorFactoryTest.java | 2 +- .../move/generic/SelectorBasedChangeMoveTest.java | 2 +- .../selector/move/generic/SwapMoveSelectorTest.java | 2 +- .../move/generic/list/ListChangeMoveSelectorTest.java | 2 +- .../selector/value/ValueSelectorFactoryTest.java | 2 +- .../solver/core/impl/solver/DefaultSolverTest.java | 2 +- .../solver/core/impl/solver/SolverMetricsIT.java | 2 +- .../singleentity/MixedCustomMoveIteratorFactory.java | 2 +- .../mixed/singleentity/MixedCustomPhaseCommand.java | 2 +- docs/TODO.md | 2 ++ .../pages/enterprise-edition/enterprise-edition.adoc | 2 +- .../optimization-algorithms/move-selector-reference.adoc | 2 ++ .../pages/optimization-algorithms/neighborhoods.adoc | 2 +- .../pages/responding-to-change/responding-to-change.adoc | 2 +- .../TimefoldProcessorGeneratedGizmoSupplierTest.java | 5 ++--- 75 files changed, 81 insertions(+), 88 deletions(-) rename core/src/main/java/ai/timefold/solver/core/{api => impl}/score/director/ScoreDirector.java (92%) 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..c30ce49cfb0 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,13 @@ 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; +import ai.timefold.solver.core.preview.api.move.Rebaser; /** * Specifies that a bean property (or a field) is the id to match - * when {@link ScoreDirector#lookUpWorkingObject(Object) locating} + * when {@link Rebaser#rebase(Object) locating} * 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..ef2a37b727f 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 added registered with the solver. */ @Target({ METHOD, FIELD }) @Retention(RUNTIME) 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/LookUpManager.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/lookup/LookUpManager.java index 83ded2e7e5c..c1c9955369f 100644 --- 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 @@ -4,7 +4,7 @@ import java.util.Map; import ai.timefold.solver.core.api.domain.common.PlanningId; -import ai.timefold.solver.core.api.score.director.ScoreDirector; +import ai.timefold.solver.core.impl.score.director.ScoreDirector; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; 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/lookup/LookUpStrategyType.java index 92a33b6c092..fb75fba5976 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/lookup/LookUpStrategyType.java @@ -3,7 +3,7 @@ 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/solution/descriptor/SolutionDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/descriptor/SolutionDescriptor.java index f850e794e04..6d6a8a7f10b 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,7 +44,6 @@ 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; @@ -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; 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/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..9c9efaf5f5d 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 @@ -8,11 +8,11 @@ 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; 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..b46ed5a459d 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 @@ -7,8 +7,8 @@ import java.util.stream.Collectors; 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; 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..b00dbab5887 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 @@ -4,7 +4,7 @@ import java.util.SequencedCollection; 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; 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/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/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..c26b9f6d2a4 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 @@ -5,9 +5,9 @@ import java.util.SequencedCollection; 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; 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..110301f9522 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 @@ -6,9 +6,9 @@ import java.util.SequencedCollection; 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; 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..f9f5247f880 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 @@ -6,9 +6,9 @@ import java.util.SequencedCollection; 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; 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..92f149720ca 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 @@ -6,9 +6,9 @@ import java.util.SequencedCollection; 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; 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..f04b91b0985 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,9 +4,9 @@ import java.util.Objects; import java.util.SequencedCollection; -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; 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..88bde6e0bcc 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 @@ -7,9 +7,9 @@ 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; 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..c4783f672fb 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 @@ -7,9 +7,9 @@ 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; 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..0de8caf6099 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,9 +4,9 @@ import java.util.Objects; import java.util.SequencedCollection; -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; 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..94eec14f1c3 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 @@ -5,10 +5,10 @@ import java.util.SequencedCollection; 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; 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..8b2bab8d90b 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 @@ -6,10 +6,10 @@ import java.util.SequencedCollection; 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; 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..ff27cc980fc 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 @@ -5,10 +5,10 @@ import java.util.SequencedCollection; 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; 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..5b815be1347 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 @@ -6,9 +6,9 @@ import java.util.SequencedCollection; 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; 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..b831c537d77 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 @@ -6,9 +6,9 @@ import java.util.Objects; import java.util.SequencedCollection; -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.impl.util.CollectionUtils; 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/VariableChangeRecordingScoreDirector.java b/core/src/main/java/ai/timefold/solver/core/impl/move/VariableChangeRecordingScoreDirector.java index 4cec6300335..c2301c1d778 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/VariableChangeRecordingScoreDirector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/VariableChangeRecordingScoreDirector.java @@ -7,13 +7,13 @@ import java.util.Objects; import ai.timefold.solver.core.api.score.Score; -import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.VariableDescriptor; import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove; import ai.timefold.solver.core.impl.score.director.InnerScoreDirector; import ai.timefold.solver.core.impl.score.director.RevertableScoreDirector; +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.VariableDescriptorCache; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/partitionedsearch/partitioner/SolutionPartitioner.java b/core/src/main/java/ai/timefold/solver/core/impl/partitionedsearch/partitioner/SolutionPartitioner.java index a40b2519104..b304183a995 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/partitionedsearch/partitioner/SolutionPartitioner.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/partitionedsearch/partitioner/SolutionPartitioner.java @@ -6,7 +6,6 @@ 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.solution.cloner.SolutionCloner; -import ai.timefold.solver.core.api.score.director.ScoreDirector; /** * Splits one {@link PlanningSolution solution} into multiple partitions. @@ -27,11 +26,10 @@ public interface SolutionPartitioner { * Any class that is {@link SolutionCloner solution cloned} must also be partitioned cloned. * A class can be partition cloned without being solution cloned. * - * @param scoreDirector never null, the {@link ScoreDirector} - * which has the {@link ScoreDirector#getWorkingSolution()} that needs to be split up + * @param workingSolution the original solution to split; must not be modified by this method. * @param runnablePartThreadLimit null if unlimited, never negative * @return never null, {@link List#size()} of at least 1. */ - List splitWorkingSolution(ScoreDirector scoreDirector, Integer runnablePartThreadLimit); + List splitWorkingSolution(Solution_ workingSolution, Integer runnablePartThreadLimit); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/AbstractScoreDirector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/AbstractScoreDirector.java index 9a2b125808c..ed2f0fb2a65 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/AbstractScoreDirector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/AbstractScoreDirector.java @@ -18,7 +18,6 @@ import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.api.score.analysis.ConstraintAnalysis; import ai.timefold.solver.core.api.score.analysis.MatchAnalysis; -import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.api.solver.ScoreAnalysisFetchPolicy; import ai.timefold.solver.core.api.solver.change.ProblemChange; import ai.timefold.solver.core.api.solver.change.ProblemChangeDirector; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/AbstractScoreDirectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/AbstractScoreDirectorFactory.java index 5917115465e..9de1add6391 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/AbstractScoreDirectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/AbstractScoreDirectorFactory.java @@ -2,7 +2,6 @@ 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.config.solver.EnvironmentMode; import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/InnerScoreDirector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/InnerScoreDirector.java index 71134c5dd8f..6b15cfb2f2e 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/InnerScoreDirector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/InnerScoreDirector.java @@ -20,7 +20,6 @@ import ai.timefold.solver.core.api.score.constraint.ConstraintMatchTotal; import ai.timefold.solver.core.api.score.constraint.ConstraintRef; import ai.timefold.solver.core.api.score.constraint.Indictment; -import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.api.score.stream.Constraint; import ai.timefold.solver.core.api.score.stream.ConstraintJustification; import ai.timefold.solver.core.api.solver.ScoreAnalysisFetchPolicy; 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/impl/score/director/ScoreDirector.java similarity index 92% rename from core/src/main/java/ai/timefold/solver/core/api/score/director/ScoreDirector.java rename to core/src/main/java/ai/timefold/solver/core/impl/score/director/ScoreDirector.java index 24f17aa8041..5e23d457fcc 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/score/director/ScoreDirector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/ScoreDirector.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.api.score.director; +package ai.timefold.solver.core.impl.score.director; import ai.timefold.solver.core.api.domain.common.PlanningId; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; @@ -11,6 +11,8 @@ /** * The ScoreDirector holds the {@link PlanningSolution working solution} * and calculates the {@link Score} for it. + * This is not public API and the users should refrain from using it or its implementations directly. + * There are no backward compatibility guarantees for this API, and it may change without warning. * * @param the solution type, the class with the {@link PlanningSolution} annotation */ diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/ValueRangeManager.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/ValueRangeManager.java index fef42e73827..b6049753fad 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/ValueRangeManager.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/ValueRangeManager.java @@ -6,7 +6,6 @@ import ai.timefold.solver.core.api.domain.valuerange.ValueRange; 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.ProblemSizeStatistics; import ai.timefold.solver.core.api.solver.change.ProblemChange; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/VariableDescriptorAwareScoreDirector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/VariableDescriptorAwareScoreDirector.java index 047687bd8e0..0358e81b25f 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/VariableDescriptorAwareScoreDirector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/VariableDescriptorAwareScoreDirector.java @@ -1,6 +1,5 @@ package ai.timefold.solver.core.impl.score.director; -import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.VariableDescriptor; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirector.java index baceb3b1c72..562e2c1da07 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirector.java @@ -10,10 +10,10 @@ import ai.timefold.solver.core.api.score.constraint.ConstraintMatch; import ai.timefold.solver.core.api.score.constraint.ConstraintMatchTotal; import ai.timefold.solver.core.api.score.constraint.Indictment; -import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.impl.score.constraint.ConstraintMatchPolicy; import ai.timefold.solver.core.impl.score.director.AbstractScoreDirector; import ai.timefold.solver.core.impl.score.director.InnerScore; +import ai.timefold.solver.core.impl.score.director.ScoreDirector; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/incremental/IncrementalScoreDirector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/incremental/IncrementalScoreDirector.java index 576e3b6b589..be5baca30c3 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/incremental/IncrementalScoreDirector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/incremental/IncrementalScoreDirector.java @@ -14,7 +14,6 @@ import ai.timefold.solver.core.api.score.constraint.ConstraintMatch; import ai.timefold.solver.core.api.score.constraint.ConstraintMatchTotal; import ai.timefold.solver.core.api.score.constraint.Indictment; -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.domain.variable.descriptor.ListVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.VariableDescriptor; @@ -22,6 +21,7 @@ import ai.timefold.solver.core.impl.score.constraint.DefaultIndictment; import ai.timefold.solver.core.impl.score.director.AbstractScoreDirector; import ai.timefold.solver.core.impl.score.director.InnerScore; +import ai.timefold.solver.core.impl.score.director.ScoreDirector; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/stream/BavetConstraintStreamScoreDirector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/stream/BavetConstraintStreamScoreDirector.java index fab45272199..ae5aa3e2a46 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/stream/BavetConstraintStreamScoreDirector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/stream/BavetConstraintStreamScoreDirector.java @@ -9,13 +9,13 @@ import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.api.score.constraint.ConstraintMatchTotal; import ai.timefold.solver.core.api.score.constraint.Indictment; -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.domain.variable.declarative.ConsistencyTracker; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.VariableDescriptor; import ai.timefold.solver.core.impl.score.director.AbstractScoreDirector; 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.stream.bavet.BavetConstraintSession; import org.jspecify.annotations.NullMarked; diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/Move.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/Move.java index 3119a54f67c..56f69ca8eb1 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/move/Move.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/Move.java @@ -8,7 +8,6 @@ import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.domain.solution.ProblemFactProperty; import ai.timefold.solver.core.api.domain.variable.PlanningVariable; -import ai.timefold.solver.core.api.score.director.ScoreDirector; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -57,12 +56,12 @@ public interface Move { void execute(MutableSolutionView solutionView); /** - * Rebases a move from an origin {@link ScoreDirector} to another destination {@link ScoreDirector} - * which is usually on another {@link Thread}. + * Rebases a move from an origin working solution + * to another destination working solution which is usually on another {@link Thread}. * It is necessary for multithreaded solving to function. *

* The new move returned by this method translates the entities and problem facts - * to the destination {@link PlanningSolution} of the destination {@link ScoreDirector}, + * to the destination {@link PlanningSolution} of the destination. * That destination {@link PlanningSolution} is a deep planning clone (or an even deeper clone) * of the origin {@link PlanningSolution} that this move has been generated from. *

@@ -72,7 +71,7 @@ public interface Move { * as the original {@link PlanningSolution} to begin with. *

* An implementation of this method typically iterates through every entity and fact instance in this move, - * translates each one to the destination {@link ScoreDirector} with {@link Rebaser#rebase(Object)} + * translates each one to the destination with {@link Rebaser#rebase(Object)} * and creates a new move instance of the same move type, using those translated instances. *

* The destination {@link PlanningSolution} can be in a different state than the original {@link PlanningSolution}. diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/Rebaser.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/Rebaser.java index 0456a793527..0103ec3b0d0 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/move/Rebaser.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/Rebaser.java @@ -1,14 +1,13 @@ package ai.timefold.solver.core.preview.api.move; import ai.timefold.solver.core.api.domain.common.PlanningId; -import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.api.solver.change.ProblemChange; import org.jspecify.annotations.Nullable; /** * Allows to transfer an entity or fact instance (often from another {@link Thread}) - * to another {@link ScoreDirector}'s internal working instance. + * to another working solution. *

* This package and all of its contents are part of the Neighborhoods API, * which is under development and is only offered as a preview feature. @@ -26,7 +25,7 @@ public interface Rebaser { /** * Translates an entity or fact instance (often from another {@link Thread}) - * to another {@link ScoreDirector}'s internal working instance. + * to another working solution. * Useful for move rebasing and in a {@link ProblemChange} and for multi-threaded solving. *

* Matching uses {@link PlanningId}. diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedNotDoableDummyMove.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedNotDoableDummyMove.java index d34e4ab1685..2169e963862 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedNotDoableDummyMove.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedNotDoableDummyMove.java @@ -1,6 +1,6 @@ package ai.timefold.solver.core.impl.heuristic.move; -import ai.timefold.solver.core.api.score.director.ScoreDirector; +import ai.timefold.solver.core.impl.score.director.ScoreDirector; import ai.timefold.solver.core.testdomain.TestdataSolution; import org.jspecify.annotations.NullMarked; diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java index 1ac772e270a..65a3ccdba80 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java @@ -7,7 +7,6 @@ import java.util.Comparator; -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.heuristic.selector.common.SelectionOrder; import ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig; @@ -18,6 +17,7 @@ import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.ProbabilityEntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.ShufflingEntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.SortingEntitySelector; +import ai.timefold.solver.core.impl.score.director.ScoreDirector; import ai.timefold.solver.core.impl.solver.ClassInstanceCache; import ai.timefold.solver.core.testdomain.TestdataEntity; import ai.timefold.solver.core.testdomain.TestdataSolution; diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/MoveSelectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/MoveSelectorFactoryTest.java index 6cc6a34a47a..4284247675d 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/MoveSelectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/MoveSelectorFactoryTest.java @@ -7,7 +7,6 @@ import java.util.Comparator; import java.util.function.Consumer; -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.heuristic.selector.common.SelectionOrder; import ai.timefold.solver.core.config.heuristic.selector.common.decorator.SelectionSorterOrder; @@ -21,6 +20,7 @@ import ai.timefold.solver.core.impl.heuristic.selector.move.decorator.ProbabilityMoveSelector; import ai.timefold.solver.core.impl.heuristic.selector.move.decorator.ShufflingMoveSelector; import ai.timefold.solver.core.impl.heuristic.selector.move.decorator.SortingMoveSelector; +import ai.timefold.solver.core.impl.score.director.ScoreDirector; import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector; import ai.timefold.solver.core.testdomain.TestdataSolution; import ai.timefold.solver.core.testdomain.common.DummyValueFactory; diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedChangeMoveTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedChangeMoveTest.java index 7c93eef1586..e96c2fb164f 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedChangeMoveTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedChangeMoveTest.java @@ -8,10 +8,10 @@ import java.util.Arrays; import ai.timefold.solver.core.api.score.SimpleScore; -import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.config.heuristic.selector.move.generic.ChangeMoveSelectorConfig; import ai.timefold.solver.core.config.solver.EnvironmentMode; 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.easy.EasyScoreDirectorFactory; import ai.timefold.solver.core.testdomain.TestdataEntity; import ai.timefold.solver.core.testdomain.TestdataSolution; diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveSelectorTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveSelectorTest.java index a350b3b4b12..83560031d14 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveSelectorTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SwapMoveSelectorTest.java @@ -16,7 +16,6 @@ import java.util.List; import java.util.Random; -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.SelectorTestUtils; @@ -30,6 +29,7 @@ import ai.timefold.solver.core.impl.heuristic.selector.entity.mimic.MimicReplayingEntitySelector; import ai.timefold.solver.core.impl.phase.scope.AbstractPhaseScope; import ai.timefold.solver.core.impl.phase.scope.AbstractStepScope; +import ai.timefold.solver.core.impl.score.director.ScoreDirector; import ai.timefold.solver.core.impl.solver.scope.SolverScope; import ai.timefold.solver.core.testdomain.TestdataEntity; import ai.timefold.solver.core.testdomain.TestdataObject; diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListChangeMoveSelectorTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListChangeMoveSelectorTest.java index ab9125e8002..01615518f44 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListChangeMoveSelectorTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListChangeMoveSelectorTest.java @@ -25,9 +25,9 @@ import java.util.List; import java.util.Random; -import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.api.solver.SolutionManager; 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.domain.metamodel.ElementPosition; import ai.timefold.solver.core.testdomain.TestdataValue; import ai.timefold.solver.core.testdomain.list.TestdataListEntity; diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java index d0e58e0896c..7261b00e8e1 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java @@ -12,7 +12,6 @@ import java.util.List; import java.util.stream.Stream; -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.heuristic.selector.common.SelectionOrder; import ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig; @@ -33,6 +32,7 @@ import ai.timefold.solver.core.impl.heuristic.selector.value.decorator.UnassignedListValueSelector; import ai.timefold.solver.core.impl.phase.scope.AbstractPhaseScope; import ai.timefold.solver.core.impl.phase.scope.AbstractStepScope; +import ai.timefold.solver.core.impl.score.director.ScoreDirector; import ai.timefold.solver.core.impl.solver.ClassInstanceCache; import ai.timefold.solver.core.impl.solver.scope.SolverScope; import ai.timefold.solver.core.testdomain.TestdataEntity; diff --git a/core/src/test/java/ai/timefold/solver/core/impl/solver/DefaultSolverTest.java b/core/src/test/java/ai/timefold/solver/core/impl/solver/DefaultSolverTest.java index e911dfd035e..b8b27a2cb70 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/solver/DefaultSolverTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/solver/DefaultSolverTest.java @@ -28,7 +28,6 @@ import ai.timefold.solver.core.api.score.constraint.ConstraintMatchTotal; import ai.timefold.solver.core.api.score.constraint.ConstraintRef; import ai.timefold.solver.core.api.score.constraint.Indictment; -import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.api.solver.SolutionManager; import ai.timefold.solver.core.api.solver.SolverFactory; import ai.timefold.solver.core.api.solver.phase.PhaseCommand; @@ -71,6 +70,7 @@ import ai.timefold.solver.core.impl.score.DummySimpleScoreEasyScoreCalculator; import ai.timefold.solver.core.impl.score.constraint.DefaultConstraintMatchTotal; import ai.timefold.solver.core.impl.score.constraint.DefaultIndictment; +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.Pair; import ai.timefold.solver.core.preview.api.move.builtin.Moves; diff --git a/core/src/test/java/ai/timefold/solver/core/impl/solver/SolverMetricsIT.java b/core/src/test/java/ai/timefold/solver/core/impl/solver/SolverMetricsIT.java index ca48c4b25bd..40053bfc790 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/solver/SolverMetricsIT.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/solver/SolverMetricsIT.java @@ -23,7 +23,6 @@ import ai.timefold.solver.core.api.score.HardSoftScore; import ai.timefold.solver.core.api.score.SimpleScore; import ai.timefold.solver.core.api.score.calculator.EasyScoreCalculator; -import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.api.solver.Solver; import ai.timefold.solver.core.api.solver.SolverFactory; import ai.timefold.solver.core.api.solver.phase.PhaseCommand; @@ -43,6 +42,7 @@ import ai.timefold.solver.core.impl.heuristic.selector.move.generic.SelectorBasedChangeMove; import ai.timefold.solver.core.impl.phase.event.PhaseLifecycleListenerAdapter; import ai.timefold.solver.core.impl.phase.scope.AbstractStepScope; +import ai.timefold.solver.core.impl.score.director.ScoreDirector; import ai.timefold.solver.core.impl.solver.scope.SolverScope; import ai.timefold.solver.core.preview.api.move.builtin.Moves; import ai.timefold.solver.core.testdomain.TestdataEntity; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/MixedCustomMoveIteratorFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/MixedCustomMoveIteratorFactory.java index bb8949e6350..00920c687e9 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/MixedCustomMoveIteratorFactory.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/MixedCustomMoveIteratorFactory.java @@ -4,9 +4,9 @@ import java.util.random.RandomGenerator; import java.util.stream.Stream; -import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.impl.heuristic.selector.move.factory.MoveIteratorFactory; import ai.timefold.solver.core.impl.heuristic.selector.move.generic.SelectorBasedChangeMove; +import ai.timefold.solver.core.impl.score.director.ScoreDirector; public class MixedCustomMoveIteratorFactory implements MoveIteratorFactory> { diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/MixedCustomPhaseCommand.java b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/MixedCustomPhaseCommand.java index 2a9e92cfc2d..ea5c1594a70 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/MixedCustomPhaseCommand.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/MixedCustomPhaseCommand.java @@ -3,9 +3,9 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.api.solver.phase.PhaseCommand; import ai.timefold.solver.core.api.solver.phase.PhaseCommandContext; +import ai.timefold.solver.core.impl.score.director.ScoreDirector; public class MixedCustomPhaseCommand implements PhaseCommand { diff --git a/docs/TODO.md b/docs/TODO.md index cb5651a07d8..09d5ea1a787 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -32,6 +32,8 @@ - [ ] lookups no longer accept null values. - [ ] `DomainAccessType` is gone, code uses GIZMO when possible - [ ] `PhaseCommand` was significantly refactored. +- [ ] `ScoreDirector` no longer public API. +- [ ] `SolutionPartitioner` no longer uses ScoreDirector, uses solution instead. - [ ] `AutoDiscoverMemberType` is gone Remove this file when done. \ No newline at end of file diff --git a/docs/src/modules/ROOT/pages/enterprise-edition/enterprise-edition.adoc b/docs/src/modules/ROOT/pages/enterprise-edition/enterprise-edition.adoc index 573fa43d4bd..9d715993f33 100644 --- a/docs/src/modules/ROOT/pages/enterprise-edition/enterprise-edition.adoc +++ b/docs/src/modules/ROOT/pages/enterprise-edition/enterprise-edition.adoc @@ -628,7 +628,7 @@ Implement the `SolutionPartitioner` interface: ---- public interface SolutionPartitioner { - List splitWorkingSolution(ScoreDirector scoreDirector, Integer runnablePartThreadLimit); + List splitWorkingSolution(Solution_ workingSolution, Integer runnablePartThreadLimit); } ---- diff --git a/docs/src/modules/ROOT/pages/optimization-algorithms/move-selector-reference.adoc b/docs/src/modules/ROOT/pages/optimization-algorithms/move-selector-reference.adoc index b60efaedc8a..34c30afad0f 100644 --- a/docs/src/modules/ROOT/pages/optimization-algorithms/move-selector-reference.adoc +++ b/docs/src/modules/ROOT/pages/optimization-algorithms/move-selector-reference.adoc @@ -7,6 +7,8 @@ This chapter describes the move selectors that can be used to select moves for the optimization algorithms. NOTE: Move Selectors are not public API and even though they are not yet deprecated, we encourage you to check out the xref:optimization-algorithms/neighborhoods.adoc[the Neighborhoods API], which will eventually entirely replace the Move Selectors API. +While the Move Selectors API will continue to be supported throughout the lifetime of Timefold Solver 2.0, +it will only receive critical bug fixes and no new features. [#whatIsAMoveSelector] == What is a `MoveSelector`? diff --git a/docs/src/modules/ROOT/pages/optimization-algorithms/neighborhoods.adoc b/docs/src/modules/ROOT/pages/optimization-algorithms/neighborhoods.adoc index d7b774f4e55..98084cc309a 100644 --- a/docs/src/modules/ROOT/pages/optimization-algorithms/neighborhoods.adoc +++ b/docs/src/modules/ROOT/pages/optimization-algorithms/neighborhoods.adoc @@ -6,7 +6,7 @@ IMPORTANT: The Neighborhoods API is a preview feature. It intends to simplify the creation of custom moves, eventually replacing xref:optimization-algorithms/move-selector-reference.adoc[move selectors]. -The component is under development and key features are yet to be delivered. +The component is under development and many features are yet to be delivered. While we believe that the basic building blocks of the API are already stable, we reserve the right to change the API or remove any part of it. Your feedback is highly appreciated and will be imperative in shaping the future of this component. diff --git a/docs/src/modules/ROOT/pages/responding-to-change/responding-to-change.adoc b/docs/src/modules/ROOT/pages/responding-to-change/responding-to-change.adoc index 5e4573ad210..b39d56ce415 100644 --- a/docs/src/modules/ROOT/pages/responding-to-change/responding-to-change.adoc +++ b/docs/src/modules/ROOT/pages/responding-to-change/responding-to-change.adoc @@ -530,7 +530,7 @@ Changes to an original Solution entity’s variables must not affect its clone. When implementing problem changes, consider the following: -. Any change in a `ProblemChange` must be done on the `@PlanningSolution` instance of ``scoreDirector.getWorkingSolution()``. +. Any change in a `ProblemChange` must be done on the `@PlanningSolution` instance provided to the `ProblemChange` implementation. . The `workingSolution` is xref:using-timefold-solver/modeling-planning-problems.adoc#cloningASolution[a planning clone] of the ``BestSolutionChangedEvent``'s ``bestSolution``. * The `workingSolution` in the `Solver` is never the same solution instance as in the rest of your application: it is a planning clone. diff --git a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorGeneratedGizmoSupplierTest.java b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorGeneratedGizmoSupplierTest.java index f6b9cf0440c..558523a10c6 100644 --- a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorGeneratedGizmoSupplierTest.java +++ b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorGeneratedGizmoSupplierTest.java @@ -18,7 +18,6 @@ import ai.timefold.solver.core.api.score.SimpleScore; import ai.timefold.solver.core.api.score.calculator.EasyScoreCalculator; import ai.timefold.solver.core.api.score.calculator.IncrementalScoreCalculator; -import ai.timefold.solver.core.api.score.director.ScoreDirector; import ai.timefold.solver.core.api.score.stream.Constraint; import ai.timefold.solver.core.api.score.stream.ConstraintFactory; import ai.timefold.solver.core.api.score.stream.ConstraintProvider; @@ -28,6 +27,7 @@ import ai.timefold.solver.core.impl.heuristic.selector.move.factory.MoveListFactory; import ai.timefold.solver.core.impl.heuristic.selector.move.generic.SelectorBasedChangeMove; import ai.timefold.solver.core.impl.partitionedsearch.partitioner.SolutionPartitioner; +import ai.timefold.solver.core.impl.score.director.ScoreDirector; import ai.timefold.solver.core.preview.api.move.Move; import ai.timefold.solver.core.testdomain.TestdataEntity; import ai.timefold.solver.core.testdomain.TestdataSolution; @@ -157,8 +157,7 @@ public abstract static class DummyAbstractEntity { public static class DummySolutionPartitioner implements SolutionPartitioner { @Override - public List splitWorkingSolution(ScoreDirector scoreDirector, - Integer runnablePartThreadLimit) { + public List splitWorkingSolution(TestdataSolution workingSolution, Integer runnablePartThreadLimit) { return null; } } From f3528b2e6e52258a4e46ab775db26fa80806649b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Petrovick=C3=BD?= Date: Tue, 17 Feb 2026 10:42:31 +0100 Subject: [PATCH 3/3] refactor: remove all leftover deprecations --- .../solver/core/api/domain/common/Lookup.java | 32 ++ .../core/api/domain/common/PlanningId.java | 3 +- .../solution/PlanningEntityProperty.java | 2 +- .../solver/change/ProblemChangeDirector.java | 41 +- .../core/api/solver/phase/PhaseCommand.java | 2 +- .../api/solver/phase/PhaseCommandContext.java | 32 +- .../ImmutableLookupStrategy.java} | 10 +- .../LookUpStrategyType.java | 2 +- .../impl/domain/common/LookupManager.java | 50 ++ .../LookupStrategy.java} | 9 +- .../LookupStrategyResolver.java} | 23 +- .../NoneLookupStrategy.java} | 9 +- .../PlanningIdLookupStrategy.java} | 14 +- .../entity/descriptor/EntityDescriptor.java | 81 +--- .../impl/domain/lookup/LookUpManager.java | 73 --- .../descriptor/SolutionDescriptor.java | 8 +- .../descriptor/GenuineVariableDescriptor.java | 2 +- .../move/AbstractSelectorBasedMove.java | 10 +- .../move/SelectorBasedCompositeMove.java | 6 +- .../move/SelectorBasedNoChangeMove.java | 4 +- .../SelectionSorterWeightFactory.java | 44 -- .../impl/heuristic/selector/list/SubList.java | 9 +- .../move/generic/SelectorBasedChangeMove.java | 8 +- .../SelectorBasedPillarChangeMove.java | 8 +- .../generic/SelectorBasedPillarSwapMove.java | 8 +- .../SelectorBasedRuinRecreateMove.java | 8 +- .../move/generic/SelectorBasedSwapMove.java | 8 +- .../list/SelectorBasedListAssignMove.java | 8 +- .../list/SelectorBasedListChangeMove.java | 8 +- .../list/SelectorBasedListSwapMove.java | 8 +- .../list/SelectorBasedListUnassignMove.java | 7 +- .../list/SelectorBasedSubListChangeMove.java | 9 +- .../list/SelectorBasedSubListSwapMove.java | 6 +- .../SelectorBasedSubListUnassignMove.java | 8 +- .../list/kopt/SelectorBasedKOptListMove.java | 6 +- .../kopt/SelectorBasedTwoOptListMove.java | 11 +- .../SelectorBasedListRuinRecreateMove.java | 10 +- .../solver/core/impl/move/ChangeAction.java | 4 +- .../ListVariableAfterAssignmentAction.java | 6 +- .../move/ListVariableAfterChangeAction.java | 7 +- .../ListVariableAfterUnassignmentAction.java | 6 +- .../ListVariableBeforeAssignmentAction.java | 6 +- .../move/ListVariableBeforeChangeAction.java | 9 +- .../ListVariableBeforeUnassignmentAction.java | 6 +- .../solver/core/impl/move/MoveDirector.java | 13 +- .../core/impl/move/RecordedUndoMove.java | 6 +- .../move/TriggerVariableListenersAction.java | 4 +- .../core/impl/move/VariableChangeAction.java | 7 +- .../VariableChangeRecordingScoreDirector.java | 5 - .../stream/DefaultMoveStreamFactory.java | 23 - .../custom/DefaultPhaseCommandContext.java | 15 +- .../score/director/AbstractScoreDirector.java | 16 +- .../score/director/InnerScoreDirector.java | 7 - .../impl/score/director/ScoreDirector.java | 35 +- .../stream/common/AbstractConstraint.java | 3 - .../solver/core/impl/solver/Assigner.java | 2 +- .../change/DefaultProblemChangeDirector.java | 17 +- .../solver/core/preview/api/move/Move.java | 23 +- .../preview/api/move/MutableSolutionView.java | 12 - .../solver/core/preview/api/move/Rebaser.java | 43 -- .../preview/api/move/builtin/ChangeMove.java | 8 +- .../api/move/builtin/CompositeMove.java | 6 +- .../api/move/builtin/ListAssignMove.java | 8 +- .../api/move/builtin/ListChangeMove.java | 9 +- .../api/move/builtin/ListSwapMove.java | 7 +- .../api/move/builtin/ListUnassignMove.java | 6 +- .../preview/api/move/builtin/SwapMove.java | 7 +- .../stream/MoveStreamFactory.java | 21 - .../core/api/solver/SolverManagerTest.java | 10 +- .../config/solver/EnvironmentModeTest.java | 2 +- .../common/index/AbstractIndexerTest.java | 1 - ...DefaultConstructionHeuristicPhaseTest.java | 50 +- .../AbstractLookupTest.java | 11 +- .../LookupManagerTest.java} | 6 +- .../LookupStrategyIdOrFailTest.java} | 50 +- .../LookupStrategyIdOrNoneTest.java} | 22 +- .../LookupStrategyImmutableTest.java} | 6 +- .../LookupStrategyNoneTest.java} | 6 +- .../entity/EntitySelectorFactoryTest.java | 22 +- .../move/MoveSelectorFactoryTest.java | 4 +- .../decorator/SortingMoveSelectorTest.java | 14 +- .../value/ValueSelectorFactoryTest.java | 13 +- .../core/impl/move/MoveDirectorTest.java | 428 +++++++++++++++++- .../core/impl/solver/DefaultSolverTest.java | 2 +- .../core/impl/solver/SolverMetricsIT.java | 8 +- .../preview/api/move/builtin/DummyMove.java | 4 +- .../api/move/builtin/ListChangeMoveTest.java | 14 +- .../common/DummyEntityComparator.java | 15 + .../common/DummyEntityComparatorFactory.java | 20 + .../common/DummyValueComparator.java | 3 + .../common/DummyValueComparatorFactory.java | 18 + .../testdomain/common/DummyValueFactory.java | 13 - .../common/DummyWeightValueFactory.java | 14 - .../common/TestSortableFactory.java | 12 - ...java => TestSortableObjectComparator.java} | 5 +- .../TestSortableObjectComparatorFactory.java | 17 + ...ataObjectSortableDescendingComparator.java | 3 + ...ctSortableDescendingComparatorFactory.java | 17 + ...stdataObjectSortableDescendingFactory.java | 18 - .../TestdataListSortableEntity.java | 6 +- .../TestdataListFactorySortableEntity.java | 7 +- .../TestdataInvalidListSortableEntity.java | 4 +- ...dataListSortableEntityProvidingEntity.java | 6 +- ...tFactorySortableEntityProvidingEntity.java | 7 +- .../singleentity/MixedCustomPhaseCommand.java | 2 +- .../TestdataComparatorSortableEntity.java | 6 +- .../TestdataFactorySortableEntity.java | 6 +- ...aInvalidMixedComparatorSortableEntity.java | 4 +- ...mparatorSortableEntityProvidingEntity.java | 6 +- ...aFactorySortableEntityProvidingEntity.java | 6 +- .../core/testutil/PlannerTestUtils.java | 2 +- docs/TODO.md | 2 + .../constraint-configuration.adoc | 168 +------ .../score-calculation.adoc | 11 - .../neighborhoods.adoc | 8 +- .../optimization-algorithms/overview.adoc | 6 +- .../responding-to-change.adoc | 34 -- tools/migration/pom.xml | 2 +- .../change/MockProblemChangeDirector.java | 29 +- 119 files changed, 1026 insertions(+), 1058 deletions(-) create mode 100644 core/src/main/java/ai/timefold/solver/core/api/domain/common/Lookup.java rename core/src/main/java/ai/timefold/solver/core/impl/domain/{lookup/ImmutableLookUpStrategy.java => common/ImmutableLookupStrategy.java} (63%) rename core/src/main/java/ai/timefold/solver/core/impl/domain/{lookup => common}/LookUpStrategyType.java (95%) create mode 100644 core/src/main/java/ai/timefold/solver/core/impl/domain/common/LookupManager.java rename core/src/main/java/ai/timefold/solver/core/impl/domain/{lookup/LookUpStrategy.java => common/LookupStrategy.java} (52%) rename core/src/main/java/ai/timefold/solver/core/impl/domain/{lookup/LookUpStrategyResolver.java => common/LookupStrategyResolver.java} (80%) rename core/src/main/java/ai/timefold/solver/core/impl/domain/{lookup/NoneLookUpStrategy.java => common/NoneLookupStrategy.java} (75%) rename core/src/main/java/ai/timefold/solver/core/impl/domain/{lookup/PlanningIdLookUpStrategy.java => common/PlanningIdLookupStrategy.java} (86%) delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/domain/lookup/LookUpManager.java delete mode 100644 core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/common/decorator/SelectionSorterWeightFactory.java delete mode 100644 core/src/main/java/ai/timefold/solver/core/preview/api/move/Rebaser.java rename core/src/test/java/ai/timefold/solver/core/impl/domain/{lookup => common}/AbstractLookupTest.java (69%) rename core/src/test/java/ai/timefold/solver/core/impl/domain/{lookup/LookUpManagerTest.java => common/LookupManagerTest.java} (94%) rename core/src/test/java/ai/timefold/solver/core/impl/domain/{lookup/LookUpStrategyIdOrFailTest.java => common/LookupStrategyIdOrFailTest.java} (67%) rename core/src/test/java/ai/timefold/solver/core/impl/domain/{lookup/LookUpStrategyIdOrNoneTest.java => common/LookupStrategyIdOrNoneTest.java} (82%) rename core/src/test/java/ai/timefold/solver/core/impl/domain/{lookup/LookUpStrategyImmutableTest.java => common/LookupStrategyImmutableTest.java} (95%) rename core/src/test/java/ai/timefold/solver/core/impl/domain/{lookup/LookUpStrategyNoneTest.java => common/LookupStrategyNoneTest.java} (96%) create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyEntityComparator.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyEntityComparatorFactory.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyValueComparatorFactory.java delete mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyValueFactory.java delete mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyWeightValueFactory.java delete mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableFactory.java rename core/src/test/java/ai/timefold/solver/core/testdomain/common/{TestSortableComparator.java => TestSortableObjectComparator.java} (62%) create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableObjectComparatorFactory.java create mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableDescendingComparatorFactory.java delete mode 100644 core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableDescendingFactory.java 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 c30ce49cfb0..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 @@ -12,11 +12,10 @@ import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; import ai.timefold.solver.core.api.solver.change.ProblemChange; import ai.timefold.solver.core.preview.api.move.Move; -import ai.timefold.solver.core.preview.api.move.Rebaser; /** * Specifies that a bean property (or a field) is the id to match - * when {@link Rebaser#rebase(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/PlanningEntityProperty.java b/core/src/main/java/ai/timefold/solver/core/api/domain/solution/PlanningEntityProperty.java index ef2a37b727f..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 @@ -13,7 +13,7 @@ * 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 registered with the solver. + * 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/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 2c81e15ea2b..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 @@ -20,7 +20,7 @@ public interface PhaseCommand { /** * Changes the current {@link PhaseCommandContext#getWorkingSolution() working solution}. * The solver is notified of the changes through {@link PhaseCommandContext}, - * specifically through {@link PhaseCommandContext#execute(Move)}. + * 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. *

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 index 6b5faeeeefa..2d3d388e31d 100644 --- 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 @@ -2,9 +2,10 @@ 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 ai.timefold.solver.core.preview.api.move.Rebaser; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -17,7 +18,8 @@ * @see PhaseCommand */ @NullMarked -public interface PhaseCommandContext { +public interface PhaseCommandContext + extends Lookup { /** * Returns the meta-model of the {@link #getWorkingSolution() working solution}. @@ -29,7 +31,7 @@ public interface PhaseCommandContext { /** * Returns the current working solution. * It must not be modified directly, - * but only through {@link #execute(Move)} or {@link #executeTemporarily(Move, Function)}. + * 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 @@ -47,23 +49,21 @@ public interface PhaseCommandContext { boolean isPhaseTerminated(); /** - * As defined by {@link #execute(Move, boolean)}, - * but with the guarantee of a fresh score. + * Executes the given move and updates the working solution + * without recalculating the score for performance reasons. + * + * @param move the move to execute */ - default void execute(Move move) { - execute(move, true); - } + void execute(Move move); /** * Executes the given move and updates the working solution, - * optionally without recalculating the score for performance reasons. + * and returns the new score of the working solution. * * @param move the move to execute - * @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 new score of the working solution after executing the move */ - void execute(Move move, boolean guaranteeFreshScore); + > Score_ executeAndCalculateScore(Move move); /** * As defined by {@link #executeTemporarily(Move, Function, boolean)}, @@ -90,9 +90,7 @@ default void execute(Move move) { @Nullable Result_ executeTemporarily(Move move, Function temporarySolutionConsumer, boolean guaranteeFreshScore); - /** - * As defined by {@link Rebaser#rebase(Object)}, but for the working solution of this context. - */ - @Nullable T lookupWorkingObject(@Nullable T original); + @Override + @Nullable T lookUpWorkingObject(@Nullable T problemFactOrPlanningEntity); } 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 95% 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 fb75fba5976..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,4 +1,4 @@ -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; 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 c1c9955369f..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.impl.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 6d6a8a7f10b..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 @@ -47,12 +47,12 @@ 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; @@ -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/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/heuristic/move/AbstractSelectorBasedMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/AbstractSelectorBasedMove.java index 9c9efaf5f5d..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,6 +6,7 @@ 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.impl.domain.valuerange.descriptor.AbstractValueRangeDescriptor; @@ -16,7 +17,6 @@ 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/SelectorBasedCompositeMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedCompositeMove.java index b46ed5a459d..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.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 b00dbab5887..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.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/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/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/generic/SelectorBasedChangeMove.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/SelectorBasedChangeMove.java index c26b9f6d2a4..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.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 110301f9522..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.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 f9f5247f880..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,6 +5,7 @@ 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.impl.domain.variable.descriptor.GenuineVariableDescriptor; import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove; @@ -12,7 +13,6 @@ 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 92f149720ca..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.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 f04b91b0985..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.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 88bde6e0bcc..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.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 c4783f672fb..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.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 0de8caf6099..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.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 94eec14f1c3..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,6 +4,7 @@ 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.impl.domain.variable.descriptor.ListVariableDescriptor; import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove; @@ -11,7 +12,6 @@ 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 8b2bab8d90b..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,6 +5,7 @@ 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.impl.domain.variable.descriptor.ListVariableDescriptor; import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove; @@ -12,7 +13,6 @@ 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 ff27cc980fc..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,6 +4,7 @@ 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.impl.domain.variable.descriptor.ListVariableDescriptor; import ai.timefold.solver.core.impl.heuristic.move.AbstractSelectorBasedMove; @@ -11,7 +12,6 @@ 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 5b815be1347..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.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 b831c537d77..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.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/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 62d0eae5075..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 @@ -5,6 +5,7 @@ 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; @@ -22,14 +23,13 @@ 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; @@ -85,12 +85,15 @@ public void assignValuesAndAdd( } externalScoreDirector.beforeListVariableElementAssigned(variableDescriptor, value); } - externalScoreDirector.beforeListVariableChanged(variableDescriptor, destinationEntity, 0, 0); + externalScoreDirector.beforeListVariableChanged(variableDescriptor, destinationEntity, destinationIndex, + destinationIndex); variableDescriptor.getValue(destinationEntity).addAll(destinationIndex, values); - externalScoreDirector.afterListVariableChanged(variableDescriptor, destinationEntity, 0, values.size()); + externalScoreDirector.afterListVariableChanged(variableDescriptor, destinationEntity, destinationIndex, + destinationIndex + values.size()); for (var value : values) { externalScoreDirector.afterListVariableElementAssigned(variableDescriptor, value); } + externalScoreDirector.triggerVariableListeners(); } @Override @@ -408,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 { @Override @@ -10,7 +10,7 @@ public void undo(VariableDescriptorAwareScoreDirector scoreDirector) } @Override - public TriggerVariableListenersAction rebase(Rebaser rebaser) { + public TriggerVariableListenersAction rebase(Lookup lookup) { return this; } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/VariableChangeAction.java b/core/src/main/java/ai/timefold/solver/core/impl/move/VariableChangeAction.java index 443478c2515..0cc30f7dd03 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/VariableChangeAction.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/VariableChangeAction.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.VariableDescriptor; import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector; -import ai.timefold.solver.core.preview.api.move.Rebaser; record VariableChangeAction(Entity_ entity, Value_ oldValue, VariableDescriptor variableDescriptor) implements ChangeAction { @@ -15,8 +15,9 @@ public void undo(VariableDescriptorAwareScoreDirector scoreDirector) } @Override - public ChangeAction rebase(Rebaser rebaser) { - return new VariableChangeAction<>(rebaser.rebase(entity), rebaser.rebase(oldValue), variableDescriptor); + public ChangeAction rebase(Lookup lookup) { + return new VariableChangeAction<>(lookup.lookUpWorkingObject(entity), lookup.lookUpWorkingObject(oldValue), + variableDescriptor); } } \ No newline at end of file diff --git a/core/src/main/java/ai/timefold/solver/core/impl/move/VariableChangeRecordingScoreDirector.java b/core/src/main/java/ai/timefold/solver/core/impl/move/VariableChangeRecordingScoreDirector.java index c2301c1d778..c7d03da58a5 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/move/VariableChangeRecordingScoreDirector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/move/VariableChangeRecordingScoreDirector.java @@ -223,9 +223,4 @@ public void triggerVariableListeners() { return Objects.requireNonNull(backingScoreDirector).lookUpWorkingObject(externalObject); } - @Override - public @Nullable E lookUpWorkingObjectOrReturnNull(@Nullable E externalObject) { - return Objects.requireNonNull(backingScoreDirector).lookUpWorkingObjectOrReturnNull(externalObject); - } - } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/DefaultMoveStreamFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/DefaultMoveStreamFactory.java index 66f381364d8..262426fc8e6 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/DefaultMoveStreamFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/neighborhood/stream/DefaultMoveStreamFactory.java @@ -23,7 +23,6 @@ import ai.timefold.solver.core.preview.api.neighborhood.stream.function.BiNeighborhoodsMapper; import ai.timefold.solver.core.preview.api.neighborhood.stream.function.BiNeighborhoodsPredicate; import ai.timefold.solver.core.preview.api.neighborhood.stream.function.UniNeighborhoodsPredicate; -import ai.timefold.solver.core.preview.api.neighborhood.stream.joiner.NeighborhoodsJoiners; import ai.timefold.solver.core.preview.api.neighborhood.stream.sampling.UniSamplingStream; import org.jspecify.annotations.NullMarked; @@ -164,28 +163,6 @@ public SolutionDescriptor getSolutionDescriptor() { return enumeratingStreamFactory.getSolutionDescriptor(); } - @Override - public UniEnumeratingStream - forEachAssignablePosition(PlanningListVariableMetaModel variableMetaModel) { - // Stream with unpinned entities; - // includes null if the variable allows unassigned values. - var unpinnedEntities = - forEach(variableMetaModel.entity().type(), variableMetaModel.allowsUnassignedValues()); - // Stream with unpinned values, which are assigned to any list variable; - // always includes null so that we can later create a position at the end of the list, - // i.e. with no value after it. - var nodeSharingSupportFunctions = getNodeSharingSupportFunctions(variableMetaModel); - var unpinnedValues = forEach(variableMetaModel.type(), true) - .filter(nodeSharingSupportFunctions.assignedValueOrNullFilter); - // Joins the two previous streams to create pairs of (entity, value), - // eliminating values which do not match that entity's value range. - // It maps these pairs to expected target positions in that entity's list variable. - return unpinnedEntities.join(unpinnedValues, - NeighborhoodsJoiners.filtering(nodeSharingSupportFunctions.valueInRangeFilter)) - .map(nodeSharingSupportFunctions.toElementPositionMapper) - .distinct(); - } - public record NodeSharingSupportFunctions( PlanningVariableMetaModel variableMetaModel, BiNeighborhoodsPredicate differentValueFilter, diff --git a/core/src/main/java/ai/timefold/solver/core/impl/phase/custom/DefaultPhaseCommandContext.java b/core/src/main/java/ai/timefold/solver/core/impl/phase/custom/DefaultPhaseCommandContext.java index 925d837e3f1..9f1d728ffe0 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/phase/custom/DefaultPhaseCommandContext.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/phase/custom/DefaultPhaseCommandContext.java @@ -4,6 +4,7 @@ import java.util.function.BooleanSupplier; import java.util.function.Function; +import ai.timefold.solver.core.api.score.Score; import ai.timefold.solver.core.api.solver.phase.PhaseCommandContext; import ai.timefold.solver.core.impl.move.MoveDirector; import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningSolutionMetaModel; @@ -39,13 +40,19 @@ public boolean isPhaseTerminated() { } @Override - public T lookupWorkingObject(T original) { - return moveDirector.rebase(original); + public @Nullable T lookUpWorkingObject(@Nullable T problemFactOrPlanningEntity) { + return moveDirector.lookUpWorkingObject(problemFactOrPlanningEntity); } @Override - public void execute(Move move, boolean guaranteeFreshScore) { - moveDirector.execute(move, guaranteeFreshScore); + public void execute(Move move) { + moveDirector.execute(move, false); + } + + @Override + public > Score_ executeAndCalculateScore(Move move) { + moveDirector.execute(move, true); + return moveDirector.getScoreDirector().getSolutionDescriptor().getScore(getWorkingSolution()); } @Override diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/AbstractScoreDirector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/AbstractScoreDirector.java index ed2f0fb2a65..cf3e77e9512 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/AbstractScoreDirector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/AbstractScoreDirector.java @@ -22,8 +22,8 @@ import ai.timefold.solver.core.api.solver.change.ProblemChange; import ai.timefold.solver.core.api.solver.change.ProblemChangeDirector; import ai.timefold.solver.core.config.solver.EnvironmentMode; +import ai.timefold.solver.core.impl.domain.common.LookupManager; import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor; -import ai.timefold.solver.core.impl.domain.lookup.LookUpManager; import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; import ai.timefold.solver.core.impl.domain.variable.ListVariableStateSupply; import ai.timefold.solver.core.impl.domain.variable.VariableListener; @@ -78,7 +78,7 @@ public abstract class AbstractScoreDirector neighborhoodsElementUpdateNotifier; private final boolean lookUpEnabled; - private final @Nullable LookUpManager lookUpManager; + private final @Nullable LookupManager lookUpManager; protected final ConstraintMatchPolicy constraintMatchPolicy; private boolean expectShadowVariablesInCorrectState; private final VariableDescriptorCache variableDescriptorCache; @@ -112,7 +112,7 @@ protected AbstractScoreDirector(AbstractScoreDirectorBuilder(); var solutionDescriptor = this.scoreDirectorFactory.getSolutionDescriptor(); this.lookUpEnabled = builder.lookUpEnabled; - this.lookUpManager = lookUpEnabled ? new LookUpManager(solutionDescriptor.getLookUpStrategyResolver()) : null; + this.lookUpManager = lookUpEnabled ? new LookupManager(solutionDescriptor.getLookUpStrategyResolver()) : null; this.constraintMatchPolicy = builder.constraintMatchPolicy; this.expectShadowVariablesInCorrectState = builder.expectShadowVariablesInCorrectState; this.variableDescriptorCache = new VariableDescriptorCache<>(solutionDescriptor); @@ -669,16 +669,6 @@ public void afterProblemFactRemoved(Object problemFact) { return lookUpManager.lookUpWorkingObject(externalObject); } - @Override - public @Nullable E lookUpWorkingObjectOrReturnNull(@Nullable E externalObject) { - if (!lookUpEnabled) { - throw new IllegalStateException( - "When lookUpEnabled (%s) is disabled in the constructor, this method should not be called." - .formatted(lookUpEnabled)); - } - return lookUpManager.lookUpWorkingObjectOrReturnNull(externalObject); - } - // ************************************************************************ // Assert methods // ************************************************************************ diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/InnerScoreDirector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/InnerScoreDirector.java index 6b15cfb2f2e..5a3bfaca11c 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/InnerScoreDirector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/InnerScoreDirector.java @@ -401,13 +401,6 @@ default ScoreAnalysis buildScoreAnalysis(ScoreAnalysisFetchPolicy scoreA return new ScoreAnalysis<>(state.raw(), constraintAnalysisMap, state.isFullyAssigned()); } - /* - * The following methods are copied here from ScoreDirector because they are deprecated there for removal. - * They will only be supported on this type, which serves for internal use only, - * as opposed to ScoreDirector, which is a public type. - * This way, we can ensure that these methods are used correctly and in a safe manner. - */ - default void beforeEntityAdded(Object entity) { beforeEntityAdded(getSolutionDescriptor().findEntityDescriptorOrFail(entity.getClass()), entity); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/ScoreDirector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/ScoreDirector.java index 5e23d457fcc..357ec422ec5 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/ScoreDirector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/ScoreDirector.java @@ -1,12 +1,10 @@ package ai.timefold.solver.core.impl.score.director; -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.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} @@ -17,7 +15,8 @@ * @param the solution type, the class with the {@link PlanningSolution} annotation */ @NullMarked -public interface ScoreDirector { +public interface ScoreDirector + extends Lookup { /** * The {@link PlanningSolution} that is used to calculate the {@link Score}. @@ -45,32 +44,4 @@ public interface ScoreDirector { 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/impl/score/stream/common/AbstractConstraint.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/AbstractConstraint.java index 1012a561cf4..59c2e3310f3 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/AbstractConstraint.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/AbstractConstraint.java @@ -136,9 +136,6 @@ public ConstraintRef getConstraintRef() { @Override public > Score_ getConstraintWeight() { - if (defaultConstraintWeight == null) { // Configurable weights (deprecated) have no default. - return null; - } return adjustConstraintWeight((Score_) defaultConstraintWeight); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/solver/Assigner.java b/core/src/main/java/ai/timefold/solver/core/impl/solver/Assigner.java index 5b94349405c..f895c9654d7 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/solver/Assigner.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/solver/Assigner.java @@ -45,7 +45,7 @@ public List apply(InnerScoreDirector scoreDi .formatted(originalSolution, uninitializedCount)); } var originalScoreAnalysis = scoreDirector.buildScoreAnalysis(fetchPolicy); - var clonedElement = Objects.requireNonNull(scoreDirector.lookUpWorkingObject(originalElement)); + var clonedElement = scoreDirector.lookUpWorkingObject(originalElement); var processor = new AssignmentProcessor<>(solverFactory, propositionFunction, recommendationConstructor, fetchPolicy, clonedElement, originalScoreAnalysis); return processor.apply(scoreDirector); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/solver/change/DefaultProblemChangeDirector.java b/core/src/main/java/ai/timefold/solver/core/impl/solver/change/DefaultProblemChangeDirector.java index 4037dcebd2f..8df27dc613e 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/solver/change/DefaultProblemChangeDirector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/solver/change/DefaultProblemChangeDirector.java @@ -1,7 +1,6 @@ package ai.timefold.solver.core.impl.solver.change; import java.util.Objects; -import java.util.Optional; import java.util.function.Consumer; import ai.timefold.solver.core.api.solver.change.ProblemChangeDirector; @@ -35,7 +34,7 @@ public void addEntity(Entity entity, Consumer entityConsumer) { public void removeEntity(Entity entity, Consumer entityConsumer) { Objects.requireNonNull(entity, () -> "Entity (" + entity + ") cannot be null."); Objects.requireNonNull(entityConsumer, () -> "Entity consumer (" + entityConsumer + ") cannot be null."); - var workingEntity = lookUpWorkingObjectOrFail(entity); + var workingEntity = lookUpWorkingObject(entity); scoreDirector.beforeEntityRemoved(workingEntity); entityConsumer.accept(workingEntity); scoreDirector.afterEntityRemoved(workingEntity); @@ -47,7 +46,7 @@ public void changeVariable(Entity entity, String variableName, Consumer Objects.requireNonNull(entity, () -> "Entity (" + entity + ") cannot be null."); Objects.requireNonNull(variableName, () -> "Planning variable name (" + variableName + ") cannot be null."); Objects.requireNonNull(entityConsumer, () -> "Entity consumer (" + entityConsumer + ") cannot be null."); - var workingEntity = lookUpWorkingObjectOrFail(entity); + var workingEntity = lookUpWorkingObject(entity); scoreDirector.beforeVariableChanged(workingEntity, variableName); entityConsumer.accept(workingEntity); scoreDirector.afterVariableChanged(workingEntity, variableName); @@ -68,7 +67,7 @@ public void removeProblemFact(ProblemFact problemFact, Consumer "Problem fact (" + problemFact + ") cannot be null."); Objects.requireNonNull(problemFactConsumer, () -> "Problem fact consumer (" + problemFactConsumer + ") cannot be null."); - var workingProblemFact = lookUpWorkingObjectOrFail(problemFact); + var workingProblemFact = lookUpWorkingObject(problemFact); scoreDirector.beforeProblemFactRemoved(workingProblemFact); problemFactConsumer.accept(workingProblemFact); scoreDirector.afterProblemFactRemoved(workingProblemFact); @@ -81,7 +80,7 @@ public void changeProblemProperty(EntityOrProblemFact prob () -> "Problem fact or entity (" + problemFactOrEntity + ") cannot be null."); Objects.requireNonNull(problemFactOrEntityConsumer, () -> "Problem fact or entity consumer (" + problemFactOrEntityConsumer + ") cannot be null."); - var workingEntityOrProblemFact = lookUpWorkingObjectOrFail(problemFactOrEntity); + var workingEntityOrProblemFact = lookUpWorkingObject(problemFactOrEntity); scoreDirector.beforeProblemPropertyChanged(workingEntityOrProblemFact); problemFactOrEntityConsumer.accept(workingEntityOrProblemFact); scoreDirector.afterProblemPropertyChanged(workingEntityOrProblemFact); @@ -89,14 +88,8 @@ public void changeProblemProperty(EntityOrProblemFact prob @Override public @Nullable EntityOrProblemFact - lookUpWorkingObjectOrFail(@Nullable EntityOrProblemFact externalObject) { - return scoreDirector.lookUpWorkingObject(externalObject); - } - - @Override - public Optional lookUpWorkingObject(@Nullable EntityOrProblemFact externalObject) { - return Optional.ofNullable(scoreDirector.lookUpWorkingObjectOrReturnNull(externalObject)); + return scoreDirector.lookUpWorkingObject(externalObject); } @Override diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/Move.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/Move.java index 56f69ca8eb1..b6f4d04b648 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/move/Move.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/Move.java @@ -3,6 +3,7 @@ import java.util.Collection; import java.util.SequencedCollection; +import ai.timefold.solver.core.api.domain.common.Lookup; 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.PlanningSolution; @@ -25,19 +26,7 @@ *

* For tabu search, a Move should implement {@link Object#equals(Object)} and {@link Object#hashCode()}, * {@link #getPlanningEntities()} and {@link #getPlanningValues()}. - *

- * This package and all of its contents are part of the Neighborhoods API, - * which is under development and is only offered as a preview feature. - * There are no guarantees for backward compatibility; - * any class, method, or field may change or be removed without prior notice, - * although we will strive to avoid this as much as possible. - *

- * We encourage you to try the API and give us feedback on your experience with it, - * before we finalize the API. - * Please direct your feedback to - * Timefold Solver GitHub - * or to Timefold Discord. - * + * * @param * @see MoveTester How to test {@link Move}s. */ @@ -71,8 +60,10 @@ public interface Move { * as the original {@link PlanningSolution} to begin with. *

* An implementation of this method typically iterates through every entity and fact instance in this move, - * translates each one to the destination with {@link Rebaser#rebase(Object)} + * translates each one to the destination with {@link Lookup#lookUpWorkingObject(Object)} * and creates a new move instance of the same move type, using those translated instances. + * If the working object isn't getting cloned, as many problem facts wouldn't be, + * it doesn't need to be translated and can be reused in the new move instance as is. *

* The destination {@link PlanningSolution} can be in a different state than the original {@link PlanningSolution}. * So, rebasing can only depend on the identity of {@link PlanningEntity planning entities} @@ -84,10 +75,10 @@ public interface Move { * The default implementation throws an {@link UnsupportedOperationException}, * making multithreaded solving impossible unless the move class implements this method. * - * @param rebaser Do not store this parameter in a field + * @param lookup Do not store this parameter in a field * @return New move that does the same change as this move on another solution instance */ - default Move rebase(Rebaser rebaser) { + default Move rebase(Lookup lookup) { throw new UnsupportedOperationException( "Move class (%s) doesn't implement the rebase() method, so multithreaded solving is impossible." .formatted(getClass())); diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/MutableSolutionView.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/MutableSolutionView.java index 42144cf5d18..44e2fa7b45a 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/move/MutableSolutionView.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/MutableSolutionView.java @@ -34,18 +34,6 @@ @NullMarked public interface MutableSolutionView extends SolutionView { - /** - * As defined by {@link #assignValueAndAdd(PlanningListVariableMetaModel, Object, Object, int)}. - * Will be removed right before this API is moved out of preview. - * - * @deprecated Use {@link #assignValueAndAdd(PlanningListVariableMetaModel, Object, Object, int)} instead. - */ - @Deprecated(forRemoval = true) - default void assignValue(PlanningListVariableMetaModel variableMetaModel, - Value_ value, Entity_ destinationEntity, int destinationIndex) { - assignValueAndAdd(variableMetaModel, value, destinationEntity, destinationIndex); - } - /** * Puts a given value at a particular index in a given entity's {@link PlanningListVariable planning list variable}. * Moves all values at or after the index to the right, much like {@link List#add(int, Object)}. diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/Rebaser.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/Rebaser.java deleted file mode 100644 index 0103ec3b0d0..00000000000 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/move/Rebaser.java +++ /dev/null @@ -1,43 +0,0 @@ -package ai.timefold.solver.core.preview.api.move; - -import ai.timefold.solver.core.api.domain.common.PlanningId; -import ai.timefold.solver.core.api.solver.change.ProblemChange; - -import org.jspecify.annotations.Nullable; - -/** - * Allows to transfer an entity or fact instance (often from another {@link Thread}) - * to another working solution. - *

- * This package and all of its contents are part of the Neighborhoods API, - * which is under development and is only offered as a preview feature. - * There are no guarantees for backward compatibility; - * any class, method, or field may change or be removed without prior notice, - * although we will strive to avoid this as much as possible. - *

- * We encourage you to try the API and give us feedback on your experience with it, - * before we finalize the API. - * Please direct your feedback to - * Timefold Solver GitHub - * or to Timefold Discord. - */ -public interface Rebaser { - - /** - * Translates an entity or fact instance (often from another {@link Thread}) - * to another working solution. - * Useful for 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 - */ - @Nullable T rebase(@Nullable T problemFactOrPlanningEntity); - -} diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ChangeMove.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ChangeMove.java index a439221baa7..3b450ceacd3 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ChangeMove.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ChangeMove.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.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.domain.variable.PlanningVariable; import ai.timefold.solver.core.impl.move.AbstractMove; import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningVariableMetaModel; import ai.timefold.solver.core.preview.api.move.MutableSolutionView; -import ai.timefold.solver.core.preview.api.move.Rebaser; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -51,9 +51,9 @@ public void execute(MutableSolutionView solutionView) { } @Override - public ChangeMove rebase(Rebaser rebaser) { - return new ChangeMove<>(variableMetaModel, Objects.requireNonNull(rebaser.rebase(entity)), - rebaser.rebase(toPlanningValue)); + public ChangeMove rebase(Lookup lookup) { + return new ChangeMove<>(variableMetaModel, lookup.lookUpWorkingObject(entity), + lookup.lookUpWorkingObject(toPlanningValue)); } @Override diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/CompositeMove.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/CompositeMove.java index e892a7b4145..cab094cb235 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/CompositeMove.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/CompositeMove.java @@ -6,10 +6,10 @@ 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.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; @@ -54,10 +54,10 @@ public void execute(MutableSolutionView solutionView) { @SuppressWarnings("unchecked") @Override - public Move rebase(Rebaser rebaser) { + public Move rebase(Lookup lookup) { Move[] 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 CompositeMove<>(rebasedMoves); } diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ListAssignMove.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ListAssignMove.java index 7e035012428..ed15925f27e 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ListAssignMove.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ListAssignMove.java @@ -4,11 +4,11 @@ import java.util.Objects; import java.util.SequencedCollection; +import ai.timefold.solver.core.api.domain.common.Lookup; import ai.timefold.solver.core.impl.move.AbstractMove; import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningListVariableMetaModel; 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; @@ -37,9 +37,9 @@ public void execute(MutableSolutionView mutableSolutionView) { } @Override - public Move rebase(Rebaser rebaser) { - return new ListAssignMove<>(variableMetaModel, Objects.requireNonNull(rebaser.rebase(planningValue)), - Objects.requireNonNull(rebaser.rebase(destinationEntity)), destinationIndex); + public Move rebase(Lookup lookup) { + return new ListAssignMove<>(variableMetaModel, lookup.lookUpWorkingObject(planningValue), + lookup.lookUpWorkingObject(destinationEntity), destinationIndex); } @Override diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ListChangeMove.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ListChangeMove.java index 1ec68f44633..fa39b10c418 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ListChangeMove.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ListChangeMove.java @@ -4,6 +4,7 @@ import java.util.Objects; import java.util.SequencedCollection; +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.PlanningListVariable; @@ -11,7 +12,6 @@ import ai.timefold.solver.core.impl.move.AbstractMove; import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningListVariableMetaModel; import ai.timefold.solver.core.preview.api.move.MutableSolutionView; -import ai.timefold.solver.core.preview.api.move.Rebaser; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -134,10 +134,9 @@ public void execute(MutableSolutionView solutionView) { } @Override - public ListChangeMove rebase(Rebaser rebaser) { - return new ListChangeMove<>(variableMetaModel, - Objects.requireNonNull(rebaser.rebase(sourceEntity)), sourceIndex, - Objects.requireNonNull(rebaser.rebase(destinationEntity)), destinationIndex); + public ListChangeMove rebase(Lookup lookup) { + return new ListChangeMove<>(variableMetaModel, lookup.lookUpWorkingObject(sourceEntity), sourceIndex, + lookup.lookUpWorkingObject(destinationEntity), destinationIndex); } @Override diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ListSwapMove.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ListSwapMove.java index f037792d780..76348d0fc48 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ListSwapMove.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ListSwapMove.java @@ -6,6 +6,7 @@ import java.util.Objects; import java.util.SequencedCollection; +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.PlanningListVariable; @@ -13,7 +14,6 @@ import ai.timefold.solver.core.impl.move.AbstractMove; import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningListVariableMetaModel; import ai.timefold.solver.core.preview.api.move.MutableSolutionView; -import ai.timefold.solver.core.preview.api.move.Rebaser; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -134,8 +134,9 @@ public void execute(MutableSolutionView solutionView) { } @Override - public ListSwapMove rebase(Rebaser rebaser) { - return new ListSwapMove<>(variableMetaModel, rebaser.rebase(leftEntity), leftIndex, rebaser.rebase(rightEntity), + public ListSwapMove rebase(Lookup lookup) { + return new ListSwapMove<>(variableMetaModel, lookup.lookUpWorkingObject(leftEntity), leftIndex, + lookup.lookUpWorkingObject(rightEntity), rightIndex); } diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ListUnassignMove.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ListUnassignMove.java index 6d6b83b73c5..15fc0132522 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ListUnassignMove.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/ListUnassignMove.java @@ -5,11 +5,11 @@ import java.util.Objects; import java.util.SequencedCollection; +import ai.timefold.solver.core.api.domain.common.Lookup; import ai.timefold.solver.core.impl.move.AbstractMove; import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningListVariableMetaModel; 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; import org.jspecify.annotations.Nullable; @@ -39,8 +39,8 @@ public void execute(MutableSolutionView solutionView) { } @Override - public Move rebase(Rebaser rebaser) { - return new ListUnassignMove<>(variableMetaModel, Objects.requireNonNull(rebaser.rebase(sourceEntity)), sourceIndex); + public Move rebase(Lookup lookup) { + return new ListUnassignMove<>(variableMetaModel, lookup.lookUpWorkingObject(sourceEntity), sourceIndex); } @Override diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/SwapMove.java b/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/SwapMove.java index afc1a0e748d..97963b1e7b5 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/SwapMove.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/move/builtin/SwapMove.java @@ -6,13 +6,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.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.domain.variable.PlanningVariable; import ai.timefold.solver.core.impl.move.AbstractMove; import ai.timefold.solver.core.preview.api.domain.metamodel.PlanningVariableMetaModel; import ai.timefold.solver.core.preview.api.move.MutableSolutionView; -import ai.timefold.solver.core.preview.api.move.Rebaser; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -80,8 +80,9 @@ public Entity_ getRightEntity() { } @Override - public SwapMove rebase(Rebaser rebaser) { - return new SwapMove<>(variableMetaModelList, rebaser.rebase(leftEntity), rebaser.rebase(rightEntity)); + public SwapMove rebase(Lookup lookup) { + return new SwapMove<>(variableMetaModelList, lookup.lookUpWorkingObject(leftEntity), + lookup.lookUpWorkingObject(rightEntity)); } @Override diff --git a/core/src/main/java/ai/timefold/solver/core/preview/api/neighborhood/stream/MoveStreamFactory.java b/core/src/main/java/ai/timefold/solver/core/preview/api/neighborhood/stream/MoveStreamFactory.java index 02a80a00c66..28f841e69ba 100644 --- a/core/src/main/java/ai/timefold/solver/core/preview/api/neighborhood/stream/MoveStreamFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/preview/api/neighborhood/stream/MoveStreamFactory.java @@ -115,25 +115,4 @@ public interface MoveStreamFactory { UniSamplingStream pick(UniEnumeratingStream enumeratingStream); - /** - * Enumerate all possible positions of a list variable to which a value can be assigned. - * This will eliminate all positions on {@link PlanningPin pinned entities}, - * as well as all {@link PlanningPinToIndex pinned indexes}. - * If the list variable {@link PlanningListVariable#allowsUnassignedValues() allows unassigned values}, - * the resulting stream will include a single instance of {@link UnassignedElement} instance. - *

- * Will be removed right before this API is moved out of preview. - * - * @param variableMetaModel the meta model of the list variable to enumerate - * @return enumerating stream with all assignable positions of a given list variable - * @see ElementPosition Read more about element positions. - * @deprecated Use {@link #forEachDestinationIncludingUnassigned(PlanningListVariableMetaModel)} instead, - * or see if {@link #forEachDestination(PlanningListVariableMetaModel)} - * or {@link #forEachAssignedValue(PlanningListVariableMetaModel)} - * fits your needs better. - */ - @Deprecated(forRemoval = true) - UniEnumeratingStream - forEachAssignablePosition(PlanningListVariableMetaModel variableMetaModel); - } diff --git a/core/src/test/java/ai/timefold/solver/core/api/solver/SolverManagerTest.java b/core/src/test/java/ai/timefold/solver/core/api/solver/SolverManagerTest.java index bf0efc614af..12e9633ab64 100644 --- a/core/src/test/java/ai/timefold/solver/core/api/solver/SolverManagerTest.java +++ b/core/src/test/java/ai/timefold/solver/core/api/solver/SolverManagerTest.java @@ -879,7 +879,7 @@ void skipAhead() throws ExecutionException, InterruptedException { var solution = context.getWorkingSolution(); var entity = solution.getEntityList().getFirst(); var move = Moves.change(variableMetaModel, entity, solution.getValueList().getFirst()); - context.execute(move); + context.executeAndCalculateScore(move); }, (PhaseCommandContext context) -> { var variableMetaModel = context.getSolutionMetaModel() .genuineEntity(TestdataEntity.class) @@ -887,7 +887,7 @@ void skipAhead() throws ExecutionException, InterruptedException { var solution = context.getWorkingSolution(); var entity = solution.getEntityList().get(1); var move = Moves.change(variableMetaModel, entity, solution.getValueList().get(1)); - context.execute(move); + context.executeAndCalculateScore(move); }, (PhaseCommandContext context) -> { var variableMetaModel = context.getSolutionMetaModel() .genuineEntity(TestdataEntity.class) @@ -895,7 +895,7 @@ void skipAhead() throws ExecutionException, InterruptedException { var solution = context.getWorkingSolution(); var entity = solution.getEntityList().get(2); var move = Moves.change(variableMetaModel, entity, solution.getValueList().get(2)); - context.execute(move); + context.executeAndCalculateScore(move); }, (PhaseCommandContext context) -> { // In the next best solution event, both e1 and e2 are definitely not null (but e3 might be). latch.countDown(); @@ -905,7 +905,7 @@ void skipAhead() throws ExecutionException, InterruptedException { var solution = context.getWorkingSolution(); var entity = solution.getEntityList().get(3); var move = Moves.change(variableMetaModel, entity, solution.getValueList().get(3)); - context.execute(move); + context.executeAndCalculateScore(move); })); try (var solverManager = createSolverManagerWithOneSolver(solverConfig)) { var bestSolutionCount = new AtomicInteger(); @@ -1034,7 +1034,7 @@ private SolverManager createSolverManagerTestableByDiffe var solution = context.getWorkingSolution(); var entity = solution.getEntityList().get(x); var move = Moves.change(variableMetaModel, entity, solution.getValueList().get(x)); - context.execute(move); + context.executeAndCalculateScore(move); })) .toArray(PhaseConfig[]::new)); return createDefaultSolverManager(solverConfig); diff --git a/core/src/test/java/ai/timefold/solver/core/config/solver/EnvironmentModeTest.java b/core/src/test/java/ai/timefold/solver/core/config/solver/EnvironmentModeTest.java index 6d2a37ae3c7..fd9d131c42f 100644 --- a/core/src/test/java/ai/timefold/solver/core/config/solver/EnvironmentModeTest.java +++ b/core/src/test/java/ai/timefold/solver/core/config/solver/EnvironmentModeTest.java @@ -282,7 +282,7 @@ public void changeWorkingSolution(PhaseCommandContext context) .basicVariable("value", TestdataValue.class); for (var entity : solution.getEntityList()) { var move = Moves.change(variable, entity, firstValue); - context.execute(move); + context.executeAndCalculateScore(move); } } } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/bavet/common/index/AbstractIndexerTest.java b/core/src/test/java/ai/timefold/solver/core/impl/bavet/common/index/AbstractIndexerTest.java index 0c017c6c84c..f40f56409f1 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/bavet/common/index/AbstractIndexerTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/bavet/common/index/AbstractIndexerTest.java @@ -36,7 +36,6 @@ protected static ListAssert assertForEach(Indexer indexer, Objec return assertThat(result); } - @Deprecated protected static List forEachToTuples(Indexer indexer, Object... objectProperties) { var properties = switch (objectProperties.length) { case 0 -> CompositeKey.none(); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhaseTest.java b/core/src/test/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhaseTest.java index 0335a8cf1cf..1fced7dc018 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhaseTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhaseTest.java @@ -35,8 +35,10 @@ import ai.timefold.solver.core.testdomain.TestdataSolution; import ai.timefold.solver.core.testdomain.TestdataValue; import ai.timefold.solver.core.testdomain.common.DummyHardSoftEasyScoreCalculator; +import ai.timefold.solver.core.testdomain.common.DummyValueComparator; +import ai.timefold.solver.core.testdomain.common.DummyValueComparatorFactory; import ai.timefold.solver.core.testdomain.common.TestdataObjectSortableDescendingComparator; -import ai.timefold.solver.core.testdomain.common.TestdataObjectSortableDescendingFactory; +import ai.timefold.solver.core.testdomain.common.TestdataObjectSortableDescendingComparatorFactory; import ai.timefold.solver.core.testdomain.list.TestDistanceMeter; import ai.timefold.solver.core.testdomain.list.TestdataListEntity; import ai.timefold.solver.core.testdomain.list.TestdataListSolution; @@ -118,7 +120,7 @@ void solveWithInitializedEntities() { solution = PlannerTestUtils.solve(solverConfig, solution); assertThat(solution).isNotNull(); - var solvedE1 = solution.getEntityList().get(0); + var solvedE1 = solution.getEntityList().getFirst(); assertCode("e1", solvedE1); assertThat(solvedE1.getValue()).isNotNull(); var solvedE2 = solution.getEntityList().get(1); @@ -168,7 +170,7 @@ void solveWithPinnedEntities() { solution = PlannerTestUtils.solve(solverConfig, solution); assertThat(solution).isNotNull(); - var solvedE1 = solution.getEntityList().get(0); + var solvedE1 = solution.getEntityList().getFirst(); assertCode("e1", solvedE1); assertThat(solvedE1.getValue()).isNotNull(); var solvedE2 = solution.getEntityList().get(1); @@ -262,8 +264,8 @@ void solveWithAllowsUnassignedBasicVariable() { assertSoftly(softly -> { softly.assertThat(bestSolution.getScore()) .isEqualTo(SimpleScore.of(-1)); // No value assigned twice, null once. - var firstEntity = bestSolution.getEntityList().get(0); - var firstValue = bestSolution.getValueList().get(0); + var firstEntity = bestSolution.getEntityList().getFirst(); + var firstValue = bestSolution.getValueList().getFirst(); softly.assertThat(firstEntity.getValue()) .isEqualTo(firstValue); var secondEntity = bestSolution.getEntityList().get(1); @@ -299,7 +301,7 @@ void solveWithAllowsUnassignedValuesListVariable() { assertSoftly(softly -> { softly.assertThat(bestSolution.getScore()) .isEqualTo(SimpleScore.of(-2)); // Length of the entity's value list. - var firstEntity = bestSolution.getEntityList().get(0); + var firstEntity = bestSolution.getEntityList().getFirst(); var firstValue = bestSolution.getValueList().get(0); var secondValue = bestSolution.getValueList().get(1); softly.assertThat(firstEntity.getValueList()) @@ -1010,7 +1012,7 @@ void solveListVariableQueueComparator(ConstructionHeuristicTestConfig phaseConfi .findFirst() .orElseThrow(IllegalArgumentException::new); assertThat(entity.getValueList()).hasSize(1); - assertThat(entity.getValueList().get(0).getComparatorValue()).isEqualTo(phaseConfig.expected[i]); + assertThat(entity.getValueList().getFirst().getComparatorValue()).isEqualTo(phaseConfig.expected[i]); } } } @@ -1036,7 +1038,7 @@ void solveListVariableQueueFactory(ConstructionHeuristicTestConfig phaseConfig) .findFirst() .orElseThrow(IllegalArgumentException::new); assertThat(entity.getValueList()).hasSize(1); - assertThat(entity.getValueList().get(0).getComparatorValue()).isEqualTo(phaseConfig.expected[i]); + assertThat(entity.getValueList().getFirst().getComparatorValue()).isEqualTo(phaseConfig.expected[i]); } } } @@ -1100,7 +1102,7 @@ void solveListVariableEntityRangeQueueComparator(ConstructionHeuristicTestConfig .findFirst() .orElseThrow(IllegalArgumentException::new); assertThat(entity.getValueList()).hasSize(1); - assertThat(entity.getValueList().get(0).getComparatorValue()).isEqualTo(phaseConfig.expected[i]); + assertThat(entity.getValueList().getFirst().getComparatorValue()).isEqualTo(phaseConfig.expected[i]); } } } @@ -1128,7 +1130,7 @@ void solveListVariableEntityRangeQueueFactory(ConstructionHeuristicTestConfig ph .findFirst() .orElseThrow(IllegalArgumentException::new); assertThat(entity.getValueList()).hasSize(1); - assertThat(entity.getValueList().get(0).getComparatorValue()).isEqualTo(phaseConfig.expected[i]); + assertThat(entity.getValueList().getFirst().getComparatorValue()).isEqualTo(phaseConfig.expected[i]); } } } @@ -1144,7 +1146,8 @@ private static List generateEntityFactorySortin .withId("sortedEntitySelector") .withSelectionOrder(SelectionOrder.SORTED) .withCacheType(SelectionCacheType.PHASE) - .withComparatorFactoryClass(TestdataObjectSortableDescendingFactory.class)) + .withComparatorFactoryClass( + TestdataObjectSortableDescendingComparatorFactory.class)) .withValueSelectorConfig( new ValueSelectorConfig() .withMimicSelectorRef("sortedValueSelector")) @@ -1161,7 +1164,8 @@ private static List generateEntityFactorySortin .withId("sortedEntitySelector") .withSelectionOrder(SelectionOrder.SORTED) .withCacheType(SelectionCacheType.PHASE) - .withComparatorFactoryClass(TestdataObjectSortableDescendingFactory.class)) + .withComparatorFactoryClass( + TestdataObjectSortableDescendingComparatorFactory.class)) .withValueSelectorConfig( new ValueSelectorConfig() .withMimicSelectorRef("sortedValueSelector")) @@ -1227,7 +1231,8 @@ void solveEntityFactorySorting(ConstructionHeuristicTestConfig phaseConfig) { .findFirst() .orElseThrow(IllegalArgumentException::new); assertThat(entity.getValueList()).hasSize(1); - assertThat(TestdataObjectSortableDescendingFactory.extractCode(entity.getValueList().get(0).getCode())) + assertThat( + TestdataObjectSortableDescendingComparator.extractCode(entity.getValueList().getFirst().getCode())) .isEqualTo(phaseConfig.expected[i]); } } @@ -1245,7 +1250,8 @@ private static List generateValueFactorySorting .withValueSelectorConfig(new ValueSelectorConfig() .withSelectionOrder(SelectionOrder.SORTED) .withCacheType(SelectionCacheType.PHASE) - .withComparatorFactoryClass(TestdataObjectSortableDescendingFactory.class)))), + .withComparatorFactoryClass( + TestdataObjectSortableDescendingComparatorFactory.class)))), new int[] { 2, 1, 0 }, // Only values are sorted in descending order false)); @@ -1259,7 +1265,8 @@ private static List generateValueFactorySorting .withValueSelectorConfig(new ValueSelectorConfig() .withSelectionOrder(SelectionOrder.SORTED) .withCacheType(SelectionCacheType.PHASE) - .withComparatorFactoryClass(TestdataObjectSortableDescendingFactory.class)))), + .withComparatorFactoryClass( + TestdataObjectSortableDescendingComparatorFactory.class)))), new int[] { 2, 1, 0 }, // Only values are sorted in descending order false)); @@ -1315,7 +1322,7 @@ void solveValueFactorySorting(ConstructionHeuristicTestConfig phaseConfig) { .findFirst() .orElseThrow(IllegalArgumentException::new); assertThat(entity.getValue()).isNotNull(); - assertThat(TestdataObjectSortableDescendingFactory.extractCode(entity.getValue().getCode())) + assertThat(TestdataObjectSortableDescendingComparator.extractCode(entity.getValue().getCode())) .isEqualTo(phaseConfig.expected[i]); } } @@ -1391,11 +1398,14 @@ void failConstructionHeuristicListMixedProperties() { var solution = new TestdataInvalidListSortableSolution(); assertThatCode(() -> PlannerTestUtils.solve(solverConfig, solution)) .hasMessageContaining( - "The entityClass (class ai.timefold.solver.core.testdomain.list.sort.invalid.TestdataInvalidListSortableEntity) property (valueList)") + "The entityClass (class %s) property (valueList)" + .formatted(TestdataInvalidListSortableEntity.class.getName())) .hasMessageContaining( - "cannot have a comparatorClass (ai.timefold.solver.core.testdomain.common.DummyValueComparator)") + "cannot have a comparatorClass (%s)" + .formatted(DummyValueComparator.class.getName())) .hasMessageContaining( - "comparatorFactoryClass (ai.timefold.solver.core.testdomain.common.DummyValueFactory) at the same time."); + "comparatorFactoryClass (%s) at the same time." + .formatted(DummyValueComparatorFactory.class.getName())); } @Test @@ -1414,7 +1424,7 @@ void failConstructionHeuristicBothNearbyAndSorting() { .withSelectionOrder(SelectionOrder.SORTED) .withCacheType(SelectionCacheType.PHASE) .withComparatorFactoryClass( - TestdataObjectSortableDescendingFactory.class) + TestdataObjectSortableDescendingComparatorFactory.class) .withNearbySelectionConfig(new NearbySelectionConfig() .withOriginValueSelectorConfig(new ValueSelectorConfig() .withMimicSelectorRef("sortedEntitySelector")) diff --git a/core/src/test/java/ai/timefold/solver/core/impl/domain/lookup/AbstractLookupTest.java b/core/src/test/java/ai/timefold/solver/core/impl/domain/common/AbstractLookupTest.java similarity index 69% rename from core/src/test/java/ai/timefold/solver/core/impl/domain/lookup/AbstractLookupTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/domain/common/AbstractLookupTest.java index f1def15bd9a..1f94fb058f6 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/domain/lookup/AbstractLookupTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/domain/common/AbstractLookupTest.java @@ -1,6 +1,5 @@ -package ai.timefold.solver.core.impl.domain.lookup; +package ai.timefold.solver.core.impl.domain.common; -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; @@ -9,7 +8,7 @@ abstract class AbstractLookupTest { private final LookUpStrategyType lookUpStrategyType; - protected LookUpManager lookUpManager; + protected LookupManager lookUpManager; protected AbstractLookupTest(LookUpStrategyType lookUpStrategyType) { this.lookUpStrategyType = lookUpStrategyType; @@ -17,13 +16,13 @@ protected AbstractLookupTest(LookUpStrategyType lookUpStrategyType) { @BeforeEach void setUpLookUpManager() { - lookUpManager = new LookUpManager(createLookupStrategyResolver(lookUpStrategyType)); + lookUpManager = new LookupManager(createLookupStrategyResolver(lookUpStrategyType)); } - protected LookUpStrategyResolver createLookupStrategyResolver(LookUpStrategyType lookUpStrategyType) { + protected LookupStrategyResolver createLookupStrategyResolver(LookUpStrategyType lookUpStrategyType) { DescriptorPolicy descriptorPolicy = new DescriptorPolicy(); descriptorPolicy.setMemberAccessorFactory(new MemberAccessorFactory()); descriptorPolicy.setDomainAccessType(DomainAccessType.FORCE_REFLECTION); - return new LookUpStrategyResolver(descriptorPolicy, lookUpStrategyType); + return new LookupStrategyResolver(descriptorPolicy, lookUpStrategyType); } } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/domain/lookup/LookUpManagerTest.java b/core/src/test/java/ai/timefold/solver/core/impl/domain/common/LookupManagerTest.java similarity index 94% rename from core/src/test/java/ai/timefold/solver/core/impl/domain/lookup/LookUpManagerTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/domain/common/LookupManagerTest.java index 31e47c8f22f..15d22e8f278 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/domain/lookup/LookUpManagerTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/domain/common/LookupManagerTest.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.domain.lookup; +package ai.timefold.solver.core.impl.domain.common; import static org.assertj.core.api.Assertions.assertThat; @@ -10,9 +10,9 @@ import org.junit.jupiter.api.Test; -class LookUpManagerTest extends AbstractLookupTest { +class LookupManagerTest extends AbstractLookupTest { - public LookUpManagerTest() { + public LookupManagerTest() { super(LookUpStrategyType.PLANNING_ID_OR_NONE); } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/domain/lookup/LookUpStrategyIdOrFailTest.java b/core/src/test/java/ai/timefold/solver/core/impl/domain/common/LookupStrategyIdOrFailTest.java similarity index 67% rename from core/src/test/java/ai/timefold/solver/core/impl/domain/lookup/LookUpStrategyIdOrFailTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/domain/common/LookupStrategyIdOrFailTest.java index 5376b9edce3..00ca100934a 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/domain/lookup/LookUpStrategyIdOrFailTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/domain/common/LookupStrategyIdOrFailTest.java @@ -1,8 +1,9 @@ -package ai.timefold.solver.core.impl.domain.lookup; +package ai.timefold.solver.core.impl.domain.common; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import ai.timefold.solver.core.api.domain.common.PlanningId; import ai.timefold.solver.core.testdomain.clone.lookup.TestdataObjectEnum; @@ -13,64 +14,70 @@ import org.junit.jupiter.api.Test; -class LookUpStrategyIdOrFailTest extends AbstractLookupTest { +class LookupStrategyIdOrFailTest extends AbstractLookupTest { - public LookUpStrategyIdOrFailTest() { + public LookupStrategyIdOrFailTest() { super(LookUpStrategyType.PLANNING_ID_OR_FAIL_FAST); } @Test void addRemoveWithIntegerId() { - TestdataObjectIntegerId object = new TestdataObjectIntegerId(0); + var object = new TestdataObjectIntegerId(0); lookUpManager.addWorkingObject(object); lookUpManager.removeWorkingObject(object); // The removed object cannot be looked up - assertThat(lookUpManager.lookUpWorkingObjectOrReturnNull(object)).isNull(); + assertThatThrownBy(() -> lookUpManager.lookUpWorkingObject(object)) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("externalObject") + .hasMessageContaining("no known workingObject"); } @Test void addRemoveWithPrimitiveIntId() { - TestdataObjectPrimitiveIntId object = new TestdataObjectPrimitiveIntId(0); + var object = new TestdataObjectPrimitiveIntId(0); lookUpManager.addWorkingObject(object); lookUpManager.removeWorkingObject(object); // The removed object cannot be looked up - assertThat(lookUpManager.lookUpWorkingObjectOrReturnNull(object)).isNull(); + assertThatThrownBy(() -> lookUpManager.lookUpWorkingObject(object)) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("externalObject") + .hasMessageContaining("no known workingObject"); } @Test void addRemoveEnum() { - TestdataObjectEnum object = TestdataObjectEnum.THIRD_VALUE; + var object = TestdataObjectEnum.THIRD_VALUE; lookUpManager.addWorkingObject(object); lookUpManager.removeWorkingObject(object); } @Test void addWithNullId() { - TestdataObjectIntegerId object = new TestdataObjectIntegerId(null); + var object = new TestdataObjectIntegerId(null); assertThatIllegalArgumentException().isThrownBy(() -> lookUpManager.addWorkingObject(object)); } @Test void removeWithNullId() { - TestdataObjectIntegerId object = new TestdataObjectIntegerId(null); + var object = new TestdataObjectIntegerId(null); assertThatIllegalArgumentException().isThrownBy(() -> lookUpManager.removeWorkingObject(object)); } @Test void addWithoutId() { - TestdataObjectNoId object = new TestdataObjectNoId(); + var object = new TestdataObjectNoId(); assertThatIllegalArgumentException().isThrownBy(() -> lookUpManager.addWorkingObject(object)); } @Test void removeWithoutId() { - TestdataObjectNoId object = new TestdataObjectNoId(); + var object = new TestdataObjectNoId(); assertThatIllegalArgumentException().isThrownBy(() -> lookUpManager.removeWorkingObject(object)); } @Test void addSameIdTwice() { - TestdataObjectIntegerId object = new TestdataObjectIntegerId(2); + var object = new TestdataObjectIntegerId(2); lookUpManager.addWorkingObject(object); assertThatIllegalStateException() .isThrownBy(() -> lookUpManager.addWorkingObject(new TestdataObjectIntegerId(2))) @@ -80,7 +87,7 @@ void addSameIdTwice() { @Test void removeWithoutAdding() { - TestdataObjectIntegerId object = new TestdataObjectIntegerId(0); + var object = new TestdataObjectIntegerId(0); assertThatIllegalStateException() .isThrownBy(() -> lookUpManager.removeWorkingObject(object)) .withMessageContaining("differ"); @@ -88,14 +95,14 @@ void removeWithoutAdding() { @Test void lookUpWithId() { - TestdataObjectIntegerId object = new TestdataObjectIntegerId(1); + var object = new TestdataObjectIntegerId(1); lookUpManager.addWorkingObject(object); assertThat(lookUpManager.lookUpWorkingObject(new TestdataObjectIntegerId(1))).isSameAs(object); } @Test void lookUpWithoutId() { - TestdataObjectNoId object = new TestdataObjectNoId(); + var object = new TestdataObjectNoId(); assertThatIllegalArgumentException() .isThrownBy(() -> lookUpManager.lookUpWorkingObject(object)) .withMessageContaining("does not have a @" + PlanningId.class.getSimpleName()); @@ -103,13 +110,16 @@ void lookUpWithoutId() { @Test void lookUpWithoutAdding() { - TestdataObjectIntegerId object = new TestdataObjectIntegerId(0); - assertThat(lookUpManager.lookUpWorkingObjectOrReturnNull(object)).isNull(); + var object = new TestdataObjectIntegerId(0); + assertThatThrownBy(() -> lookUpManager.lookUpWorkingObject(object)) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("externalObject") + .hasMessageContaining("no known workingObject"); } @Test void addWithTwoIds() { - TestdataObjectMultipleIds object = new TestdataObjectMultipleIds(); + var object = new TestdataObjectMultipleIds(); assertThatIllegalArgumentException() .isThrownBy(() -> lookUpManager.addWorkingObject(object)) .withMessageContaining("3 members") @@ -118,7 +128,7 @@ void addWithTwoIds() { @Test void removeWithTwoIds() { - TestdataObjectMultipleIds object = new TestdataObjectMultipleIds(); + var object = new TestdataObjectMultipleIds(); assertThatIllegalArgumentException() .isThrownBy(() -> lookUpManager.removeWorkingObject(object)) .withMessageContaining("3 members") diff --git a/core/src/test/java/ai/timefold/solver/core/impl/domain/lookup/LookUpStrategyIdOrNoneTest.java b/core/src/test/java/ai/timefold/solver/core/impl/domain/common/LookupStrategyIdOrNoneTest.java similarity index 82% rename from core/src/test/java/ai/timefold/solver/core/impl/domain/lookup/LookUpStrategyIdOrNoneTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/domain/common/LookupStrategyIdOrNoneTest.java index 5d4ebf027ab..4a1ac34ccf5 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/domain/lookup/LookUpStrategyIdOrNoneTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/domain/common/LookupStrategyIdOrNoneTest.java @@ -1,8 +1,9 @@ -package ai.timefold.solver.core.impl.domain.lookup; +package ai.timefold.solver.core.impl.domain.common; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import ai.timefold.solver.core.api.domain.common.PlanningId; import ai.timefold.solver.core.testdomain.clone.lookup.TestdataObjectIntegerId; @@ -13,9 +14,9 @@ import org.junit.jupiter.api.Test; -class LookUpStrategyIdOrNoneTest extends AbstractLookupTest { +class LookupStrategyIdOrNoneTest extends AbstractLookupTest { - public LookUpStrategyIdOrNoneTest() { + public LookupStrategyIdOrNoneTest() { super(LookUpStrategyType.PLANNING_ID_OR_NONE); } @@ -25,7 +26,10 @@ void addRemoveWithIntegerId() { lookUpManager.addWorkingObject(object); lookUpManager.removeWorkingObject(object); // The removed object cannot be looked up - assertThat(lookUpManager.lookUpWorkingObjectOrReturnNull(object)).isNull(); + assertThatThrownBy(() -> lookUpManager.lookUpWorkingObject(object)) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("externalObject") + .hasMessageContaining("no known workingObject"); } @Test @@ -34,7 +38,10 @@ void addRemoveWithPrimitiveIntId() { lookUpManager.addWorkingObject(object); lookUpManager.removeWorkingObject(object); // The removed object cannot be looked up - assertThat(lookUpManager.lookUpWorkingObjectOrReturnNull(object)).isNull(); + assertThatThrownBy(() -> lookUpManager.lookUpWorkingObject(object)) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("externalObject") + .hasMessageContaining("no known workingObject"); } @Test @@ -104,7 +111,10 @@ void lookUpWithoutId() { @Test void lookUpWithoutAdding() { TestdataObjectIntegerId object = new TestdataObjectIntegerId(0); - assertThat(lookUpManager.lookUpWorkingObjectOrReturnNull(object)).isNull(); + assertThatThrownBy(() -> lookUpManager.lookUpWorkingObject(object)) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("externalObject") + .hasMessageContaining("no known workingObject"); } @Test diff --git a/core/src/test/java/ai/timefold/solver/core/impl/domain/lookup/LookUpStrategyImmutableTest.java b/core/src/test/java/ai/timefold/solver/core/impl/domain/common/LookupStrategyImmutableTest.java similarity index 95% rename from core/src/test/java/ai/timefold/solver/core/impl/domain/lookup/LookUpStrategyImmutableTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/domain/common/LookupStrategyImmutableTest.java index 46e12b0c4e1..a05b3745c44 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/domain/lookup/LookUpStrategyImmutableTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/domain/common/LookupStrategyImmutableTest.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.domain.lookup; +package ai.timefold.solver.core.impl.domain.common; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.params.provider.Arguments.arguments; @@ -27,9 +27,9 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -class LookUpStrategyImmutableTest extends AbstractLookupTest { +class LookupStrategyImmutableTest extends AbstractLookupTest { - public LookUpStrategyImmutableTest() { + public LookupStrategyImmutableTest() { super(LookUpStrategyType.PLANNING_ID_OR_NONE); } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/domain/lookup/LookUpStrategyNoneTest.java b/core/src/test/java/ai/timefold/solver/core/impl/domain/common/LookupStrategyNoneTest.java similarity index 96% rename from core/src/test/java/ai/timefold/solver/core/impl/domain/lookup/LookUpStrategyNoneTest.java rename to core/src/test/java/ai/timefold/solver/core/impl/domain/common/LookupStrategyNoneTest.java index 6841ddf13f5..aa2c68adf53 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/domain/lookup/LookUpStrategyNoneTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/domain/common/LookupStrategyNoneTest.java @@ -1,4 +1,4 @@ -package ai.timefold.solver.core.impl.domain.lookup; +package ai.timefold.solver.core.impl.domain.common; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; @@ -9,9 +9,9 @@ import org.junit.jupiter.api.Test; -class LookUpStrategyNoneTest extends AbstractLookupTest { +class LookupStrategyNoneTest extends AbstractLookupTest { - public LookUpStrategyNoneTest() { + public LookupStrategyNoneTest() { super(LookUpStrategyType.NONE); } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java index 65a3ccdba80..a7db2b96546 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactoryTest.java @@ -5,15 +5,12 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.Mockito.mock; -import java.util.Comparator; - import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionOrder; import ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig; import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy; import ai.timefold.solver.core.impl.heuristic.selector.SelectorTestUtils; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.ProbabilityEntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.ShufflingEntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.SortingEntitySelector; @@ -21,6 +18,8 @@ import ai.timefold.solver.core.impl.solver.ClassInstanceCache; import ai.timefold.solver.core.testdomain.TestdataEntity; import ai.timefold.solver.core.testdomain.TestdataSolution; +import ai.timefold.solver.core.testdomain.common.DummyEntityComparator; +import ai.timefold.solver.core.testdomain.common.DummyEntityComparatorFactory; import org.junit.jupiter.api.Test; @@ -152,7 +151,7 @@ void applySorting_withComparatorClass() { @Test void applySorting_withComparatorFactoryClass() { EntitySelectorConfig entitySelectorConfig = new EntitySelectorConfig() - .withComparatorFactoryClass(DummySelectionComparatorFactory.class); + .withComparatorFactoryClass(DummyEntityComparatorFactory.class); applySorting(entitySelectorConfig); } @@ -203,19 +202,4 @@ public double createProbabilityWeight(ScoreDirector scoreDirec } } - public static class DummySelectionComparatorFactory - implements SelectionSorterWeightFactory { - - @Override - public Comparable createSorterWeight(TestdataSolution solution, TestdataEntity selection) { - return 0; - } - } - - public static class DummyEntityComparator implements Comparator { - @Override - public int compare(TestdataEntity testdataEntity, TestdataEntity testdataEntity2) { - return 0; - } - } } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/MoveSelectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/MoveSelectorFactoryTest.java index 4284247675d..dda6be6889a 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/MoveSelectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/MoveSelectorFactoryTest.java @@ -23,7 +23,7 @@ import ai.timefold.solver.core.impl.score.director.ScoreDirector; import ai.timefold.solver.core.impl.score.director.VariableDescriptorAwareScoreDirector; import ai.timefold.solver.core.testdomain.TestdataSolution; -import ai.timefold.solver.core.testdomain.common.DummyValueFactory; +import ai.timefold.solver.core.testdomain.common.DummyValueComparatorFactory; import org.jspecify.annotations.NonNull; import org.junit.jupiter.api.Test; @@ -218,7 +218,7 @@ void applySorting_withComparatorFactoryClass() { var baseMoveSelector = SelectorTestUtils. mockMoveSelector(); var moveSelectorConfig = new DummyMoveSelectorConfig(); moveSelectorConfig.setSorterOrder(SelectionSorterOrder.ASCENDING); - moveSelectorConfig.setComparatorFactoryClass(DummyValueFactory.class); + moveSelectorConfig.setComparatorFactoryClass(DummyValueComparatorFactory.class); var moveSelectorFactory = new DummyMoveSelectorFactory(moveSelectorConfig, baseMoveSelector); var sortingMoveSelector = diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SortingMoveSelectorTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SortingMoveSelectorTest.java index 01b8a00f57c..b1336e84a63 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SortingMoveSelectorTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/decorator/SortingMoveSelectorTest.java @@ -15,6 +15,7 @@ import java.util.List; import java.util.function.Consumer; +import ai.timefold.solver.core.api.domain.common.ComparatorFactory; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionOrder; import ai.timefold.solver.core.config.heuristic.selector.common.decorator.SelectionSorterOrder; @@ -23,7 +24,6 @@ import ai.timefold.solver.core.impl.heuristic.move.SelectorBasedDummyMove; import ai.timefold.solver.core.impl.heuristic.selector.SelectorTestUtils; import ai.timefold.solver.core.impl.heuristic.selector.common.CodeAssertableSorter; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.move.AbstractMoveSelectorFactory; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; import ai.timefold.solver.core.impl.phase.scope.AbstractPhaseScope; @@ -62,12 +62,6 @@ void cacheTypeJustInTime() { private static List generateConfiguration() { return List.of( - new DummySorterMoveSelectorConfig() - .withSorterOrder(SelectionSorterOrder.ASCENDING) - .withComparatorFactoryClass(TestCodeAssertableComparatorFactory.class), - new DummySorterMoveSelectorConfig() - .withSorterOrder(SelectionSorterOrder.ASCENDING) - .withComparatorClass(TestCodeAssertableComparator.class), new DummySorterMoveSelectorConfig() .withSorterOrder(SelectionSorterOrder.ASCENDING) .withComparatorFactoryClass(TestCodeAssertableComparatorFactory.class), @@ -201,11 +195,11 @@ protected MoveSelector buildBaseMoveSelector(HeuristicConfigPo } } - public static class TestCodeAssertableComparatorFactory implements SelectionSorterWeightFactory { + public static class TestCodeAssertableComparatorFactory implements ComparatorFactory { @Override - public Comparable createSorterWeight(Object o, CodeAssertable selection) { - return selection.getCode(); + public Comparator createComparator(Object solution) { + return Comparator.comparing(CodeAssertable::getCode); } } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java index 7261b00e8e1..e7dcff8a575 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactoryTest.java @@ -23,7 +23,6 @@ import ai.timefold.solver.core.impl.heuristic.selector.SelectorTestUtils; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionFilter; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionProbabilityWeightFactory; -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; import ai.timefold.solver.core.impl.heuristic.selector.value.decorator.AssignedListValueSelector; import ai.timefold.solver.core.impl.heuristic.selector.value.decorator.FilteringValueSelector; import ai.timefold.solver.core.impl.heuristic.selector.value.decorator.IterableFromEntityPropertyValueSelector; @@ -38,6 +37,7 @@ import ai.timefold.solver.core.testdomain.TestdataEntity; import ai.timefold.solver.core.testdomain.TestdataSolution; import ai.timefold.solver.core.testdomain.TestdataValue; +import ai.timefold.solver.core.testdomain.common.DummyValueComparatorFactory; import ai.timefold.solver.core.testdomain.list.TestdataListEntity; import ai.timefold.solver.core.testdomain.list.TestdataListSolution; @@ -223,7 +223,7 @@ void applySorting_withComparatorClass() { void applySorting_withComparatorFactoryClass() { ValueSelectorConfig valueSelectorConfig = new ValueSelectorConfig() .withCacheType(SelectionCacheType.PHASE) - .withComparatorFactoryClass(DummySelectionComparatorFactory.class); + .withComparatorFactoryClass(DummyValueComparatorFactory.class); applySorting(valueSelectorConfig, true); applySorting(valueSelectorConfig, false); } @@ -319,15 +319,6 @@ public double createProbabilityWeight(ScoreDirector scoreDirec } } - public static class DummySelectionComparatorFactory - implements SelectionSorterWeightFactory { - - @Override - public Comparable createSorterWeight(TestdataSolution solution, TestdataValue selection) { - return 0; - } - } - public static class DummyValueComparator implements Comparator { @Override public int compare(TestdataValue testdataValue, TestdataValue testdataValue2) { diff --git a/core/src/test/java/ai/timefold/solver/core/impl/move/MoveDirectorTest.java b/core/src/test/java/ai/timefold/solver/core/impl/move/MoveDirectorTest.java index e3501971e8f..f20aa593301 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/move/MoveDirectorTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/move/MoveDirectorTest.java @@ -295,6 +295,430 @@ void assignValueAndSetOnEmptyList() { assertThat(entity.getValueList()).isEmpty(); } + @Test + void assignValueAndAddToEmptyList() { + var solutionDescriptor = TestdataListSolution.buildSolutionDescriptor(); + var solutionMetaModel = solutionDescriptor.getMetaModel(); + var variableMetaModel = solutionMetaModel.genuineEntity(TestdataListEntity.class) + .listVariable("valueList", TestdataListValue.class); + + var unassignedValue = new TestdataListValue("unassignedValue"); + var entity = new TestdataListEntity("A"); + var solution = new TestdataListSolution(); + solution.setEntityList(List.of(entity)); + solution.setValueList(List.of(unassignedValue)); + SolutionManager.updateShadowVariables(solution); + + var f = new BavetConstraintStreamScoreDirectorFactory<>(solutionDescriptor, + constraintFactory -> new Constraint[] { constraintFactory.forEach(TestdataListEntity.class) + .penalize(SimpleScore.ONE).asConstraint("Dummy constraint") }, + EnvironmentMode.FULL_ASSERT); + var scoreDirector = new BavetConstraintStreamScoreDirector.Builder<>(f).build(); + scoreDirector.setWorkingSolution(solution); + scoreDirector.calculateScore(); + + // Assign unassignedValue to empty list at index 0. + var moveDirector = new MoveDirector<>(scoreDirector).ephemeral(); + moveDirector.assignValueAndAdd(variableMetaModel, unassignedValue, entity, 0); + assertSoftly(softly -> { + softly.assertThat(entity.getValueList()).containsExactly(unassignedValue); + softly.assertThat(moveDirector.getPositionOf(variableMetaModel, unassignedValue)) + .isEqualTo(ElementPosition.of(entity, 0)); + }); + + // Undo it. + moveDirector.close(); + assertSoftly(softly -> { + softly.assertThat(entity.getValueList()).isEmpty(); + softly.assertThat(moveDirector.getPositionOf(variableMetaModel, unassignedValue)) + .isInstanceOf(UnassignedElement.class); + }); + } + + @Test + void assignValueAndAddAtStart() { + var solutionDescriptor = TestdataListSolution.buildSolutionDescriptor(); + var solutionMetaModel = solutionDescriptor.getMetaModel(); + var variableMetaModel = solutionMetaModel.genuineEntity(TestdataListEntity.class) + .listVariable("valueList", TestdataListValue.class); + + var value1 = new TestdataListValue("value1"); + var value2 = new TestdataListValue("value2"); + var unassignedValue = new TestdataListValue("unassignedValue"); + var entity = new TestdataListEntity("A", value1, value2); + var solution = new TestdataListSolution(); + solution.setEntityList(List.of(entity)); + solution.setValueList(List.of(value1, value2, unassignedValue)); + SolutionManager.updateShadowVariables(solution); + + var f = new BavetConstraintStreamScoreDirectorFactory<>(solutionDescriptor, + constraintFactory -> new Constraint[] { constraintFactory.forEach(TestdataListEntity.class) + .penalize(SimpleScore.ONE).asConstraint("Dummy constraint") }, + EnvironmentMode.FULL_ASSERT); + var scoreDirector = new BavetConstraintStreamScoreDirector.Builder<>(f).build(); + scoreDirector.setWorkingSolution(solution); + scoreDirector.calculateScore(); + + // Assign unassignedValue to index 0, shifting value1 and value2 to the right. + var moveDirector = new MoveDirector<>(scoreDirector).ephemeral(); + moveDirector.assignValueAndAdd(variableMetaModel, unassignedValue, entity, 0); + assertSoftly(softly -> { + softly.assertThat(entity.getValueList()).containsExactly(unassignedValue, value1, value2); + softly.assertThat(moveDirector.getPositionOf(variableMetaModel, unassignedValue)) + .isEqualTo(ElementPosition.of(entity, 0)); + softly.assertThat(moveDirector.getPositionOf(variableMetaModel, value1)) + .isEqualTo(ElementPosition.of(entity, 1)); + softly.assertThat(moveDirector.getPositionOf(variableMetaModel, value2)) + .isEqualTo(ElementPosition.of(entity, 2)); + }); + + // Undo it. + moveDirector.close(); + assertSoftly(softly -> { + softly.assertThat(entity.getValueList()).containsExactly(value1, value2); + softly.assertThat(moveDirector.getPositionOf(variableMetaModel, value1)) + .isEqualTo(ElementPosition.of(entity, 0)); + softly.assertThat(moveDirector.getPositionOf(variableMetaModel, value2)) + .isEqualTo(ElementPosition.of(entity, 1)); + softly.assertThat(moveDirector.getPositionOf(variableMetaModel, unassignedValue)) + .isInstanceOf(UnassignedElement.class); + }); + } + + @Test + void assignValueAndAddInMiddle() { + var solutionDescriptor = TestdataListSolution.buildSolutionDescriptor(); + var solutionMetaModel = solutionDescriptor.getMetaModel(); + var variableMetaModel = solutionMetaModel.genuineEntity(TestdataListEntity.class) + .listVariable("valueList", TestdataListValue.class); + + var value1 = new TestdataListValue("value1"); + var value2 = new TestdataListValue("value2"); + var value3 = new TestdataListValue("value3"); + var unassignedValue = new TestdataListValue("unassignedValue"); + var entity = new TestdataListEntity("A", value1, value2, value3); + var solution = new TestdataListSolution(); + solution.setEntityList(List.of(entity)); + solution.setValueList(List.of(value1, value2, value3, unassignedValue)); + SolutionManager.updateShadowVariables(solution); + + var f = new BavetConstraintStreamScoreDirectorFactory<>(solutionDescriptor, + constraintFactory -> new Constraint[] { constraintFactory.forEach(TestdataListEntity.class) + .penalize(SimpleScore.ONE).asConstraint("Dummy constraint") }, + EnvironmentMode.FULL_ASSERT); + var scoreDirector = new BavetConstraintStreamScoreDirector.Builder<>(f).build(); + scoreDirector.setWorkingSolution(solution); + scoreDirector.calculateScore(); + + // Assign unassignedValue to index 1, shifting value2 and value3 to the right. + var moveDirector = new MoveDirector<>(scoreDirector).ephemeral(); + moveDirector.assignValueAndAdd(variableMetaModel, unassignedValue, entity, 1); + assertSoftly(softly -> { + softly.assertThat(entity.getValueList()).containsExactly(value1, unassignedValue, value2, value3); + softly.assertThat(moveDirector.getPositionOf(variableMetaModel, unassignedValue)) + .isEqualTo(ElementPosition.of(entity, 1)); + softly.assertThat(moveDirector.getPositionOf(variableMetaModel, value2)) + .isEqualTo(ElementPosition.of(entity, 2)); + softly.assertThat(moveDirector.getPositionOf(variableMetaModel, value3)) + .isEqualTo(ElementPosition.of(entity, 3)); + }); + + // Undo it. + moveDirector.close(); + assertSoftly(softly -> { + softly.assertThat(entity.getValueList()).containsExactly(value1, value2, value3); + softly.assertThat(moveDirector.getPositionOf(variableMetaModel, value1)) + .isEqualTo(ElementPosition.of(entity, 0)); + softly.assertThat(moveDirector.getPositionOf(variableMetaModel, value2)) + .isEqualTo(ElementPosition.of(entity, 1)); + softly.assertThat(moveDirector.getPositionOf(variableMetaModel, value3)) + .isEqualTo(ElementPosition.of(entity, 2)); + softly.assertThat(moveDirector.getPositionOf(variableMetaModel, unassignedValue)) + .isInstanceOf(UnassignedElement.class); + }); + } + + @Test + void assignValueAndAddAtEnd() { + var solutionDescriptor = TestdataListSolution.buildSolutionDescriptor(); + var solutionMetaModel = solutionDescriptor.getMetaModel(); + var variableMetaModel = solutionMetaModel.genuineEntity(TestdataListEntity.class) + .listVariable("valueList", TestdataListValue.class); + + var value1 = new TestdataListValue("value1"); + var value2 = new TestdataListValue("value2"); + var unassignedValue = new TestdataListValue("unassignedValue"); + var entity = new TestdataListEntity("A", value1, value2); + var solution = new TestdataListSolution(); + solution.setEntityList(List.of(entity)); + solution.setValueList(List.of(value1, value2, unassignedValue)); + SolutionManager.updateShadowVariables(solution); + + var f = new BavetConstraintStreamScoreDirectorFactory<>(solutionDescriptor, + constraintFactory -> new Constraint[] { constraintFactory.forEach(TestdataListEntity.class) + .penalize(SimpleScore.ONE).asConstraint("Dummy constraint") }, + EnvironmentMode.FULL_ASSERT); + var scoreDirector = new BavetConstraintStreamScoreDirector.Builder<>(f).build(); + scoreDirector.setWorkingSolution(solution); + scoreDirector.calculateScore(); + + // Assign unassignedValue to index 2 (end of the list). + var moveDirector = new MoveDirector<>(scoreDirector).ephemeral(); + moveDirector.assignValueAndAdd(variableMetaModel, unassignedValue, entity, 2); + assertSoftly(softly -> { + softly.assertThat(entity.getValueList()).containsExactly(value1, value2, unassignedValue); + softly.assertThat(moveDirector.getPositionOf(variableMetaModel, unassignedValue)) + .isEqualTo(ElementPosition.of(entity, 2)); + softly.assertThat(moveDirector.getPositionOf(variableMetaModel, value1)) + .isEqualTo(ElementPosition.of(entity, 0)); + softly.assertThat(moveDirector.getPositionOf(variableMetaModel, value2)) + .isEqualTo(ElementPosition.of(entity, 1)); + }); + + // Undo it. + moveDirector.close(); + assertSoftly(softly -> { + softly.assertThat(entity.getValueList()).containsExactly(value1, value2); + softly.assertThat(moveDirector.getPositionOf(variableMetaModel, unassignedValue)) + .isInstanceOf(UnassignedElement.class); + }); + } + + @Test + void assignValueAndAddFailsWhenValueAlreadyAssigned() { + var solutionDescriptor = TestdataListSolution.buildSolutionDescriptor(); + var solutionMetaModel = solutionDescriptor.getMetaModel(); + var variableMetaModel = solutionMetaModel.genuineEntity(TestdataListEntity.class) + .listVariable("valueList", TestdataListValue.class); + + var value1 = new TestdataListValue("value1"); + var value2 = new TestdataListValue("value2"); + var entity = new TestdataListEntity("A", value1, value2); + var solution = new TestdataListSolution(); + solution.setEntityList(List.of(entity)); + solution.setValueList(List.of(value1, value2)); + SolutionManager.updateShadowVariables(solution); + + var f = new BavetConstraintStreamScoreDirectorFactory<>(solutionDescriptor, + constraintFactory -> new Constraint[] { constraintFactory.forEach(TestdataListEntity.class) + .penalize(SimpleScore.ONE).asConstraint("Dummy constraint") }, + EnvironmentMode.FULL_ASSERT); + var scoreDirector = new BavetConstraintStreamScoreDirector.Builder<>(f).build(); + scoreDirector.setWorkingSolution(solution); + scoreDirector.calculateScore(); + + var moveDirector = new MoveDirector<>(scoreDirector).ephemeral(); + // Try to assign value1 which is already assigned - should fail. + Assertions.assertThatThrownBy(() -> moveDirector.assignValueAndAdd(variableMetaModel, value1, entity, 1)) + .isInstanceOf(IllegalStateException.class); + moveDirector.close(); + } + + @Test + void assignValuesAndAddToEmptyList() { + var solutionDescriptor = TestdataListSolution.buildSolutionDescriptor(); + var solutionMetaModel = solutionDescriptor.getMetaModel(); + var variableMetaModel = solutionMetaModel.genuineEntity(TestdataListEntity.class) + .listVariable("valueList", TestdataListValue.class); + + var unassigned1 = new TestdataListValue("unassigned1"); + var unassigned2 = new TestdataListValue("unassigned2"); + var entity = new TestdataListEntity("A"); + var solution = new TestdataListSolution(); + solution.setEntityList(List.of(entity)); + solution.setValueList(List.of(unassigned1, unassigned2)); + SolutionManager.updateShadowVariables(solution); + + var f = new BavetConstraintStreamScoreDirectorFactory<>(solutionDescriptor, + constraintFactory -> new Constraint[] { constraintFactory.forEach(TestdataListEntity.class) + .penalize(SimpleScore.ONE).asConstraint("Dummy constraint") }, + EnvironmentMode.FULL_ASSERT); + var scoreDirector = new BavetConstraintStreamScoreDirector.Builder<>(f).build(); + scoreDirector.setWorkingSolution(solution); + scoreDirector.calculateScore(); + + var moveDirector = new MoveDirector<>(scoreDirector).ephemeral(); + moveDirector.assignValuesAndAdd(variableMetaModel, List.of(unassigned1, unassigned2), entity, 0); + assertThat(entity.getValueList()).containsExactly(unassigned1, unassigned2); + + // Undo it. + moveDirector.close(); + assertSoftly(softly -> { + softly.assertThat(entity.getValueList()).isEmpty(); + softly.assertThat(moveDirector.getPositionOf(variableMetaModel, unassigned1)) + .isInstanceOf(UnassignedElement.class); + softly.assertThat(moveDirector.getPositionOf(variableMetaModel, unassigned2)) + .isInstanceOf(UnassignedElement.class); + }); + } + + @Test + void assignValuesAndAddAtStart() { + var solutionDescriptor = TestdataListSolution.buildSolutionDescriptor(); + var solutionMetaModel = solutionDescriptor.getMetaModel(); + var variableMetaModel = solutionMetaModel.genuineEntity(TestdataListEntity.class) + .listVariable("valueList", TestdataListValue.class); + + var value1 = new TestdataListValue("value1"); + var value2 = new TestdataListValue("value2"); + var unassigned1 = new TestdataListValue("unassigned1"); + var unassigned2 = new TestdataListValue("unassigned2"); + var entity = new TestdataListEntity("A", value1, value2); + var solution = new TestdataListSolution(); + solution.setEntityList(List.of(entity)); + solution.setValueList(List.of(value1, value2, unassigned1, unassigned2)); + SolutionManager.updateShadowVariables(solution); + + var f = new BavetConstraintStreamScoreDirectorFactory<>(solutionDescriptor, + constraintFactory -> new Constraint[] { constraintFactory.forEach(TestdataListEntity.class) + .penalize(SimpleScore.ONE).asConstraint("Dummy constraint") }, + EnvironmentMode.FULL_ASSERT); + var scoreDirector = new BavetConstraintStreamScoreDirector.Builder<>(f).build(); + scoreDirector.setWorkingSolution(solution); + scoreDirector.calculateScore(); + + // Assign two unassigned values at index 0, shifting value1 and value2 to the right. + var moveDirector = new MoveDirector<>(scoreDirector).ephemeral(); + moveDirector.assignValuesAndAdd(variableMetaModel, List.of(unassigned1, unassigned2), entity, 0); + assertThat(entity.getValueList()).containsExactly(unassigned1, unassigned2, value1, value2); + + // Undo it. + moveDirector.close(); + assertSoftly(softly -> { + softly.assertThat(entity.getValueList()).containsExactly(value1, value2); + softly.assertThat(moveDirector.getPositionOf(variableMetaModel, value1)) + .isEqualTo(ElementPosition.of(entity, 0)); + softly.assertThat(moveDirector.getPositionOf(variableMetaModel, value2)) + .isEqualTo(ElementPosition.of(entity, 1)); + softly.assertThat(moveDirector.getPositionOf(variableMetaModel, unassigned1)) + .isInstanceOf(UnassignedElement.class); + softly.assertThat(moveDirector.getPositionOf(variableMetaModel, unassigned2)) + .isInstanceOf(UnassignedElement.class); + }); + } + + @Test + void assignValuesAndAddInMiddle() { + var solutionDescriptor = TestdataListSolution.buildSolutionDescriptor(); + var solutionMetaModel = solutionDescriptor.getMetaModel(); + var variableMetaModel = solutionMetaModel.genuineEntity(TestdataListEntity.class) + .listVariable("valueList", TestdataListValue.class); + + var value1 = new TestdataListValue("value1"); + var value2 = new TestdataListValue("value2"); + var value3 = new TestdataListValue("value3"); + var unassigned1 = new TestdataListValue("unassigned1"); + var unassigned2 = new TestdataListValue("unassigned2"); + var entity = new TestdataListEntity("A", value1, value2, value3); + var solution = new TestdataListSolution(); + solution.setEntityList(List.of(entity)); + solution.setValueList(List.of(value1, value2, value3, unassigned1, unassigned2)); + SolutionManager.updateShadowVariables(solution); + + var f = new BavetConstraintStreamScoreDirectorFactory<>(solutionDescriptor, + constraintFactory -> new Constraint[] { constraintFactory.forEach(TestdataListEntity.class) + .penalize(SimpleScore.ONE).asConstraint("Dummy constraint") }, + EnvironmentMode.FULL_ASSERT); + var scoreDirector = new BavetConstraintStreamScoreDirector.Builder<>(f).build(); + scoreDirector.setWorkingSolution(solution); + scoreDirector.calculateScore(); + + // Assign two unassigned values at index 1, shifting value2 and value3 to the right. + var moveDirector = new MoveDirector<>(scoreDirector).ephemeral(); + moveDirector.assignValuesAndAdd(variableMetaModel, List.of(unassigned1, unassigned2), entity, 1); + assertThat(entity.getValueList()) + .containsExactly(value1, unassigned1, unassigned2, value2, value3); + + // Undo it. + moveDirector.close(); + assertSoftly(softly -> { + softly.assertThat(entity.getValueList()).containsExactly(value1, value2, value3); + softly.assertThat(moveDirector.getPositionOf(variableMetaModel, value1)) + .isEqualTo(ElementPosition.of(entity, 0)); + softly.assertThat(moveDirector.getPositionOf(variableMetaModel, value2)) + .isEqualTo(ElementPosition.of(entity, 1)); + softly.assertThat(moveDirector.getPositionOf(variableMetaModel, value3)) + .isEqualTo(ElementPosition.of(entity, 2)); + softly.assertThat(moveDirector.getPositionOf(variableMetaModel, unassigned1)) + .isInstanceOf(UnassignedElement.class); + softly.assertThat(moveDirector.getPositionOf(variableMetaModel, unassigned2)) + .isInstanceOf(UnassignedElement.class); + }); + } + + @Test + void assignValuesAndAddAtEnd() { + var solutionDescriptor = TestdataListSolution.buildSolutionDescriptor(); + var solutionMetaModel = solutionDescriptor.getMetaModel(); + var variableMetaModel = solutionMetaModel.genuineEntity(TestdataListEntity.class) + .listVariable("valueList", TestdataListValue.class); + + var value1 = new TestdataListValue("value1"); + var value2 = new TestdataListValue("value2"); + var unassigned1 = new TestdataListValue("unassigned1"); + var unassigned2 = new TestdataListValue("unassigned2"); + var entity = new TestdataListEntity("A", value1, value2); + var solution = new TestdataListSolution(); + solution.setEntityList(List.of(entity)); + solution.setValueList(List.of(value1, value2, unassigned1, unassigned2)); + SolutionManager.updateShadowVariables(solution); + + var f = new BavetConstraintStreamScoreDirectorFactory<>(solutionDescriptor, + constraintFactory -> new Constraint[] { constraintFactory.forEach(TestdataListEntity.class) + .penalize(SimpleScore.ONE).asConstraint("Dummy constraint") }, + EnvironmentMode.FULL_ASSERT); + var scoreDirector = new BavetConstraintStreamScoreDirector.Builder<>(f).build(); + scoreDirector.setWorkingSolution(solution); + scoreDirector.calculateScore(); + + // Assign two unassigned values at the end (index 2). + var moveDirector = new MoveDirector<>(scoreDirector).ephemeral(); + moveDirector.assignValuesAndAdd(variableMetaModel, List.of(unassigned1, unassigned2), entity, 2); + assertThat(entity.getValueList()).containsExactly(value1, value2, unassigned1, unassigned2); + + // Undo it. + moveDirector.close(); + assertSoftly(softly -> { + softly.assertThat(entity.getValueList()).containsExactly(value1, value2); + softly.assertThat(moveDirector.getPositionOf(variableMetaModel, unassigned1)) + .isInstanceOf(UnassignedElement.class); + softly.assertThat(moveDirector.getPositionOf(variableMetaModel, unassigned2)) + .isInstanceOf(UnassignedElement.class); + }); + } + + @Test + void assignValuesAndAddFailsWhenValueAlreadyAssigned() { + var solutionDescriptor = TestdataListSolution.buildSolutionDescriptor(); + var solutionMetaModel = solutionDescriptor.getMetaModel(); + var variableMetaModel = solutionMetaModel.genuineEntity(TestdataListEntity.class) + .listVariable("valueList", TestdataListValue.class); + + var value1 = new TestdataListValue("value1"); + var value2 = new TestdataListValue("value2"); + var unassigned1 = new TestdataListValue("unassigned1"); + var entity = new TestdataListEntity("A", value1, value2); + var solution = new TestdataListSolution(); + solution.setEntityList(List.of(entity)); + solution.setValueList(List.of(value1, value2, unassigned1)); + SolutionManager.updateShadowVariables(solution); + + var f = new BavetConstraintStreamScoreDirectorFactory<>(solutionDescriptor, + constraintFactory -> new Constraint[] { constraintFactory.forEach(TestdataListEntity.class) + .penalize(SimpleScore.ONE).asConstraint("Dummy constraint") }, + EnvironmentMode.FULL_ASSERT); + var scoreDirector = new BavetConstraintStreamScoreDirector.Builder<>(f).build(); + scoreDirector.setWorkingSolution(solution); + scoreDirector.calculateScore(); + + var moveDirector = new MoveDirector<>(scoreDirector).ephemeral(); + // Try to assign a list containing value1 (already assigned) - should fail. + Assertions.assertThatThrownBy( + () -> moveDirector.assignValuesAndAdd(variableMetaModel, List.of(value1, unassigned1), entity, 1)) + .isInstanceOf(IllegalStateException.class); + moveDirector.close(); + } + @Test void assignValueAndSetFailsWhenValueAlreadyAssigned() { var solutionDescriptor = TestdataListSolution.buildSolutionDescriptor(); @@ -857,7 +1281,7 @@ void swapFirstAndLastValuesBetweenLists() { } @Test - void rebase() { + void lookUpWorkingObject() { var mockScoreDirector = mock(InnerScoreDirector.class); when(mockScoreDirector.lookUpWorkingObject(any(TestdataValue.class))).thenAnswer(invocation -> { var value = (TestdataValue) invocation.getArgument(0); @@ -866,7 +1290,7 @@ void rebase() { var moveDirector = new MoveDirector(mockScoreDirector); var expectedValue = new TestdataValue("value"); - var actualValue = moveDirector.rebase(expectedValue); + var actualValue = moveDirector.lookUpWorkingObject(expectedValue); assertSoftly(softly -> { softly.assertThat(actualValue).isNotSameAs(expectedValue); softly.assertThat(actualValue.getCode()).isEqualTo(expectedValue.getCode()); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/solver/DefaultSolverTest.java b/core/src/test/java/ai/timefold/solver/core/impl/solver/DefaultSolverTest.java index b8b27a2cb70..c353855c571 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/solver/DefaultSolverTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/solver/DefaultSolverTest.java @@ -2275,7 +2275,7 @@ public void changeWorkingSolution(PhaseCommandContext cont .listVariable("valueList", TestdataListValue.class); var entity = context.getWorkingSolution().getEntityList().getFirst(); var move = Moves.assign(variableMetaModel, new TestdataListValue("bad value"), entity, 0); - context.execute(move); + context.executeAndCalculateScore(move); } } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/solver/SolverMetricsIT.java b/core/src/test/java/ai/timefold/solver/core/impl/solver/SolverMetricsIT.java index 40053bfc790..7aa40f7d9b5 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/solver/SolverMetricsIT.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/solver/SolverMetricsIT.java @@ -471,13 +471,13 @@ private record SetTestdataEntityValueCustomPhaseCommand(TestdataEntity entity, T @Override public void changeWorkingSolution(PhaseCommandContext context) { - var workingEntity = context.lookupWorkingObject(entity); - var workingValue = context.lookupWorkingObject(value); + var workingEntity = context.lookUpWorkingObject(entity); + var workingValue = context.lookUpWorkingObject(value); var move = Moves.change(context.getSolutionMetaModel() .genuineEntity(TestdataEntity.class) .basicVariable("value", TestdataValue.class), workingEntity, workingValue); - context.execute(move); + context.executeAndCalculateScore(move); } } @@ -526,7 +526,7 @@ void solveStepScoreMetrics() { var step = new AtomicInteger(-1); ((DefaultSolver) solver) - .addPhaseLifecycleListener(new PhaseLifecycleListenerAdapter() { + .addPhaseLifecycleListener(new PhaseLifecycleListenerAdapter<>() { @Override public void stepEnded(AbstractStepScope stepScope) { super.stepEnded(stepScope); diff --git a/core/src/test/java/ai/timefold/solver/core/preview/api/move/builtin/DummyMove.java b/core/src/test/java/ai/timefold/solver/core/preview/api/move/builtin/DummyMove.java index bc247e5025a..fb639bded72 100644 --- a/core/src/test/java/ai/timefold/solver/core/preview/api/move/builtin/DummyMove.java +++ b/core/src/test/java/ai/timefold/solver/core/preview/api/move/builtin/DummyMove.java @@ -4,9 +4,9 @@ import java.util.Objects; import java.util.SequencedCollection; +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 ai.timefold.solver.core.testdomain.TestdataSolution; import ai.timefold.solver.core.testutil.CodeAssertable; @@ -40,7 +40,7 @@ public void execute(MutableSolutionView solutionView) { } @Override - public Move rebase(Rebaser rebaser) { + public Move rebase(Lookup lookup) { return this; } diff --git a/core/src/test/java/ai/timefold/solver/core/preview/api/move/builtin/ListChangeMoveTest.java b/core/src/test/java/ai/timefold/solver/core/preview/api/move/builtin/ListChangeMoveTest.java index e4f8c6e643b..e2492c1e1cd 100644 --- a/core/src/test/java/ai/timefold/solver/core/preview/api/move/builtin/ListChangeMoveTest.java +++ b/core/src/test/java/ai/timefold/solver/core/preview/api/move/builtin/ListChangeMoveTest.java @@ -4,9 +4,9 @@ import java.util.List; +import ai.timefold.solver.core.api.domain.common.Lookup; import ai.timefold.solver.core.preview.api.move.MoveTester; import ai.timefold.solver.core.preview.api.move.MutableSolutionView; -import ai.timefold.solver.core.preview.api.move.Rebaser; import ai.timefold.solver.core.testdomain.list.TestdataListEntity; import ai.timefold.solver.core.testdomain.list.TestdataListSolution; import ai.timefold.solver.core.testdomain.list.TestdataListValue; @@ -458,11 +458,11 @@ void rebaseCreatesNewMoveWithRebasedEntities() { variableMetaModel, entity1, 0, entity2, 1); - // Rebase with a rebaser that maps to new entities - var rebasedMove = originalMove.rebase(new Rebaser() { + // Look up new entities + var rebasedMove = originalMove.rebase(new Lookup() { @Override @SuppressWarnings("unchecked") - public T rebase(T object) { + public T lookUpWorkingObject(T object) { if (object == entity1) { return (T) rebasedEntity1; } else if (object == entity2) { @@ -470,6 +470,7 @@ public T rebase(T object) { } return object; } + }); // Verify the rebased move has the new entities @@ -495,15 +496,16 @@ void rebaseWithSameEntityForSourceAndDestination() { variableMetaModel, entity, 0, entity, 0); - var rebasedMove = originalMove.rebase(new Rebaser() { + var rebasedMove = originalMove.rebase(new Lookup() { @Override @SuppressWarnings("unchecked") - public T rebase(T object) { + public T lookUpWorkingObject(T object) { if (object == entity) { return (T) rebasedEntity; } return object; } + }); // Both source and destination should be rebased to the same entity diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyEntityComparator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyEntityComparator.java new file mode 100644 index 00000000000..9b412f4e651 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyEntityComparator.java @@ -0,0 +1,15 @@ +package ai.timefold.solver.core.testdomain.common; + +import java.util.Comparator; + +import ai.timefold.solver.core.testdomain.TestdataEntity; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public class DummyEntityComparator implements Comparator { + @Override + public int compare(TestdataEntity testdataEntity, TestdataEntity testdataEntity2) { + return 0; + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyEntityComparatorFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyEntityComparatorFactory.java new file mode 100644 index 00000000000..cfa062b04e8 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyEntityComparatorFactory.java @@ -0,0 +1,20 @@ +package ai.timefold.solver.core.testdomain.common; + +import java.util.Comparator; + +import ai.timefold.solver.core.api.domain.common.ComparatorFactory; +import ai.timefold.solver.core.testdomain.TestdataEntity; +import ai.timefold.solver.core.testdomain.TestdataSolution; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public class DummyEntityComparatorFactory + implements ComparatorFactory { + + @Override + public Comparator createComparator(TestdataSolution solution) { + return new DummyEntityComparator(); + } + +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyValueComparator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyValueComparator.java index ada61c42eff..404cdd5a15c 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyValueComparator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyValueComparator.java @@ -4,6 +4,9 @@ import ai.timefold.solver.core.testdomain.TestdataValue; +import org.jspecify.annotations.NullMarked; + +@NullMarked public class DummyValueComparator implements Comparator { @Override diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyValueComparatorFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyValueComparatorFactory.java new file mode 100644 index 00000000000..e1fd701ebe9 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyValueComparatorFactory.java @@ -0,0 +1,18 @@ +package ai.timefold.solver.core.testdomain.common; + +import java.util.Comparator; + +import ai.timefold.solver.core.api.domain.common.ComparatorFactory; +import ai.timefold.solver.core.testdomain.TestdataValue; +import ai.timefold.solver.core.testdomain.list.sort.factory.TestdataListFactorySortableSolution; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public class DummyValueComparatorFactory implements ComparatorFactory { + + @Override + public Comparator createComparator(TestdataListFactorySortableSolution testdataListFactorySortableSolution) { + return new DummyValueComparator(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyValueFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyValueFactory.java deleted file mode 100644 index e674b6b4889..00000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyValueFactory.java +++ /dev/null @@ -1,13 +0,0 @@ -package ai.timefold.solver.core.testdomain.common; - -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; -import ai.timefold.solver.core.testdomain.TestdataValue; -import ai.timefold.solver.core.testdomain.list.sort.factory.TestdataListFactorySortableSolution; - -public class DummyValueFactory implements SelectionSorterWeightFactory { - - @Override - public Comparable createSorterWeight(TestdataListFactorySortableSolution solution, TestdataValue selection) { - return 0; - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyWeightValueFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyWeightValueFactory.java deleted file mode 100644 index 6496822201f..00000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/common/DummyWeightValueFactory.java +++ /dev/null @@ -1,14 +0,0 @@ -package ai.timefold.solver.core.testdomain.common; - -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; -import ai.timefold.solver.core.testdomain.TestdataValue; -import ai.timefold.solver.core.testdomain.list.sort.factory.TestdataListFactorySortableSolution; - -public class DummyWeightValueFactory - implements SelectionSorterWeightFactory { - - @Override - public Comparable createSorterWeight(TestdataListFactorySortableSolution solution, TestdataValue selection) { - return v -> 0; - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableFactory.java deleted file mode 100644 index 5ca230331c9..00000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableFactory.java +++ /dev/null @@ -1,12 +0,0 @@ -package ai.timefold.solver.core.testdomain.common; - -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; - -public class TestSortableFactory - implements SelectionSorterWeightFactory { - - @Override - public Comparable createSorterWeight(Object o, TestSortableObject selection) { - return selection; - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableComparator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableObjectComparator.java similarity index 62% rename from core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableComparator.java rename to core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableObjectComparator.java index 6c7d606c4ae..761dc2bb735 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableComparator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableObjectComparator.java @@ -2,7 +2,10 @@ import java.util.Comparator; -public class TestSortableComparator implements Comparator { +import org.jspecify.annotations.NullMarked; + +@NullMarked +public class TestSortableObjectComparator implements Comparator { @Override public int compare(TestSortableObject v1, TestSortableObject v2) { diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableObjectComparatorFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableObjectComparatorFactory.java new file mode 100644 index 00000000000..d8fc438cf1a --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestSortableObjectComparatorFactory.java @@ -0,0 +1,17 @@ +package ai.timefold.solver.core.testdomain.common; + +import java.util.Comparator; + +import ai.timefold.solver.core.api.domain.common.ComparatorFactory; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public class TestSortableObjectComparatorFactory + implements ComparatorFactory { + + @Override + public Comparator createComparator(Object solution) { + return new TestSortableObjectComparator(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableDescendingComparator.java b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableDescendingComparator.java index 84d894aafd7..d75cbd7ae7b 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableDescendingComparator.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableDescendingComparator.java @@ -4,6 +4,9 @@ import ai.timefold.solver.core.testdomain.TestdataObject; +import org.jspecify.annotations.NullMarked; + +@NullMarked public class TestdataObjectSortableDescendingComparator implements Comparator { @Override diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableDescendingComparatorFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableDescendingComparatorFactory.java new file mode 100644 index 00000000000..0e46b10ea0e --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableDescendingComparatorFactory.java @@ -0,0 +1,17 @@ +package ai.timefold.solver.core.testdomain.common; + +import java.util.Comparator; + +import ai.timefold.solver.core.api.domain.common.ComparatorFactory; +import ai.timefold.solver.core.testdomain.TestdataObject; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public class TestdataObjectSortableDescendingComparatorFactory implements ComparatorFactory { + + @Override + public Comparator createComparator(Object solution) { + return new TestdataObjectSortableDescendingComparator(); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableDescendingFactory.java b/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableDescendingFactory.java deleted file mode 100644 index e4bff2ea6cf..00000000000 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/common/TestdataObjectSortableDescendingFactory.java +++ /dev/null @@ -1,18 +0,0 @@ -package ai.timefold.solver.core.testdomain.common; - -import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory; -import ai.timefold.solver.core.testdomain.TestdataObject; - -public class TestdataObjectSortableDescendingFactory implements SelectionSorterWeightFactory { - - @Override - public Comparable createSorterWeight(Object solution, TestdataObject selection) { - // Descending order - return -extractCode(selection.getCode()); - } - - public static int extractCode(String code) { - var idx = code.lastIndexOf(" "); - return Integer.parseInt(code.substring(idx + 1)); - } -} diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/TestdataListSortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/TestdataListSortableEntity.java index 258435d6eb0..d742a352389 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/TestdataListSortableEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/comparator/TestdataListSortableEntity.java @@ -6,14 +6,14 @@ import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.variable.PlanningListVariable; import ai.timefold.solver.core.testdomain.TestdataObject; -import ai.timefold.solver.core.testdomain.common.TestSortableComparator; import ai.timefold.solver.core.testdomain.common.TestSortableObject; +import ai.timefold.solver.core.testdomain.common.TestSortableObjectComparator; import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; -@PlanningEntity(comparatorClass = TestSortableComparator.class) +@PlanningEntity(comparatorClass = TestSortableObjectComparator.class) public class TestdataListSortableEntity extends TestdataObject implements TestSortableObject { - @PlanningListVariable(valueRangeProviderRefs = "valueRange", comparatorClass = TestSortableComparator.class) + @PlanningListVariable(valueRangeProviderRefs = "valueRange", comparatorClass = TestSortableObjectComparator.class) private List valueList; private int difficulty; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableEntity.java index 1ecfea00c3c..cdee6fc9520 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/factory/TestdataListFactorySortableEntity.java @@ -6,14 +6,15 @@ import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.variable.PlanningListVariable; import ai.timefold.solver.core.testdomain.TestdataObject; -import ai.timefold.solver.core.testdomain.common.TestSortableFactory; import ai.timefold.solver.core.testdomain.common.TestSortableObject; +import ai.timefold.solver.core.testdomain.common.TestSortableObjectComparatorFactory; import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; -@PlanningEntity(comparatorFactoryClass = TestSortableFactory.class) +@PlanningEntity(comparatorFactoryClass = TestSortableObjectComparatorFactory.class) public class TestdataListFactorySortableEntity extends TestdataObject implements TestSortableObject { - @PlanningListVariable(valueRangeProviderRefs = "valueRange", comparatorFactoryClass = TestSortableFactory.class) + @PlanningListVariable(valueRangeProviderRefs = "valueRange", + comparatorFactoryClass = TestSortableObjectComparatorFactory.class) private List valueList; private int difficulty; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/invalid/TestdataInvalidListSortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/invalid/TestdataInvalidListSortableEntity.java index 0e184321bb3..814d00c74c5 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/invalid/TestdataInvalidListSortableEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/sort/invalid/TestdataInvalidListSortableEntity.java @@ -7,14 +7,14 @@ import ai.timefold.solver.core.api.domain.variable.PlanningListVariable; import ai.timefold.solver.core.testdomain.TestdataObject; import ai.timefold.solver.core.testdomain.common.DummyValueComparator; -import ai.timefold.solver.core.testdomain.common.DummyValueFactory; +import ai.timefold.solver.core.testdomain.common.DummyValueComparatorFactory; import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; @PlanningEntity public class TestdataInvalidListSortableEntity extends TestdataObject { @PlanningListVariable(valueRangeProviderRefs = "valueRange", comparatorClass = DummyValueComparator.class, - comparatorFactoryClass = DummyValueFactory.class) + comparatorFactoryClass = DummyValueComparatorFactory.class) private List valueList; private int difficulty; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/TestdataListSortableEntityProvidingEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/TestdataListSortableEntityProvidingEntity.java index 95765393bb6..5490648263e 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/TestdataListSortableEntityProvidingEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/comparator/TestdataListSortableEntityProvidingEntity.java @@ -8,14 +8,14 @@ import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; import ai.timefold.solver.core.api.domain.variable.PlanningListVariable; import ai.timefold.solver.core.testdomain.TestdataObject; -import ai.timefold.solver.core.testdomain.common.TestSortableComparator; import ai.timefold.solver.core.testdomain.common.TestSortableObject; +import ai.timefold.solver.core.testdomain.common.TestSortableObjectComparator; import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; -@PlanningEntity(comparatorClass = TestSortableComparator.class) +@PlanningEntity(comparatorClass = TestSortableObjectComparator.class) public class TestdataListSortableEntityProvidingEntity extends TestdataObject implements TestSortableObject { - @PlanningListVariable(valueRangeProviderRefs = "valueRange", comparatorClass = TestSortableComparator.class) + @PlanningListVariable(valueRangeProviderRefs = "valueRange", comparatorClass = TestSortableObjectComparator.class) private List valueList; @ValueRangeProvider(id = "valueRange") @PlanningEntityCollectionProperty diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingEntity.java index f44f1f7cb3a..c6ee7d04435 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/list/valuerange/sort/factory/TestdataListFactorySortableEntityProvidingEntity.java @@ -8,15 +8,16 @@ import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; import ai.timefold.solver.core.api.domain.variable.PlanningListVariable; import ai.timefold.solver.core.testdomain.TestdataObject; -import ai.timefold.solver.core.testdomain.common.TestSortableFactory; import ai.timefold.solver.core.testdomain.common.TestSortableObject; +import ai.timefold.solver.core.testdomain.common.TestSortableObjectComparatorFactory; import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; -@PlanningEntity(comparatorFactoryClass = TestSortableFactory.class) +@PlanningEntity(comparatorFactoryClass = TestSortableObjectComparatorFactory.class) public class TestdataListFactorySortableEntityProvidingEntity extends TestdataObject implements TestSortableObject { - @PlanningListVariable(valueRangeProviderRefs = "valueRange", comparatorFactoryClass = TestSortableFactory.class) + @PlanningListVariable(valueRangeProviderRefs = "valueRange", + comparatorFactoryClass = TestSortableObjectComparatorFactory.class) private List valueList; @ValueRangeProvider(id = "valueRange") @PlanningEntityCollectionProperty diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/MixedCustomPhaseCommand.java b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/MixedCustomPhaseCommand.java index ea5c1594a70..231907e4410 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/MixedCustomPhaseCommand.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/mixed/singleentity/MixedCustomPhaseCommand.java @@ -17,7 +17,7 @@ public void changeWorkingSolution(PhaseCommandContext con var moveIteratorFactory = new MixedCustomMoveIteratorFactory(); var moveIterator = moveIteratorFactory.createRandomMoveIterator(scoreDirector, null); var move = moveIterator.next(); - context.execute(move); + context.executeAndCalculateScore(move); } } diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/TestdataComparatorSortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/TestdataComparatorSortableEntity.java index 7acebfe4253..5f3763c3e7b 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/TestdataComparatorSortableEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/comparator/TestdataComparatorSortableEntity.java @@ -3,14 +3,14 @@ import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.variable.PlanningVariable; import ai.timefold.solver.core.testdomain.TestdataObject; -import ai.timefold.solver.core.testdomain.common.TestSortableComparator; import ai.timefold.solver.core.testdomain.common.TestSortableObject; +import ai.timefold.solver.core.testdomain.common.TestSortableObjectComparator; import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; -@PlanningEntity(comparatorClass = TestSortableComparator.class) +@PlanningEntity(comparatorClass = TestSortableObjectComparator.class) public class TestdataComparatorSortableEntity extends TestdataObject implements TestSortableObject { - @PlanningVariable(valueRangeProviderRefs = "valueRange", comparatorClass = TestSortableComparator.class) + @PlanningVariable(valueRangeProviderRefs = "valueRange", comparatorClass = TestSortableObjectComparator.class) private TestdataSortableValue value; private int difficulty; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/TestdataFactorySortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/TestdataFactorySortableEntity.java index f38c8f15c45..52f827d6dce 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/TestdataFactorySortableEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/factory/TestdataFactorySortableEntity.java @@ -3,14 +3,14 @@ import ai.timefold.solver.core.api.domain.entity.PlanningEntity; import ai.timefold.solver.core.api.domain.variable.PlanningVariable; import ai.timefold.solver.core.testdomain.TestdataObject; -import ai.timefold.solver.core.testdomain.common.TestSortableFactory; import ai.timefold.solver.core.testdomain.common.TestSortableObject; +import ai.timefold.solver.core.testdomain.common.TestSortableObjectComparatorFactory; import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; -@PlanningEntity(comparatorFactoryClass = TestSortableFactory.class) +@PlanningEntity(comparatorFactoryClass = TestSortableObjectComparatorFactory.class) public class TestdataFactorySortableEntity extends TestdataObject implements TestSortableObject { - @PlanningVariable(valueRangeProviderRefs = "valueRange", comparatorFactoryClass = TestSortableFactory.class) + @PlanningVariable(valueRangeProviderRefs = "valueRange", comparatorFactoryClass = TestSortableObjectComparatorFactory.class) private TestdataSortableValue value; private int difficulty; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/TestdataInvalidMixedComparatorSortableEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/TestdataInvalidMixedComparatorSortableEntity.java index dcd18fd5f77..6f9f663e9e9 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/TestdataInvalidMixedComparatorSortableEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/sort/invalid/TestdataInvalidMixedComparatorSortableEntity.java @@ -4,14 +4,14 @@ import ai.timefold.solver.core.api.domain.variable.PlanningVariable; import ai.timefold.solver.core.testdomain.TestdataObject; import ai.timefold.solver.core.testdomain.common.DummyValueComparator; -import ai.timefold.solver.core.testdomain.common.DummyValueFactory; +import ai.timefold.solver.core.testdomain.common.DummyValueComparatorFactory; import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; @PlanningEntity public class TestdataInvalidMixedComparatorSortableEntity extends TestdataObject { @PlanningVariable(valueRangeProviderRefs = "valueRange", comparatorClass = DummyValueComparator.class, - comparatorFactoryClass = DummyValueFactory.class) + comparatorFactoryClass = DummyValueComparatorFactory.class) private TestdataSortableValue value; private int difficulty; diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/TestdataComparatorSortableEntityProvidingEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/TestdataComparatorSortableEntityProvidingEntity.java index 857d0171558..dfe473027dc 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/TestdataComparatorSortableEntityProvidingEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/comparator/TestdataComparatorSortableEntityProvidingEntity.java @@ -7,14 +7,14 @@ import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; import ai.timefold.solver.core.api.domain.variable.PlanningVariable; import ai.timefold.solver.core.testdomain.TestdataObject; -import ai.timefold.solver.core.testdomain.common.TestSortableComparator; import ai.timefold.solver.core.testdomain.common.TestSortableObject; +import ai.timefold.solver.core.testdomain.common.TestSortableObjectComparator; import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; -@PlanningEntity(comparatorClass = TestSortableComparator.class) +@PlanningEntity(comparatorClass = TestSortableObjectComparator.class) public class TestdataComparatorSortableEntityProvidingEntity extends TestdataObject implements TestSortableObject { - @PlanningVariable(valueRangeProviderRefs = "valueRange", comparatorClass = TestSortableComparator.class) + @PlanningVariable(valueRangeProviderRefs = "valueRange", comparatorClass = TestSortableObjectComparator.class) private TestdataSortableValue value; @ValueRangeProvider(id = "valueRange") @PlanningEntityCollectionProperty diff --git a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/TestdataFactorySortableEntityProvidingEntity.java b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/TestdataFactorySortableEntityProvidingEntity.java index dbc1c917452..681a08b5bee 100644 --- a/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/TestdataFactorySortableEntityProvidingEntity.java +++ b/core/src/test/java/ai/timefold/solver/core/testdomain/valuerange/sort/factory/TestdataFactorySortableEntityProvidingEntity.java @@ -7,15 +7,15 @@ import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; import ai.timefold.solver.core.api.domain.variable.PlanningVariable; import ai.timefold.solver.core.testdomain.TestdataObject; -import ai.timefold.solver.core.testdomain.common.TestSortableFactory; import ai.timefold.solver.core.testdomain.common.TestSortableObject; +import ai.timefold.solver.core.testdomain.common.TestSortableObjectComparatorFactory; import ai.timefold.solver.core.testdomain.common.TestdataSortableValue; -@PlanningEntity(comparatorFactoryClass = TestSortableFactory.class) +@PlanningEntity(comparatorFactoryClass = TestSortableObjectComparatorFactory.class) public class TestdataFactorySortableEntityProvidingEntity extends TestdataObject implements TestSortableObject { - @PlanningVariable(valueRangeProviderRefs = "valueRange", comparatorFactoryClass = TestSortableFactory.class) + @PlanningVariable(valueRangeProviderRefs = "valueRange", comparatorFactoryClass = TestSortableObjectComparatorFactory.class) private TestdataSortableValue value; @ValueRangeProvider(id = "valueRange") @PlanningEntityCollectionProperty diff --git a/core/src/test/java/ai/timefold/solver/core/testutil/PlannerTestUtils.java b/core/src/test/java/ai/timefold/solver/core/testutil/PlannerTestUtils.java index db66680bb59..4f8c99eb3c5 100644 --- a/core/src/test/java/ai/timefold/solver/core/testutil/PlannerTestUtils.java +++ b/core/src/test/java/ai/timefold/solver/core/testutil/PlannerTestUtils.java @@ -157,7 +157,7 @@ public static TestdataSolution generateTestdataSolution(String code, int entityA InnerScoreDirector scoreDirector = mock(InnerScoreDirector.class); MoveDirector moveDirector = mock(MoveDirector.class); when(moveDirector.getScoreDirector()).thenReturn(scoreDirector); - when(moveDirector.rebase(any())).thenAnswer(invocation -> { + when(moveDirector.lookUpWorkingObject(any())).thenAnswer(invocation -> { var externalObject = invocation.getArguments()[0]; if (externalObject == null) { return null; diff --git a/docs/TODO.md b/docs/TODO.md index 09d5ea1a787..faa3c38dfd4 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -34,6 +34,8 @@ - [ ] `PhaseCommand` was significantly refactored. - [ ] `ScoreDirector` no longer public API. - [ ] `SolutionPartitioner` no longer uses ScoreDirector, uses solution instead. +- [ ] `Rebaser` renamed to `Lookup` and made public. +- [ ] `ProblemChangeDirector#lookUpWorkingObject()` no longer returns `Optional`. - [ ] `AutoDiscoverMemberType` is gone Remove this file when done. \ No newline at end of file diff --git a/docs/src/modules/ROOT/pages/constraints-and-score/constraint-configuration.adoc b/docs/src/modules/ROOT/pages/constraints-and-score/constraint-configuration.adoc index 8b3e3e61eb8..91c2359a8d1 100644 --- a/docs/src/modules/ROOT/pages/constraints-and-score/constraint-configuration.adoc +++ b/docs/src/modules/ROOT/pages/constraints-and-score/constraint-configuration.adoc @@ -239,170 +239,4 @@ public class MyConstraintProvider implements ConstraintProvider { } ---- -==== - - -[#legacyConstraintConfiguration] -== Legacy constraint configuration using `@ConstraintConfiguration` - -[NOTE] -==== -This feature is deprecated and will be removed in a future release of Timefold Solver. -Please use <> instead. -==== - -First, create a new class to hold the constraint weights and other constraint parameters. -Annotate it with `@ConstraintConfiguration`: - -[source,java,options="nowrap"] ----- -@ConstraintConfiguration -public class ConferenceConstraintConfiguration { - ... -} ----- - -There will be exactly one instance of this class per planning solution. -The planning solution and the constraint configuration have a one-to-one relationship, -but they serve a different purpose, so they aren't merged into a single class. -A `@ConstraintConfiguration` class can extend a parent `@ConstraintConfiguration` class, -which can be useful in international use cases with many regional constraints. - -Add the constraint configuration on the planning solution -and annotate that field or property with `@ConstraintConfigurationProvider`: - -[source,java,options="nowrap"] ----- -@PlanningSolution -public class ConferenceSchedule { - - @ConstraintConfigurationProvider - private ConferenceConstraintConfiguration constraintConfiguration; - - ... -} ----- - -The `@ConstraintConfigurationProvider` annotation automatically exposes the constraint configuration -as a xref:using-timefold-solver/modeling-planning-problems.adoc#problemFacts[problem fact], -there is no need to add a `@ProblemFactProperty` annotation. - -The constraint configuration class holds the constraint weights, but it can also hold constraint parameters. -For example, in conference scheduling, the minimum pause constraint has a constraint weight (like any other constraint), -but it also has a constraint parameter that defines the length of the minimum pause between two talks of the same speaker. -That pause length depends on the conference (= the planning problem): -in some big conferences 20 minutes isn't enough to go from one room to the other. -That pause length is a field in the constraint configuration without a `@ConstraintWeight` annotation. - - -[#legacyConstraintWeight] -=== Add a constraint weight for each constraint - -In the constraint configuration class, add a `@ConstraintWeight` field or property for each constraint: - -[source,java,options="nowrap"] ----- -@ConstraintConfiguration -public class ConferenceConstraintConfiguration { - - @ConstraintWeight("Speaker conflict") - private HardMediumSoftScore speakerConflict = HardMediumSoftScore.ofHard(10); - - @ConstraintWeight("Theme track conflict") - private HardMediumSoftScore themeTrackConflict = HardMediumSoftScore.ofSoft(10); - @ConstraintWeight("Content conflict") - private HardMediumSoftScore contentConflict = HardMediumSoftScore.ofSoft(100); - - ... -} ----- - -The type of the constraint weights must be the same score class as xref:using-timefold-solver/modeling-planning-problems.adoc#scoreOfASolution[the planning solution's score member]. -For example, in conference scheduling, `ConferenceSchedule.getScore()` and `ConferenceConstraintConfiguration.getSpeakerConflict()` -both return a `HardMediumSoftScore`. - -A constraint weight can’t be null. -Give each constraint weight a default value, but expose them in a UI so the business users can tweak them. -The example above uses the `ofHard()`, `ofMedium()` and `ofSoft()` methods to do that. -Notice how it defaults the _"Content conflict"_ constraint as ten times more important than the _"Theme track conflict"_ constraint. -Normally, a constraint weight only uses one score level, -but it's possible to use multiple score levels (at a small performance cost). - -Each constraint has a constraint name, serving as a unique identifier for the constraint. -It connects the constraint weight with the constraint implementation. -*For each constraint weight, there must be a constraint implementation with the same constraint name.* - -* The `@ConstraintConfiguration` annotation has a `constraintPackage` property that defaults to the package of the constraint configuration class. -Cases with xref:constraints-and-score/score-calculation.adoc[Constraint Streams API] normally don't need to specify it. - -* The `@ConstraintWeight` annotation has a `value` which is the constraint name (for example "Speaker conflict"). - -So every constraint weight ends up with a name. -Each constraint weight links with a constraint implementation, -for example, in xref:constraints-and-score/score-calculation.adoc[Constraint Streams API]: - -[source,java,options="nowrap"] ----- -public class ConferenceSchedulingConstraintProvider implements ConstraintProvider { - - @Override - public Constraint[] defineConstraints(ConstraintFactory factory) { - return new Constraint[] { - speakerConflict(factory), - themeTrackConflict(factory), - contentConflict(factory), - ... - }; - } - - protected Constraint speakerConflict(ConstraintFactory factory) { - return factory.forEachUniquePair(...) - ... - .penalizeConfigurable("Speaker conflict", ...); - } - - protected Constraint themeTrackConflict(ConstraintFactory factory) { - return factory.forEachUniquePair(...) - ... - .penalizeConfigurable("Theme track conflict", ...); - } - - protected Constraint contentConflict(ConstraintFactory factory) { - return factory.forEachUniquePair(...) - ... - .penalizeConfigurable("Content conflict", ...); - } - - ... - -} ----- - -Each of the constraint weights defines the score level and score weight of their constraint. -The constraint implementation calls `rewardConfigurable()` or `penalizeConfigurable()` and the constraint weight is automatically applied. - -If the constraint implementation provides a match weight, that *match weight is multiplied with the constraint weight*. -For example, the _"Content conflict"_ constraint weight defaults to `100soft` -and the constraint implementation penalizes each match based on the number of shared content tags and the overlapping duration of the two talks: - -[source,java,options="nowrap"] ----- - @ConstraintWeight("Content conflict") - private HardMediumSoftScore contentConflict = HardMediumSoftScore.ofSoft(100); ----- - -[source,java,options="nowrap"] ----- -Constraint contentConflict(ConstraintFactory factory) { - return factory.forEachUniquePair(Talk.class, - overlapping(t -> t.getTimeslot().getStartDateTime(), - t -> t.getTimeslot().getEndDateTime()), - filtering((talk1, talk2) -> talk1.overlappingContentCount(talk2) > 0)) - .penalizeConfigurable("Content conflict", - (talk1, talk2) -> talk1.overlappingContentCount(talk2) - * talk1.overlappingDurationInMinutes(talk2)); -} ----- - -So when 2 overlapping talks share only 1 content tag and overlap by 60 minutes, the score is impacted by `-6000soft`. -But when 2 overlapping talks share 3 content tags, the match weight is 180, so the score is impacted by `-18000soft`. \ No newline at end of file +==== \ No newline at end of file diff --git a/docs/src/modules/ROOT/pages/constraints-and-score/score-calculation.adoc b/docs/src/modules/ROOT/pages/constraints-and-score/score-calculation.adoc index f4e2c78de54..25c20b46ab3 100644 --- a/docs/src/modules/ROOT/pages/constraints-and-score/score-calculation.adoc +++ b/docs/src/modules/ROOT/pages/constraints-and-score/score-calculation.adoc @@ -306,17 +306,6 @@ you may want to include an xref:using-timefold-solver/modeling-planning-problems.adoc#listVariableShadowVariablesInverseRelation[inverse relation shadow variable] to maximize performance of your constraints. -[NOTE] -==== -The `forEach()` building block has a legacy counterpart, `from()`. -This alternative approach included instances based on the initialization status of their genuine planning variables. -As an unwanted consequence, -`from()` -behaves unexpectedly for xref:using-timefold-solver/modeling-planning-problems.adoc#planningVariableAllowingUnassigned[variables with unassigned values]. -These are considered initialized even when `null`, -and therefore this legacy method could still return entities with `null` variables. -`from()`, `fromUnfiltered()` and `fromUniquePair()` are now deprecated and will be removed in Timefold Solver 2.0 -==== [#constraintStreamsPenaltiesRewards] === Penalties and rewards diff --git a/docs/src/modules/ROOT/pages/optimization-algorithms/neighborhoods.adoc b/docs/src/modules/ROOT/pages/optimization-algorithms/neighborhoods.adoc index 98084cc309a..eb90da443fa 100644 --- a/docs/src/modules/ROOT/pages/optimization-algorithms/neighborhoods.adoc +++ b/docs/src/modules/ROOT/pages/optimization-algorithms/neighborhoods.adoc @@ -94,7 +94,7 @@ it is also recommended to override the following methods: Finally, the `Move` interface specifies the following methods that the user can optionally override to gain access to additional solver features: -`Move rebase(Rebaser rebaser)`:: +`Move rebase(Lookup lookup)`:: This method creates a copy of the move that is applicable to a different working solution. This is only necessary when the solver is configured to use xref:enterprise-edition/enterprise-edition.adoc#multithreadedSolving[multi-threaded solving]. `String describe()`:: @@ -228,10 +228,10 @@ public final class ChangeMove implements Move { } @Override - public ChangeMove rebase(Rebaser rebaser) { + public ChangeMove rebase(Lookup lookup) { return new ChangeMove(timeslotVariable, - rebaser.rebase(lesson), - rebaser.rebase(timeslot)); + lookup.lookUpWorkingObject(lesson), + lookup.lookUpWorkingObject(timeslot)); } @Override diff --git a/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc b/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc index 24ae758aed5..e4529ceab9e 100644 --- a/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc +++ b/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc @@ -881,7 +881,7 @@ so that the user can perform arbitrary calculations on the working solution with The return value of the provided `Function` is returned by this method. `boolean isPhaseTerminated()`:: Returns `true` if the `PhaseCommand` should terminate. -`Object lookupWorkingObject(Object externalObject)`:: +`Object lookUpWorkingObject(Object externalObject)`:: Returns the working object that corresponds to the given external object. This is useful when the `PhaseCommand` remembers an object from some previous working solution, and needs to find the corresponding object in the current working solution, @@ -896,9 +896,9 @@ The solver will only terminate after the command returns. [WARNING] ==== -Do not change any of the problem facts in a `PhaseCommand`, and add or remove entities, +Do not change any of the problem facts in a `PhaseCommand` or add or remove entities, it will corrupt the `Solver` because any previous score or solution was for a different problem. -To change problem facts, and to add or remove entities, +To change problem facts or to add or remove entities, read about xref:responding-to-change/responding-to-change.adoc[repeated planning] and use xref:responding-to-change/responding-to-change.adoc#problemChange[ProblemChange] instead. ==== diff --git a/docs/src/modules/ROOT/pages/responding-to-change/responding-to-change.adoc b/docs/src/modules/ROOT/pages/responding-to-change/responding-to-change.adoc index b39d56ce415..6ca8b5bda2c 100644 --- a/docs/src/modules/ROOT/pages/responding-to-change/responding-to-change.adoc +++ b/docs/src/modules/ROOT/pages/responding-to-change/responding-to-change.adoc @@ -303,40 +303,6 @@ NOTE: Value of the index must never change during planning. To change how far the list is pinned, use xref:responding-to-change/responding-to-change.adoc#problemChange[ProblemChange]. -[#configureAPinningFilter] -==== Configure a `PinningFilter` - -WARNING: `PinningFilter` is deprecated and will be removed in Timefold Solver 2.0. -Use `@PlanningPin` as described above. - -Alternatively, to pin some planning entities down, -add a `PinningFilter` that returns `true` if an entity is pinned, -and `false` if it is movable. - -For example, on the employee scheduling quickstart: - -[source,java,options="nowrap"] ----- -public class ShiftPinningFilter implements PinningFilter { - - @Override - public boolean accept(EmployeeSchedule employeeSchedule, Shift shift) { - ScheduleState scheduleState = employeeSchedule.getScheduleState(); - return !scheduleState.isDraft(shift); - } -} ----- - -Configure the `PinningFilter`: - -[source,java,options="nowrap"] ----- -@PlanningEntity(pinningFilter = ShiftPinningFilter.class) -public class Shift { - ... -} ----- - [#nonvolatileReplanning] === Nonvolatile replanning to minimize disruption (semi-movable planning entities) diff --git a/tools/migration/pom.xml b/tools/migration/pom.xml index b3c16a09489..1e6cbd60510 100644 --- a/tools/migration/pom.xml +++ b/tools/migration/pom.xml @@ -15,7 +15,7 @@ Timefold Solver Migration Upgrade your code with a single command. - This migration replace all your calls to deleted/deprecated methods of Timefold with their proper alternatives. + This migration replaces all your calls to deleted/deprecated methods of Timefold with their proper alternatives. https://solver.timefold.ai diff --git a/tools/test/src/main/java/ai/timefold/solver/test/api/solver/change/MockProblemChangeDirector.java b/tools/test/src/main/java/ai/timefold/solver/test/api/solver/change/MockProblemChangeDirector.java index 4305fb6e8e9..f7c7c583b67 100644 --- a/tools/test/src/main/java/ai/timefold/solver/test/api/solver/change/MockProblemChangeDirector.java +++ b/tools/test/src/main/java/ai/timefold/solver/test/api/solver/change/MockProblemChangeDirector.java @@ -2,7 +2,6 @@ import java.util.IdentityHashMap; import java.util.Map; -import java.util.Optional; import java.util.function.Consumer; import ai.timefold.solver.core.api.solver.change.ProblemChangeDirector; @@ -34,36 +33,36 @@ public class MockProblemChangeDirector implements ProblemChangeDirector { @Override public void addEntity(@NonNull Entity entity, @NonNull Consumer entityConsumer) { - entityConsumer.accept(lookUpWorkingObjectOrFail(entity)); + entityConsumer.accept(this.lookUpWorkingObject(entity)); } @Override public void removeEntity(@NonNull Entity entity, Consumer entityConsumer) { - entityConsumer.accept(lookUpWorkingObjectOrFail(entity)); + entityConsumer.accept(this.lookUpWorkingObject(entity)); } @Override public void changeVariable(@NonNull Entity entity, @NonNull String variableName, @NonNull Consumer entityConsumer) { - entityConsumer.accept(lookUpWorkingObjectOrFail(entity)); + entityConsumer.accept(this.lookUpWorkingObject(entity)); } @Override public void addProblemFact(@NonNull ProblemFact problemFact, @NonNull Consumer problemFactConsumer) { - problemFactConsumer.accept(lookUpWorkingObjectOrFail(problemFact)); + problemFactConsumer.accept(this.lookUpWorkingObject(problemFact)); } @Override public void removeProblemFact(@NonNull ProblemFact problemFact, @NonNull Consumer problemFactConsumer) { - problemFactConsumer.accept(lookUpWorkingObjectOrFail(problemFact)); + problemFactConsumer.accept(this.lookUpWorkingObject(problemFact)); } @Override public void changeProblemProperty(@NonNull EntityOrProblemFact problemFactOrEntity, @NonNull Consumer problemFactOrEntityConsumer) { - problemFactOrEntityConsumer.accept(lookUpWorkingObjectOrFail(problemFactOrEntity)); + problemFactOrEntityConsumer.accept(this.lookUpWorkingObject(problemFactOrEntity)); } /** @@ -74,30 +73,18 @@ public void changeProblemProperty(@NonNull EntityOrProblem */ @Override public @Nullable EntityOrProblemFact - lookUpWorkingObjectOrFail(@Nullable EntityOrProblemFact externalObject) { + lookUpWorkingObject(@Nullable EntityOrProblemFact externalObject) { EntityOrProblemFact entityOrProblemFact = (EntityOrProblemFact) lookUpTable.get(externalObject); return entityOrProblemFact == null ? externalObject : entityOrProblemFact; } - /** - * If the look-up result has been provided by a {@link #whenLookingUp(Object)} call, returns the defined object. - * Otherwise, returns null. - * - * @param externalObject entity or problem fact to look up - */ - @Override - public Optional - lookUpWorkingObject(@Nullable EntityOrProblemFact externalObject) { - return Optional.ofNullable((EntityOrProblemFact) lookUpTable.get(externalObject)); - } - @Override public void updateShadowVariables() { // Do nothing. } /** - * Defines what {@link #lookUpWorkingObjectOrFail(Object)} returns. + * Defines what {@link #lookUpWorkingObject(Object)} returns. */ public @NonNull LookUpMockBuilder whenLookingUp(Object forObject) { return new LookUpMockBuilder(forObject);