From 20883a945105c9274243673095a681419599e5a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Petrovick=C3=BD?= Date: Wed, 18 Feb 2026 14:06:40 +0100 Subject: [PATCH 1/4] refactor: remove SolverManager's ProblemId generic --- .../core/api/solver/SolutionManager.java | 6 +- .../solver/core/api/solver/SolverJob.java | 20 ++--- .../core/api/solver/SolverJobBuilder.java | 43 ++++------ .../solver/core/api/solver/SolverManager.java | 49 ++++------- .../config/solver/SolverManagerConfig.java | 21 ++--- .../core/impl/solver/ConsumerSupport.java | 54 ++++++------ .../impl/solver/DefaultSolutionManager.java | 4 +- .../core/impl/solver/DefaultSolverJob.java | 83 +++++++++--------- .../impl/solver/DefaultSolverJobBuilder.java | 68 ++++++++------- .../impl/solver/DefaultSolverManager.java | 84 +++++++++---------- .../core/api/solver/SolverManagerTest.java | 32 +++---- .../core/impl/solver/ConsumerSupportTest.java | 7 +- .../impl/solver/ProblemChangeBarrageIT.java | 6 +- ...arkProcessorMultipleSolversConfigTest.java | 4 +- .../jackson/it/TimefoldTestResource.java | 6 +- .../quarkus/deployment/TimefoldProcessor.java | 4 +- ...eclarativeShadowSolutionListSolveTest.java | 2 +- ...oldProcessorExtendedSolutionSolveTest.java | 4 +- ...TimefoldProcessorGizmoKitchenSinkTest.java | 6 +- .../quarkus/TimefoldProcessorInvalidTest.java | 2 +- ...ipleSolversInvalidConstraintClassTest.java | 4 +- ...MultipleSolversInvalidEntityClassTest.java | 4 +- ...ltipleSolversInvalidSolutionClassTest.java | 4 +- ...rocessorMultipleSolversPropertiesTest.java | 4 +- ...efoldProcessorMultipleSolversYamlTest.java | 4 +- ...foldProcessorOnlyMultiConstructorTest.java | 2 +- ...mefoldProcessorPrivateConstructorTest.java | 2 +- ...efoldProcessorShadowVariableSolveTest.java | 6 +- .../quarkus/TimefoldProcessorSolveTest.java | 6 +- .../TimefoldProcessorSolverResourcesTest.java | 7 +- ...ldProcessorSolverUnusedPropertiesTest.java | 2 +- ...SupplierShadowSolutionSimpleSolveTest.java | 2 +- ...imefoldProcessorUseGettersSettersTest.java | 2 +- ...taQuarkusShadowSolutionConfigResource.java | 12 +-- ...TestdataQuarkusSolutionConfigResource.java | 12 +-- .../TimefoldDevUIMultipleSolversTest.java | 4 +- .../quarkus/it/devui/TimefoldDevUITest.java | 4 +- .../quarkus/it/TimefoldTestResource.java | 6 +- .../it/reflection/TimefoldTestResource.java | 6 +- .../solver/quarkus/TimefoldRecorder.java | 4 +- .../bean/DefaultTimefoldBeanProvider.java | 6 +- .../bean/UnavailableTimefoldBeanProvider.java | 2 +- .../TimefoldSolverAotFactory.java | 2 +- .../TimefoldSolverBeanFactory.java | 2 +- ...erMultipleSolverAutoConfigurationTest.java | 42 +++++----- ...lverSingleSolverAutoConfigurationTest.java | 8 +- ...hSolverConfigXmlAutoConfigurationTest.java | 2 +- 47 files changed, 307 insertions(+), 359 deletions(-) diff --git a/core/src/main/java/ai/timefold/solver/core/api/solver/SolutionManager.java b/core/src/main/java/ai/timefold/solver/core/api/solver/SolutionManager.java index cf6e18da6f1..106e660b3f2 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/solver/SolutionManager.java +++ b/core/src/main/java/ai/timefold/solver/core/api/solver/SolutionManager.java @@ -5,7 +5,6 @@ import java.util.List; import java.util.Objects; -import java.util.UUID; import java.util.function.Function; import ai.timefold.solver.core.api.domain.entity.PlanningEntity; @@ -57,10 +56,9 @@ public interface SolutionManager> { * * @param the solution type, the class with the {@link PlanningSolution} annotation * @param the actual score type - * @param the ID type of a submitted problem, such as {@link Long} or {@link UUID} */ - static , ProblemId_> SolutionManager - create(SolverManager solverManager) { + static > SolutionManager + create(SolverManager solverManager) { return new DefaultSolutionManager<>(solverManager); } diff --git a/core/src/main/java/ai/timefold/solver/core/api/solver/SolverJob.java b/core/src/main/java/ai/timefold/solver/core/api/solver/SolverJob.java index 5f1104cb334..f4973883b85 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/solver/SolverJob.java +++ b/core/src/main/java/ai/timefold/solver/core/api/solver/SolverJob.java @@ -3,7 +3,6 @@ import java.time.Duration; import java.util.Collections; import java.util.List; -import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.function.Consumer; @@ -11,22 +10,21 @@ import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.solver.change.ProblemChange; -import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; /** * Represents a {@link PlanningSolution problem} that has been submitted to solve on the {@link SolverManager}. * * @param the solution type, the class with the {@link PlanningSolution} annotation - * @param the ID type of a submitted problem, such as {@link Long} or {@link UUID}. */ -public interface SolverJob { +@NullMarked +public interface SolverJob { /** * @return a value given to {@link SolverManager#solve(Object, Object, Consumer)} * or {@link SolverManager#solveAndListen(Object, Object, Consumer)} */ - @NonNull - ProblemId_ getProblemId(); + Object getProblemId(); /** * Returns whether the {@link Solver} is scheduled to solve, actively solving or not. @@ -34,15 +32,13 @@ public interface SolverJob { * Returns {@link SolverStatus#NOT_SOLVING} if the solver already terminated. * */ - @NonNull SolverStatus getSolverStatus(); /** * As defined by {@link #addProblemChanges(List)}, only for a single problem change. * Prefer to submit multiple {@link ProblemChange}s at once to reduce the considerable overhead of multiple calls. */ - @NonNull - default CompletableFuture addProblemChange(@NonNull ProblemChange problemChange) { + default CompletableFuture addProblemChange(ProblemChange problemChange) { return addProblemChanges(Collections.singletonList(problemChange)); } @@ -56,8 +52,7 @@ default CompletableFuture addProblemChange(@NonNull ProblemChange addProblemChanges(@NonNull List> problemChangeList); + CompletableFuture addProblemChanges(List> problemChangeList); /** * Terminates the solver or cancels the solver job if it hasn't (re)started yet. @@ -85,7 +80,6 @@ default CompletableFuture addProblemChange(@NonNull ProblemChange addProblemChange(@NonNull ProblemChange addProblemChange(@NonNull ProblemChange the solution type, the class with the {@link PlanningSolution} annotation - * @param the ID type of submitted problem, such as {@link Long} or {@link UUID}. */ -public interface SolverJobBuilder { +@NullMarked +public interface SolverJobBuilder { /** * Sets the problem id. @@ -37,8 +36,7 @@ public interface SolverJobBuilder { * @param problemId a ID for each planning problem. This must be unique. * @return this */ - @NonNull - SolverJobBuilder withProblemId(@NonNull ProblemId_ problemId); + SolverJobBuilder withProblemId(Object problemId); /** * Sets the problem definition. @@ -46,7 +44,7 @@ public interface SolverJobBuilder { * @param problem a {@link PlanningSolution} usually with uninitialized planning variables * @return this */ - default @NonNull SolverJobBuilder withProblem(@NonNull Solution_ problem) { + default SolverJobBuilder withProblem(Solution_ problem) { return withProblemFinder(id -> problem); } @@ -56,9 +54,7 @@ public interface SolverJobBuilder { * @param problemFinder a function that returns a {@link PlanningSolution}, usually with uninitialized planning variables * @return this */ - @NonNull - SolverJobBuilder - withProblemFinder(@NonNull Function problemFinder); + SolverJobBuilder withProblemFinder(Function problemFinder); /** * Sets the best solution consumer, which may be called multiple times during the solving process. @@ -70,9 +66,8 @@ public interface SolverJobBuilder { * @param bestSolutionEventConsumer called multiple times for each new best solution on a consumer thread * @return this */ - @NonNull - SolverJobBuilder - withBestSolutionEventConsumer(@NonNull Consumer> bestSolutionEventConsumer); + SolverJobBuilder + withBestSolutionEventConsumer(Consumer> bestSolutionEventConsumer); /** * Sets the final best solution consumer, which is called at the end of the solving process and returns the final @@ -81,10 +76,8 @@ public interface SolverJobBuilder { * @param finalBestSolutionEventConsumer called only once at the end of the solving process on a consumer thread * @return this */ - @NonNull - SolverJobBuilder - withFinalBestSolutionEventConsumer( - @NonNull Consumer> finalBestSolutionEventConsumer); + SolverJobBuilder + withFinalBestSolutionEventConsumer(Consumer> finalBestSolutionEventConsumer); /** * Sets the consumer of the first initialized solution, @@ -95,8 +88,8 @@ public interface SolverJobBuilder { * @param firstInitializedSolutionEventConsumer called only once before starting the first Local Search phase * @return this */ - SolverJobBuilder withFirstInitializedSolutionEventConsumer( - @NonNull Consumer> firstInitializedSolutionEventConsumer); + SolverJobBuilder withFirstInitializedSolutionEventConsumer( + Consumer> firstInitializedSolutionEventConsumer); /** * Sets the consumer for when the solver starts its solving process. @@ -104,7 +97,7 @@ SolverJobBuilder withFirstInitializedSolutionEventConsume * @param solverJobStartedConsumer never null, called only once when the solver is starting the solving process * @return this, never null */ - SolverJobBuilder + SolverJobBuilder withSolverJobStartedEventConsumer(Consumer> solverJobStartedConsumer); /** @@ -114,9 +107,7 @@ SolverJobBuilder withFirstInitializedSolutionEventConsume * exception as an error. * @return this */ - @NonNull - SolverJobBuilder - withExceptionHandler(@NonNull BiConsumer exceptionHandler); + SolverJobBuilder withExceptionHandler(BiConsumer exceptionHandler); /** * Sets the solver config override. @@ -124,14 +115,12 @@ SolverJobBuilder withFirstInitializedSolutionEventConsume * @param solverConfigOverride allows overriding the default behavior of {@link Solver} * @return this */ - @NonNull - SolverJobBuilder withConfigOverride(@NonNull SolverConfigOverride solverConfigOverride); + SolverJobBuilder withConfigOverride(SolverConfigOverride solverConfigOverride); /** * Submits a planning problem to solve and returns immediately. The planning problem is solved on a solver {@link Thread}, * as soon as one is available. */ - @NonNull - SolverJob run(); + SolverJob run(); } diff --git a/core/src/main/java/ai/timefold/solver/core/api/solver/SolverManager.java b/core/src/main/java/ai/timefold/solver/core/api/solver/SolverManager.java index ab1bb0ed3ec..d2ac94eb73e 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/solver/SolverManager.java +++ b/core/src/main/java/ai/timefold/solver/core/api/solver/SolverManager.java @@ -2,7 +2,6 @@ import java.util.Collections; import java.util.List; -import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; @@ -32,10 +31,9 @@ * To learn more about problem change semantics, please refer to the {@link ProblemChange} Javadoc. * * @param the solution type, the class with the {@link PlanningSolution} annotation - * @param the ID type of a submitted problem, such as {@link Long} or {@link UUID}. */ @NullMarked -public interface SolverManager extends AutoCloseable { +public interface SolverManager extends AutoCloseable { // ************************************************************************ // Static creation methods: SolverConfig and SolverFactory @@ -48,9 +46,8 @@ public interface SolverManager extends AutoCloseable { * so they reuse the same {@link SolverFactory} instance. * * @param the solution type, the class with the {@link PlanningSolution} annotation - * @param the ID type of a submitted problem, such as {@link Long} or {@link UUID} */ - static SolverManager create(SolverConfig solverConfig) { + static SolverManager create(SolverConfig solverConfig) { return create(solverConfig, new SolverManagerConfig()); } @@ -61,10 +58,8 @@ static SolverManager create(Solve * so they reuse the same {@link SolverFactory} instance. * * @param the solution type, the class with the {@link PlanningSolution} annotation - * @param the ID type of a submitted problem, such as {@link Long} or {@link UUID}. */ - static SolverManager create(SolverConfig solverConfig, - SolverManagerConfig solverManagerConfig) { + static SolverManager create(SolverConfig solverConfig, SolverManagerConfig solverManagerConfig) { return create(SolverFactory.create(solverConfig), solverManagerConfig); } @@ -72,9 +67,8 @@ static SolverManager create(Solve * Use a {@link SolverFactory} to build a {@link SolverManager}. * * @param the solution type, the class with the {@link PlanningSolution} annotation - * @param the ID type of a submitted problem, such as {@link Long} or {@link UUID} */ - static SolverManager create(SolverFactory solverFactory) { + static SolverManager create(SolverFactory solverFactory) { return create(solverFactory, new SolverManagerConfig()); } @@ -82,9 +76,8 @@ static SolverManager create(Solve * Use a {@link SolverFactory} and a {@link SolverManagerConfig} to build a {@link SolverManager}. * * @param the solution type, the class with the {@link PlanningSolution} annotation - * @param the ID type of a submitted problem, such as {@link Long} or {@link UUID}. */ - static SolverManager create(SolverFactory solverFactory, + static SolverManager create(SolverFactory solverFactory, SolverManagerConfig solverManagerConfig) { return new DefaultSolverManager<>(solverFactory, solverManagerConfig); } @@ -97,7 +90,7 @@ static SolverManager create(Solve * Creates a Builder that allows to customize and submit a planning problem to solve. * */ - SolverJobBuilder solveBuilder(); + SolverJobBuilder solveBuilder(); // ************************************************************************ // Interface methods @@ -120,11 +113,8 @@ static SolverManager create(Solve * Use this problemId to {@link #terminateEarly(Object) terminate} the solver early, * @param problem a {@link PlanningSolution} usually with uninitialized planning variables */ - default SolverJob solve(ProblemId_ problemId, Solution_ problem) { - return solveBuilder() - .withProblemId(problemId) - .withProblem(problem) - .run(); + default SolverJob solve(Object problemId, Solution_ problem) { + return solveBuilder().withProblemId(problemId).withProblem(problem).run(); } /** @@ -136,11 +126,9 @@ default SolverJob solve(ProblemId_ problemId, Solution_ p * @param problem a {@link PlanningSolution} usually with uninitialized planning variables * @param finalBestSolutionConsumer called only once, at the end, on a consumer thread */ - default SolverJob solve(ProblemId_ problemId, Solution_ problem, + default SolverJob solve(Object problemId, Solution_ problem, @Nullable Consumer finalBestSolutionConsumer) { - SolverJobBuilder builder = solveBuilder() - .withProblemId(problemId) - .withProblem(problem); + var builder = solveBuilder().withProblemId(problemId).withProblem(problem); if (finalBestSolutionConsumer != null) { builder.withFinalBestSolutionEventConsumer(event -> finalBestSolutionConsumer.accept(event.solution())); } @@ -165,13 +153,10 @@ default SolverJob solve(ProblemId_ problemId, Solution_ p * @param problem a {@link PlanningSolution} usually with uninitialized planning variables * @param bestSolutionConsumer called multiple times, on a consumer thread */ - default SolverJob solveAndListen(ProblemId_ problemId, Solution_ problem, + default SolverJob solveAndListen(Object problemId, Solution_ problem, Consumer bestSolutionConsumer) { - return solveBuilder() - .withProblemId(problemId) - .withProblem(problem) - .withBestSolutionEventConsumer(event -> bestSolutionConsumer.accept(event.solution())) - .run(); + return solveBuilder().withProblemId(problemId).withProblem(problem) + .withBestSolutionEventConsumer(event -> bestSolutionConsumer.accept(event.solution())).run(); } /** @@ -184,13 +169,13 @@ default SolverJob solveAndListen(ProblemId_ problemId, So * @param problemId a value given to {@link #solve(Object, Object, Consumer)} * or {@link #solveAndListen(Object, Object, Consumer)} */ - SolverStatus getSolverStatus(ProblemId_ problemId); + SolverStatus getSolverStatus(Object problemId); /** * As defined by {@link #addProblemChanges(Object, List)}, only with a single {@link ProblemChange}. * Prefer to submit multiple {@link ProblemChange}s at once to reduce the considerable overhead of multiple calls. */ - default CompletableFuture addProblemChange(ProblemId_ problemId, ProblemChange problemChange) { + default CompletableFuture addProblemChange(Object problemId, ProblemChange problemChange) { return addProblemChanges(problemId, Collections.singletonList(problemChange)); } @@ -207,7 +192,7 @@ default CompletableFuture addProblemChange(ProblemId_ problemId, ProblemCh * @throws IllegalStateException if there is no solver actively solving the problem associated with the problemId * @see ProblemChange Learn more about problem change semantics. */ - CompletableFuture addProblemChanges(ProblemId_ problemId, List> problemChangeList); + CompletableFuture addProblemChanges(Object problemId, List> problemChangeList); /** * Terminates the solver or cancels the solver job if it hasn't (re)started yet. @@ -225,7 +210,7 @@ default CompletableFuture addProblemChange(ProblemId_ problemId, ProblemCh * @param problemId a value given to {@link #solve(Object, Object, Consumer)} * or {@link #solveAndListen(Object, Object, Consumer)} */ - void terminateEarly(ProblemId_ problemId); + void terminateEarly(Object problemId); /** * Terminates all solvers, cancels all solver jobs that haven't (re)started yet diff --git a/core/src/main/java/ai/timefold/solver/core/config/solver/SolverManagerConfig.java b/core/src/main/java/ai/timefold/solver/core/config/solver/SolverManagerConfig.java index 47645ecfdd0..eadb4b8b443 100644 --- a/core/src/main/java/ai/timefold/solver/core/config/solver/SolverManagerConfig.java +++ b/core/src/main/java/ai/timefold/solver/core/config/solver/SolverManagerConfig.java @@ -71,19 +71,16 @@ public void setThreadFactoryClass(@Nullable Class threa // Builder methods // ************************************************************************ - public @NonNull Integer resolveParallelSolverCount() { - int availableProcessorCount = getAvailableProcessors(); - Integer resolvedParallelSolverCount; - if (parallelSolverCount == null || parallelSolverCount.equals(PARALLEL_SOLVER_COUNT_AUTO)) { - resolvedParallelSolverCount = resolveParallelSolverCountAutomatically(availableProcessorCount); - } else { - resolvedParallelSolverCount = ConfigUtils.resolvePoolSize("parallelSolverCount", - parallelSolverCount, PARALLEL_SOLVER_COUNT_AUTO); - } + public int resolveParallelSolverCount() { + var availableProcessorCount = getAvailableProcessors(); + var resolvedParallelSolverCount = + (parallelSolverCount == null || parallelSolverCount.equals(PARALLEL_SOLVER_COUNT_AUTO)) + ? resolveParallelSolverCountAutomatically(availableProcessorCount) + : ConfigUtils.resolvePoolSize("parallelSolverCount", parallelSolverCount, PARALLEL_SOLVER_COUNT_AUTO); if (resolvedParallelSolverCount < 1) { - throw new IllegalArgumentException("The parallelSolverCount (" + parallelSolverCount - + ") resulted in a resolvedParallelSolverCount (" + resolvedParallelSolverCount - + ") that is lower than 1."); + throw new IllegalArgumentException( + "The parallelSolverCount (%s) resulted in a resolvedParallelSolverCount (%d) that is lower than 1." + .formatted(parallelSolverCount, resolvedParallelSolverCount)); } if (resolvedParallelSolverCount > availableProcessorCount) { LOGGER.warn("The resolvedParallelSolverCount ({}) is higher " diff --git a/core/src/main/java/ai/timefold/solver/core/impl/solver/ConsumerSupport.java b/core/src/main/java/ai/timefold/solver/core/impl/solver/ConsumerSupport.java index c39cea1e12f..b6c51231b90 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/solver/ConsumerSupport.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/solver/ConsumerSupport.java @@ -14,36 +14,38 @@ import ai.timefold.solver.core.api.solver.event.NewBestSolutionEvent; import ai.timefold.solver.core.api.solver.event.SolverJobStartedEvent; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked final class ConsumerSupport implements AutoCloseable { private final ProblemId_ problemId; - private final Consumer> bestSolutionConsumer; + private final @Nullable Consumer> bestSolutionConsumer; private final Consumer> finalBestSolutionConsumer; private final Consumer> firstInitializedSolutionConsumer; - private final Consumer> solverJobStartedConsumer; + private final @Nullable Consumer> solverJobStartedConsumer; private final BiConsumer exceptionHandler; private final Semaphore activeConsumption = new Semaphore(1); private final Semaphore firstSolutionConsumption = new Semaphore(1); private final Semaphore startSolverJobConsumption = new Semaphore(1); private final BestSolutionHolder bestSolutionHolder; private final ExecutorService consumerExecutor = Executors.newSingleThreadExecutor(); - private Solution_ firstInitializedSolution; - private Solution_ initialSolution; + private @Nullable Solution_ firstInitializedSolution; + private @Nullable Solution_ initialSolution; - public ConsumerSupport(ProblemId_ problemId, - Consumer> bestSolutionConsumer, - Consumer> finalBestSolutionConsumer, - Consumer> firstInitializedSolutionConsumer, - Consumer> solverJobStartedConsumer, + public ConsumerSupport(ProblemId_ problemId, @Nullable Consumer> bestSolutionConsumer, + @Nullable Consumer> finalBestSolutionConsumer, + @Nullable Consumer> firstInitializedSolutionConsumer, + @Nullable Consumer> solverJobStartedConsumer, BiConsumer exceptionHandler, BestSolutionHolder bestSolutionHolder) { this.problemId = problemId; this.bestSolutionConsumer = bestSolutionConsumer; this.finalBestSolutionConsumer = finalBestSolutionConsumer == null ? finalBestSolution -> { } : finalBestSolutionConsumer; - this.firstInitializedSolutionConsumer = - firstInitializedSolutionConsumer == null ? event -> { - } : firstInitializedSolutionConsumer; + this.firstInitializedSolutionConsumer = firstInitializedSolutionConsumer == null ? event -> { + } : firstInitializedSolutionConsumer; this.solverJobStartedConsumer = solverJobStartedConsumer; this.exceptionHandler = exceptionHandler; this.bestSolutionHolder = bestSolutionHolder; @@ -77,9 +79,8 @@ void consumeFirstInitializedSolution(Solution_ firstInitializedSolution, EventPr } // called on the Consumer thread this.firstInitializedSolution = firstInitializedSolution; - scheduleFirstInitializedSolutionConsumption( - solution -> firstInitializedSolutionConsumer - .accept(new FirstInitializedSolutionEventImpl<>(solution, producerId, isTerminatedEarly))); + scheduleFirstInitializedSolutionConsumption(solution -> firstInitializedSolutionConsumer + .accept(new FirstInitializedSolutionEventImpl<>(solution, producerId, isTerminatedEarly))); } // Called on the consumer thread @@ -152,14 +153,14 @@ private CompletableFuture scheduleIntermediateBestSolutionConsumption() { BestSolutionContainingProblemChanges bestSolutionContainingProblemChanges = bestSolutionHolder.take(); if (bestSolutionContainingProblemChanges != null) { try { - bestSolutionConsumer - .accept(new NewBestSolutionEventImpl<>(bestSolutionContainingProblemChanges.getBestSolution(), - bestSolutionContainingProblemChanges.getProducerId())); + if (bestSolutionConsumer != null) { + bestSolutionConsumer + .accept(new NewBestSolutionEventImpl<>(bestSolutionContainingProblemChanges.getBestSolution(), + bestSolutionContainingProblemChanges.getProducerId())); + } bestSolutionContainingProblemChanges.completeProblemChanges(); } catch (Throwable throwable) { - if (exceptionHandler != null) { - exceptionHandler.accept(problemId, throwable); - } + exceptionHandler.accept(problemId, throwable); bestSolutionContainingProblemChanges.completeProblemChangesExceptionally(throwable); } finally { activeConsumption.release(); @@ -173,8 +174,7 @@ private CompletableFuture scheduleIntermediateBestSolutionConsumption() { * Don't call without locking firstSolutionConsumption, * because the consumption may not be executed before the final best solution is executed. */ - private void scheduleFirstInitializedSolutionConsumption( - Consumer solutionConsumer) { + private void scheduleFirstInitializedSolutionConsumption(Consumer solutionConsumer) { scheduleConsumption(firstSolutionConsumption, solutionConsumer, firstInitializedSolution); } @@ -190,17 +190,15 @@ private void scheduleStartJobConsumption() { initialSolution); } - private void scheduleConsumption(Semaphore semaphore, Consumer consumer, - Solution_ solution) { + private void scheduleConsumption(Semaphore semaphore, @Nullable Consumer consumer, + @Nullable Solution_ solution) { CompletableFuture.runAsync(() -> { try { if (consumer != null && solution != null) { consumer.accept(solution); } } catch (Throwable throwable) { - if (exceptionHandler != null) { - exceptionHandler.accept(problemId, throwable); - } + exceptionHandler.accept(problemId, throwable); } finally { semaphore.release(); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolutionManager.java b/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolutionManager.java index 09122c57c5e..d1a066762db 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolutionManager.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolutionManager.java @@ -39,8 +39,8 @@ public final class DefaultSolutionManager solverFactory; private final ScoreDirectorFactory scoreDirectorFactory; - public DefaultSolutionManager(SolverManager solverManager) { - this(((DefaultSolverManager) solverManager).getSolverFactory()); + public DefaultSolutionManager(SolverManager solverManager) { + this(((DefaultSolverManager) solverManager).getSolverFactory()); } public DefaultSolutionManager(SolverFactory solverFactory) { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverJob.java b/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverJob.java index 78d140182a8..a22fc1b2a30 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverJob.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverJob.java @@ -3,7 +3,6 @@ import java.time.Duration; import java.util.List; import java.util.Objects; -import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; @@ -36,46 +35,46 @@ import ai.timefold.solver.core.impl.solver.scope.SolverScope; import ai.timefold.solver.core.impl.solver.termination.SolverTermination; -import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @param the solution type, the class with the {@link PlanningSolution} annotation - * @param the ID type of submitted problem, such as {@link Long} or {@link UUID}. */ -public final class DefaultSolverJob implements SolverJob, Callable { +@NullMarked +public final class DefaultSolverJob implements SolverJob, Callable { private static final Logger LOGGER = LoggerFactory.getLogger(DefaultSolverJob.class); - private final DefaultSolverManager solverManager; + private final DefaultSolverManager solverManager; private final DefaultSolver solver; - private final ProblemId_ problemId; - private final Function problemFinder; - private final Consumer> bestSolutionConsumer; - private final Consumer> finalBestSolutionConsumer; - private final Consumer> firstInitializedSolutionConsumer; - private final Consumer> solverJobStartedConsumer; - private final BiConsumer exceptionHandler; + private final Object problemId; + private final Function problemFinder; + private final @Nullable Consumer> bestSolutionConsumer; + private final @Nullable Consumer> finalBestSolutionConsumer; + private final @Nullable Consumer> firstInitializedSolutionConsumer; + private final @Nullable Consumer> solverJobStartedConsumer; + private final BiConsumer exceptionHandler; private volatile SolverStatus solverStatus; private final CountDownLatch terminatedLatch; private final ReentrantLock solverStatusModifyingLock; - private Future finalBestSolutionFuture; - private ConsumerSupport consumerSupport; private final AtomicBoolean terminatedEarly = new AtomicBoolean(false); private final BestSolutionHolder bestSolutionHolder = new BestSolutionHolder<>(); - private final AtomicReference temporaryProblemSizeStatistics = new AtomicReference<>(); - - public DefaultSolverJob( - DefaultSolverManager solverManager, - Solver solver, ProblemId_ problemId, - Function problemFinder, - Consumer> bestSolutionConsumer, - Consumer> finalBestSolutionConsumer, - Consumer> firstInitializedSolutionConsumer, - Consumer> solverJobStartedConsumer, - BiConsumer exceptionHandler) { + private final AtomicReference<@Nullable ProblemSizeStatistics> temporaryProblemSizeStatistics = new AtomicReference<>(); + + private @Nullable Future finalBestSolutionFuture; + private @Nullable ConsumerSupport consumerSupport; + + public DefaultSolverJob(DefaultSolverManager solverManager, Solver solver, Object problemId, + Function problemFinder, + @Nullable Consumer> bestSolutionConsumer, + @Nullable Consumer> finalBestSolutionConsumer, + @Nullable Consumer> firstInitializedSolutionConsumer, + @Nullable Consumer> solverJobStartedConsumer, + BiConsumer exceptionHandler) { this.solverManager = solverManager; this.problemId = problemId; if (!(solver instanceof DefaultSolver)) { @@ -99,12 +98,12 @@ public void setFinalBestSolutionFuture(Future finalBestSolutionFuture } @Override - public @NonNull ProblemId_ getProblemId() { + public Object getProblemId() { return problemId; } @Override - public @NonNull SolverStatus getSolverStatus() { + public SolverStatus getSolverStatus() { return solverStatus; } @@ -154,8 +153,7 @@ public Solution_ call() { private void onBestSolutionChangedEvent(BestSolutionChangedEvent bestSolutionChangedEvent) { consumerSupport.consumeIntermediateBestSolution(bestSolutionChangedEvent.getNewBestSolution(), - bestSolutionChangedEvent.getProducerId(), - bestSolutionChangedEvent::isEveryProblemChangeProcessed); + bestSolutionChangedEvent.getProducerId(), bestSolutionChangedEvent::isEveryProblemChangeProcessed); } private void solvingTerminated() { @@ -166,12 +164,12 @@ private void solvingTerminated() { } @Override - public @NonNull CompletableFuture addProblemChanges(@NonNull List> problemChangeList) { - Objects.requireNonNull(problemChangeList, () -> "A problem change list for problem (%s) must not be null." - .formatted(problemId)); + public CompletableFuture addProblemChanges(List> problemChangeList) { + Objects.requireNonNull(problemChangeList, + () -> "A problem change list for problem (%s) must not be null.".formatted(problemId)); if (problemChangeList.isEmpty()) { - throw new IllegalArgumentException("The problem change list for problem (%s) must not be empty." - .formatted(problemId)); + throw new IllegalArgumentException( + "The problem change list for problem (%s) must not be empty.".formatted(problemId)); } else if (solverStatus == SolverStatus.NOT_SOLVING) { throw new IllegalStateException("Cannot add the problem changes (%s) because the solver job (%s) is not solving." .formatted(problemChangeList, solverStatus)); @@ -219,7 +217,7 @@ public boolean isTerminatedEarly() { } @Override - public @NonNull Solution_ getFinalBestSolution() throws InterruptedException, ExecutionException { + public Solution_ getFinalBestSolution() throws InterruptedException, ExecutionException { try { return finalBestSolutionFuture.get(); } catch (CancellationException cancellationException) { @@ -230,7 +228,7 @@ public boolean isTerminatedEarly() { } @Override - public @NonNull Duration getSolvingDuration() { + public Duration getSolvingDuration() { return Duration.ofMillis(solver.getTimeMillisSpent()); } @@ -255,7 +253,7 @@ public long getMoveEvaluationSpeed() { } @Override - public @NonNull ProblemSizeStatistics getProblemSizeStatistics() { + public ProblemSizeStatistics getProblemSizeStatistics() { var solverScope = solver.getSolverScope(); var problemSizeStatistics = solverScope.getProblemSizeStatistics(); if (problemSizeStatistics != null) { @@ -329,9 +327,9 @@ public void solvingStarted(SolverScope solverScope) { */ private final class FirstInitializedSolutionPhaseLifecycleListener extends PhaseLifecycleListenerAdapter { - private final ConsumerSupport consumerSupport; + private final ConsumerSupport consumerSupport; - public FirstInitializedSolutionPhaseLifecycleListener(ConsumerSupport consumerSupport) { + public FirstInitializedSolutionPhaseLifecycleListener(ConsumerSupport consumerSupport) { this.consumerSupport = consumerSupport; } @@ -348,8 +346,7 @@ public void phaseEnded(AbstractPhaseScope phaseScope) { // The Solver thread calls the method, // but the consumption is done asynchronously by the Consumer thread. // Only happens if the phase initializes the solution. - consumerSupport.consumeFirstInitializedSolution(phaseScope.getWorkingSolution(), - phaseScope.getPhaseId(), + consumerSupport.consumeFirstInitializedSolution(phaseScope.getWorkingSolution(), phaseScope.getPhaseId(), possiblyInitializingPhase.getTerminationStatus().early()); } } @@ -360,9 +357,9 @@ public void phaseEnded(AbstractPhaseScope phaseScope) { */ private final class StartSolverJobPhaseLifecycleListener extends PhaseLifecycleListenerAdapter { - private final ConsumerSupport consumerSupport; + private final ConsumerSupport consumerSupport; - public StartSolverJobPhaseLifecycleListener(ConsumerSupport consumerSupport) { + public StartSolverJobPhaseLifecycleListener(ConsumerSupport consumerSupport) { this.consumerSupport = consumerSupport; } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverJobBuilder.java b/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverJobBuilder.java index b80e5d162ad..2f4c8c2a521 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverJobBuilder.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverJobBuilder.java @@ -1,7 +1,6 @@ package ai.timefold.solver.core.impl.solver; import java.util.Objects; -import java.util.UUID; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; @@ -15,69 +14,70 @@ import ai.timefold.solver.core.api.solver.event.NewBestSolutionEvent; import ai.timefold.solver.core.api.solver.event.SolverJobStartedEvent; -import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; /** * @param the solution type, the class with the {@link PlanningSolution} annotation - * @param the ID type of submitted problem, such as {@link Long} or {@link UUID}. */ -public final class DefaultSolverJobBuilder implements SolverJobBuilder { - - private final DefaultSolverManager solverManager; - private ProblemId_ problemId; - private Function problemFinder; - private Consumer> bestSolutionConsumer; - private Consumer> finalBestSolutionConsumer; - private Consumer> initializedSolutionConsumer; - private Consumer> solverJobStartedConsumer; - private BiConsumer exceptionHandler; - private SolverConfigOverride solverConfigOverride; - - public DefaultSolverJobBuilder(DefaultSolverManager solverManager) { +@NullMarked +public final class DefaultSolverJobBuilder implements SolverJobBuilder { + + private final DefaultSolverManager solverManager; + private @Nullable Object problemId; + private @Nullable Function problemFinder; + private @Nullable Consumer> bestSolutionConsumer; + private @Nullable Consumer> finalBestSolutionConsumer; + private @Nullable Consumer> initializedSolutionConsumer; + private @Nullable Consumer> solverJobStartedConsumer; + private @Nullable BiConsumer exceptionHandler; + private @Nullable SolverConfigOverride solverConfigOverride; + + public DefaultSolverJobBuilder(DefaultSolverManager solverManager) { this.solverManager = Objects.requireNonNull(solverManager, "The SolverManager (" + solverManager + ") cannot be null."); } @Override - public @NonNull SolverJobBuilder withProblemId(@NonNull ProblemId_ problemId) { + public SolverJobBuilder withProblemId(Object problemId) { this.problemId = Objects.requireNonNull(problemId, "Invalid problemId (null) given to SolverJobBuilder."); return this; } @Override - public @NonNull SolverJobBuilder - withProblemFinder(@NonNull Function problemFinder) { + public SolverJobBuilder + withProblemFinder(Function problemFinder) { this.problemFinder = Objects.requireNonNull(problemFinder, "Invalid problemFinder (null) given to SolverJobBuilder."); return this; } @Override - public @NonNull SolverJobBuilder - withBestSolutionEventConsumer(@NonNull Consumer> bestSolutionConsumer) { + public SolverJobBuilder + withBestSolutionEventConsumer(Consumer> bestSolutionConsumer) { this.bestSolutionConsumer = Objects.requireNonNull(bestSolutionConsumer, "Invalid bestSolutionConsumer (null) given to SolverJobBuilder."); return this; } @Override - public @NonNull SolverJobBuilder + public SolverJobBuilder withFinalBestSolutionEventConsumer( - @NonNull Consumer> finalBestSolutionConsumer) { + Consumer> finalBestSolutionConsumer) { this.finalBestSolutionConsumer = Objects.requireNonNull(finalBestSolutionConsumer, "Invalid finalBestSolutionConsumer (null) given to SolverJobBuilder."); return this; } @Override - public @NonNull SolverJobBuilder + public SolverJobBuilder withFirstInitializedSolutionEventConsumer( - @NonNull Consumer> firstInitializedSolutionConsumer) { + Consumer> firstInitializedSolutionConsumer) { this.initializedSolutionConsumer = Objects.requireNonNull(firstInitializedSolutionConsumer, "Invalid initializedSolutionConsumer (null) given to SolverJobBuilder."); return this; } @Override - public SolverJobBuilder + public SolverJobBuilder withSolverJobStartedEventConsumer(Consumer> solverJobStartedConsumer) { this.solverJobStartedConsumer = Objects.requireNonNull(solverJobStartedConsumer, "Invalid startSolverJobHandler (null) given to SolverJobBuilder."); @@ -85,23 +85,29 @@ public DefaultSolverJobBuilder(DefaultSolverManager solve } @Override - public @NonNull SolverJobBuilder - withExceptionHandler(@NonNull BiConsumer exceptionHandler) { + public SolverJobBuilder + withExceptionHandler(BiConsumer exceptionHandler) { this.exceptionHandler = Objects.requireNonNull(exceptionHandler, "Invalid exceptionHandler (null) given to SolverJobBuilder."); return this; } @Override - public @NonNull SolverJobBuilder - withConfigOverride(@NonNull SolverConfigOverride solverConfigOverride) { + public SolverJobBuilder + withConfigOverride(SolverConfigOverride solverConfigOverride) { this.solverConfigOverride = Objects.requireNonNull(solverConfigOverride, "Invalid solverConfigOverride (null) given to SolverJobBuilder."); return this; } @Override - public @NonNull SolverJob run() { + public SolverJob run() { + if (problemId == null) { + throw new IllegalStateException("The problemId is required."); + } + if (problemFinder == null) { + throw new IllegalStateException("The problemFinder function is required."); + } if (solverConfigOverride == null) { // The config is required by SolverFactory and it must be initialized this.solverConfigOverride = new SolverConfigOverride<>(); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverManager.java b/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverManager.java index 4ef9c250744..df4ab1bf1e9 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverManager.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverManager.java @@ -3,7 +3,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -28,22 +27,23 @@ import ai.timefold.solver.core.config.solver.SolverManagerConfig; import ai.timefold.solver.core.config.util.ConfigUtils; -import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @param the solution type, the class with the {@link PlanningSolution} annotation - * @param the ID type of submitted problem, such as {@link Long} or {@link UUID}. */ -public final class DefaultSolverManager implements SolverManager { +@NullMarked +public final class DefaultSolverManager implements SolverManager { private static final Logger LOGGER = LoggerFactory.getLogger(DefaultSolverManager.class); - private final BiConsumer defaultExceptionHandler; + private final BiConsumer defaultExceptionHandler; private final SolverFactory solverFactory; private final ExecutorService solverThreadPool; - private final ConcurrentMap> problemIdToSolverJobMap; + private final ConcurrentMap> problemIdToSolverJobMap; public DefaultSolverManager(SolverFactory solverFactory, SolverManagerConfig solverManagerConfig) { this.defaultExceptionHandler = @@ -66,65 +66,58 @@ private void validateSolverFactory() { solverFactory.buildSolver(); } - private ProblemId_ getProblemIdOrThrow(ProblemId_ problemId) { + private Object getProblemIdOrThrow(Object problemId) { return Objects.requireNonNull(problemId, "Invalid problemId (null) given to SolverManager."); } - private DefaultSolverJob getSolverJob(ProblemId_ problemId) { + private @Nullable DefaultSolverJob getSolverJob(Object problemId) { return problemIdToSolverJobMap.get(getProblemIdOrThrow(problemId)); } @Override - public @NonNull SolverJobBuilder solveBuilder() { + public SolverJobBuilder solveBuilder() { return new DefaultSolverJobBuilder<>(this); } - SolverJob solveAndListen(ProblemId_ problemId, - Function problemFinder, + SolverJob solveAndListen(Object problemId, Function problemFinder, Consumer> bestSolutionConsumer, - Consumer> finalBestSolutionConsumer, - Consumer> initializedSolutionConsumer, - Consumer> solverJobStartedConsumer, - BiConsumer exceptionHandler, + @Nullable Consumer> finalBestSolutionConsumer, + @Nullable Consumer> initializedSolutionConsumer, + @Nullable Consumer> solverJobStartedConsumer, + @Nullable BiConsumer exceptionHandler, SolverConfigOverride solverConfigOverride) { - if (bestSolutionConsumer == null) { - throw new IllegalStateException("The consumer bestSolutionConsumer is required."); - } - return solve(getProblemIdOrThrow(problemId), problemFinder, bestSolutionConsumer, finalBestSolutionConsumer, - initializedSolutionConsumer, solverJobStartedConsumer, exceptionHandler, solverConfigOverride); + return solve(problemId, problemFinder, bestSolutionConsumer, finalBestSolutionConsumer, initializedSolutionConsumer, + solverJobStartedConsumer, exceptionHandler, solverConfigOverride); } - SolverJob solve(ProblemId_ problemId, - Function problemFinder, - Consumer> bestSolutionConsumer, - Consumer> finalBestSolutionConsumer, - Consumer> initializedSolutionConsumer, - Consumer> solverJobStartedConsumer, - BiConsumer exceptionHandler, + SolverJob solve(Object problemId, Function problemFinder, + @Nullable Consumer> bestSolutionConsumer, + @Nullable Consumer> finalBestSolutionConsumer, + @Nullable Consumer> initializedSolutionConsumer, + @Nullable Consumer> solverJobStartedConsumer, + @Nullable BiConsumer exceptionHandler, SolverConfigOverride configOverride) { var solver = solverFactory.buildSolver(configOverride); ((DefaultSolver) solver).setMonitorTagMap(Map.of("problem.id", problemId.toString())); - BiConsumer finalExceptionHandler = (exceptionHandler != null) - ? exceptionHandler - : defaultExceptionHandler; - var solverJob = problemIdToSolverJobMap - .compute(problemId, (key, oldSolverJob) -> { - if (oldSolverJob != null) { - // TODO Future features: automatically restart solving by calling reloadProblem() - throw new IllegalStateException("The problemId (" + problemId + ") is already solving."); - } else { - return new DefaultSolverJob<>(this, solver, problemId, problemFinder, bestSolutionConsumer, - finalBestSolutionConsumer, initializedSolutionConsumer, solverJobStartedConsumer, - finalExceptionHandler); - } - }); + BiConsumer finalExceptionHandler = + (exceptionHandler != null) ? exceptionHandler : defaultExceptionHandler; + var solverJob = problemIdToSolverJobMap.compute(problemId, (key, oldSolverJob) -> { + if (oldSolverJob != null) { + // TODO Future features: automatically restart solving by calling reloadProblem() + throw new IllegalStateException("The problemId (%s) is already solving.".formatted(problemId)); + } else { + return new DefaultSolverJob<>(this, solver, problemId, problemFinder, bestSolutionConsumer, + finalBestSolutionConsumer, initializedSolutionConsumer, solverJobStartedConsumer, + finalExceptionHandler); + } + }); var future = solverThreadPool.submit(solverJob); solverJob.setFinalBestSolutionFuture(future); return solverJob; } @Override - public @NonNull SolverStatus getSolverStatus(@NonNull ProblemId_ problemId) { + public SolverStatus getSolverStatus(Object problemId) { var solverJob = getSolverJob(problemId); if (solverJob == null) { return SolverStatus.NOT_SOLVING; @@ -133,8 +126,7 @@ SolverJob solve(ProblemId_ problemId, } @Override - public @NonNull CompletableFuture addProblemChanges(@NonNull ProblemId_ problemId, - @NonNull List> problemChangeList) { + public CompletableFuture addProblemChanges(Object problemId, List> problemChangeList) { var solverJob = getSolverJob(problemId); if (solverJob == null) { // We cannot distinguish between "already terminated" and "never solved" without causing a memory leak. @@ -146,7 +138,7 @@ SolverJob solve(ProblemId_ problemId, } @Override - public void terminateEarly(@NonNull ProblemId_ problemId) { + public void terminateEarly(Object problemId) { var solverJob = getSolverJob(problemId); if (solverJob == null) { // We cannot distinguish between "already terminated" and "never solved" without causing a memory leak. @@ -162,7 +154,7 @@ public void close() { problemIdToSolverJobMap.values().forEach(DefaultSolverJob::close); } - void unregisterSolverJob(ProblemId_ problemId) { + void unregisterSolverJob(Object problemId) { problemIdToSolverJobMap.remove(getProblemIdOrThrow(problemId)); } 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 12e9633ab64..e843640f07e 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 @@ -76,7 +76,7 @@ class SolverManagerTest { - private static final Function DEFAULT_PROBLEM_FINDER = + private static final Function DEFAULT_PROBLEM_FINDER = problemId -> PlannerTestUtils.generateTestdataSolution("Generated solution " + problemId); private static final SolverManagerConfig SOLVER_MANAGER_CONFIG_WITH_1_PARALLEL_SOLVER = new SolverManagerConfig().withParallelSolverCount("1"); @@ -107,15 +107,15 @@ void solveBatch_2InParallel() throws ExecutionException, InterruptedException { } } - private static SolverManager createDefaultSolverManager(SolverConfig solverConfig) { + private static SolverManager createDefaultSolverManager(SolverConfig solverConfig) { return SolverManager.create(solverConfig); } - private static SolverManager createSolverManagerWithOneSolver(SolverConfig solverConfig) { + private static SolverManager createSolverManagerWithOneSolver(SolverConfig solverConfig) { return createSolverManager(solverConfig, SOLVER_MANAGER_CONFIG_WITH_1_PARALLEL_SOLVER); } - private static SolverManager createSolverManager(SolverConfig solverConfig, + private static SolverManager createSolverManager(SolverConfig solverConfig, SolverManagerConfig solverManagerConfig) { return SolverManager.create(solverConfig, solverManagerConfig); } @@ -421,7 +421,7 @@ void firstInitializedSolutionConsumerEarlyTerminatedCHListVar() throws Interrupt .withTerminationConfig(new TerminationConfig().withStepCountLimit(1)), new LocalSearchPhaseConfig() .withTerminationConfig(new TerminationConfig().withStepCountLimit(0))); - try (var solverManager = SolverManager. create(solverConfig)) { + try (var solverManager = SolverManager. create(solverConfig)) { // The solution will produce a CH that takes 2 steps. // The CH is configured to terminate after 1 step, guaranteeing early termination. Function problemFinder = o -> { @@ -587,13 +587,13 @@ void solveWithOverride() { SolverScope solverScope = mock(SolverScope.class); doReturn(50L).when(solverScope).calculateTimeMillisSpentUpToNow(); - var solverJob = (DefaultSolverJob) solverManager.solve(1L, problem); + var solverJob = (DefaultSolverJob) solverManager.solve(1L, problem); assertThat(solverJob.getSolverTermination().calculateSolverTimeGradient(solverScope)).isEqualTo(0.05); // Spent limit overridden by 100L var configOverride = new SolverConfigOverride() .withTerminationConfig(new TerminationConfig().withSpentLimit(Duration.ofMillis(100L))); - solverJob = (DefaultSolverJob) solverManager.solveBuilder() + solverJob = (DefaultSolverJob) solverManager.solveBuilder() .withProblemId(2L) .withProblem(problem) .withConfigOverride(configOverride) @@ -619,7 +619,7 @@ void solveWithTerminationSpentLimit() { // Override spent limit to 100 milliseconds var configOverride = new SolverConfigOverride() .withTerminationSpentLimit(Duration.ofMillis(100L)); - var solverJob = (DefaultSolverJob) solverManager.solveBuilder() + var solverJob = (DefaultSolverJob) solverManager.solveBuilder() .withProblemId(1L) .withProblem(problem) .withConfigOverride(configOverride) @@ -770,8 +770,8 @@ void testProblemSizeStatisticsForWaitingJob() throws InterruptedException, Execu @Test void testSolveBuilderForExistingSolvingMethods() { - SolverJobBuilder solverJobBuilder = mock(SolverJobBuilder.class); - SolverManager solverManager = mock(SolverManager.class); + SolverJobBuilder solverJobBuilder = mock(SolverJobBuilder.class); + SolverManager solverManager = mock(SolverManager.class); doReturn(solverJobBuilder).when(solverManager).solveBuilder(); doReturn(solverJobBuilder).when(solverJobBuilder).withProblemId(anyLong()); @@ -1003,7 +1003,7 @@ void terminateEarly() throws InterruptedException, BrokenBarrierException { } } - private void assertInitializedJobs(List> jobs) + private void assertInitializedJobs(List> jobs) throws InterruptedException, ExecutionException { for (var job : jobs) { // Method getFinalBestSolution() waits for the solving to finish, therefore it ensures synchronization. @@ -1023,7 +1023,7 @@ void submitMoreProblemsThanCpus_allGetSolved() throws InterruptedException, Exec } } - private SolverManager createSolverManagerTestableByDifferentConsumers() { + private SolverManager createSolverManagerTestableByDifferentConsumers() { var solverConfig = PlannerTestUtils.buildSolverConfig(TestdataSolution.class, TestdataEntity.class) .withPhases(IntStream.of(0, 1) .mapToObj(x -> new CustomPhaseConfig().withCustomPhaseCommands( @@ -1040,23 +1040,23 @@ private SolverManager createSolverManagerTestableByDiffe return createDefaultSolverManager(solverConfig); } - private void assertSolveWithoutConsumer(int problemCount, SolverManager solverManager) + private void assertSolveWithoutConsumer(int problemCount, SolverManager solverManager) throws InterruptedException, ExecutionException { - var jobs = new ArrayList>(problemCount); + var jobs = new ArrayList>(problemCount); for (long id = 0; id < problemCount; id++) { jobs.add(solverManager.solve(id, PlannerTestUtils.generateTestdataSolution(String.format("s%d", id)))); } assertInitializedJobs(jobs); } - private void assertSolveWithConsumer(int problemCount, SolverManager solverManager, + private void assertSolveWithConsumer(int problemCount, SolverManager solverManager, boolean listenWhileSolving) throws ExecutionException, InterruptedException { // Two solutions should be created for every problem. var solutionMap = new HashMap>(problemCount * 2); var finalBestSolutionConsumed = new CountDownLatch(problemCount); - var jobs = new ArrayList>(problemCount); + var jobs = new ArrayList>(problemCount); for (var id = 0L; id < problemCount; id++) { var consumedBestSolutions = Collections.synchronizedList(new ArrayList()); diff --git a/core/src/test/java/ai/timefold/solver/core/impl/solver/ConsumerSupportTest.java b/core/src/test/java/ai/timefold/solver/core/impl/solver/ConsumerSupportTest.java index 7ab5e6d4d19..d106234f7eb 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/solver/ConsumerSupportTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/solver/ConsumerSupportTest.java @@ -101,12 +101,13 @@ void problemChangesCompleteExceptionally_afterExceptionInConsumer() { Consumer> errorneousConsumer = bestSolution -> { throw new RuntimeException(errorMessage); }; - consumerSupport = new ConsumerSupport<>(1L, errorneousConsumer, null, null, null, null, bestSolutionHolder); + consumerSupport = new ConsumerSupport<>(1L, errorneousConsumer, null, null, null, (id, ex) -> { + }, bestSolutionHolder); CompletableFuture futureProblemChange = addProblemChange(bestSolutionHolder); consumeIntermediateBestSolution(TestdataSolution.generateSolution()); - assertThatExceptionOfType(ExecutionException.class).isThrownBy(() -> futureProblemChange.get()) + assertThatExceptionOfType(ExecutionException.class).isThrownBy(futureProblemChange::get) .havingRootCause() .isInstanceOf(RuntimeException.class) .withMessage(errorMessage); @@ -130,7 +131,7 @@ void pendingProblemChangesAreCanceled_afterFinalBestSolutionIsConsumed() throws futureProblemChange.get(); assertThat(futureProblemChange).isCompleted(); - assertThatExceptionOfType(CancellationException.class).isThrownBy(() -> pendingProblemChange.get()); + assertThatExceptionOfType(CancellationException.class).isThrownBy(pendingProblemChange::get); } private CompletableFuture addProblemChange(BestSolutionHolder bestSolutionHolder) { diff --git a/core/src/test/java/ai/timefold/solver/core/impl/solver/ProblemChangeBarrageIT.java b/core/src/test/java/ai/timefold/solver/core/impl/solver/ProblemChangeBarrageIT.java index 12a4021dd24..a17c183a7c5 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/solver/ProblemChangeBarrageIT.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/solver/ProblemChangeBarrageIT.java @@ -38,15 +38,13 @@ void problemChangeBarrageIntermediateBestSolutionConsumer() throws InterruptedEx .withEasyScoreCalculatorClass(TestdataEasyScoreCalculator.class); var futureList = new ArrayList(); - try (var solverManager = SolverManager. create(solverConfig, new SolverManagerConfig())) { + try (var solverManager = SolverManager. create(solverConfig, new SolverManagerConfig())) { var solverStartedLatch = new CountDownLatch(1); var solution = TestdataSolution.generateSolution(); var solverJob = solverManager.solveBuilder() .withProblemId(UUID.randomUUID()) .withProblem(solution) - .withFirstInitializedSolutionEventConsumer(event -> { - solverStartedLatch.countDown(); - }) + .withFirstInitializedSolutionEventConsumer(event -> solverStartedLatch.countDown()) .withBestSolutionEventConsumer(event -> { // No need to do anything. }) diff --git a/quarkus-integration/quarkus-benchmark/deployment/src/test/java/ai/timefold/solver/benchmark/quarkus/TimefoldBenchmarkProcessorMultipleSolversConfigTest.java b/quarkus-integration/quarkus-benchmark/deployment/src/test/java/ai/timefold/solver/benchmark/quarkus/TimefoldBenchmarkProcessorMultipleSolversConfigTest.java index 3942454fbbd..b6c53ea8587 100644 --- a/quarkus-integration/quarkus-benchmark/deployment/src/test/java/ai/timefold/solver/benchmark/quarkus/TimefoldBenchmarkProcessorMultipleSolversConfigTest.java +++ b/quarkus-integration/quarkus-benchmark/deployment/src/test/java/ai/timefold/solver/benchmark/quarkus/TimefoldBenchmarkProcessorMultipleSolversConfigTest.java @@ -41,11 +41,11 @@ class TimefoldBenchmarkProcessorMultipleSolversConfigTest { @Inject @Named("solver1") - SolverManager solverManager1; + SolverManager solverManager1; @Inject @Named("solver2") - SolverManager solverManager2; + SolverManager solverManager2; @Test void benchmark() throws ExecutionException, InterruptedException { diff --git a/quarkus-integration/quarkus-jackson/integration-test/src/main/java/ai/timefold/solver/quarkus/jackson/it/TimefoldTestResource.java b/quarkus-integration/quarkus-jackson/integration-test/src/main/java/ai/timefold/solver/quarkus/jackson/it/TimefoldTestResource.java index 3ce376eb952..f1ba9b00233 100644 --- a/quarkus-integration/quarkus-jackson/integration-test/src/main/java/ai/timefold/solver/quarkus/jackson/it/TimefoldTestResource.java +++ b/quarkus-integration/quarkus-jackson/integration-test/src/main/java/ai/timefold/solver/quarkus/jackson/it/TimefoldTestResource.java @@ -13,17 +13,17 @@ @Path("/timefold/test") public class TimefoldTestResource { - private final SolverManager solverManager; + private final SolverManager solverManager; @Inject - public TimefoldTestResource(SolverManager solverManager) { + public TimefoldTestResource(SolverManager solverManager) { this.solverManager = solverManager; } @POST @Path("/solver-factory") public ITestdataPlanningSolution solveWithSolverFactory(ITestdataPlanningSolution problem) { - SolverJob solverJob = solverManager.solve(1L, problem); + SolverJob solverJob = solverManager.solve(1L, problem); try { return solverJob.getFinalBestSolution(); } catch (InterruptedException e) { diff --git a/quarkus-integration/quarkus/deployment/src/main/java/ai/timefold/solver/quarkus/deployment/TimefoldProcessor.java b/quarkus-integration/quarkus/deployment/src/main/java/ai/timefold/solver/quarkus/deployment/TimefoldProcessor.java index 4827bf74250..4a8128f4c09 100644 --- a/quarkus-integration/quarkus/deployment/src/main/java/ai/timefold/solver/quarkus/deployment/TimefoldProcessor.java +++ b/quarkus-integration/quarkus/deployment/src/main/java/ai/timefold/solver/quarkus/deployment/TimefoldProcessor.java @@ -68,7 +68,6 @@ import org.jboss.jandex.MethodInfo; import org.jboss.jandex.ParameterizedType; import org.jboss.jandex.Type; -import org.jboss.jandex.TypeVariable; import org.jboss.logging.Logger; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; @@ -661,8 +660,7 @@ void recordAndRegisterRuntimeBeans(TimefoldRecorder recorder, RecorderContext re .scope(Singleton.class) .addType(ParameterizedType.create(DotName.createSimple(SolverManager.class.getName()), Type.create(DotName.createSimple(value.getSolutionClass().getName()), - Type.Kind.CLASS), - TypeVariable.create(Object.class.getName()))) + Type.Kind.CLASS))) .supplier(recorder.solverManager(key, value, GizmoMemberAccessorEntityEnhancer.getGeneratedGizmoMemberAccessorMap(recorderContext, solverConfigBuildItem diff --git a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorDeclarativeShadowSolutionListSolveTest.java b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorDeclarativeShadowSolutionListSolveTest.java index 30930eb7339..5bf039b0207 100644 --- a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorDeclarativeShadowSolutionListSolveTest.java +++ b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorDeclarativeShadowSolutionListSolveTest.java @@ -33,7 +33,7 @@ class TimefoldProcessorDeclarativeShadowSolutionListSolveTest { TestdataQuarkusDeclarativeShadowVariableListValue.class, TestdataQuarkusDeclarativeShadowVariableListConstraintProvider.class)); @Inject - SolverManager solverManager; + SolverManager solverManager; @Test void solve() throws ExecutionException, InterruptedException { diff --git a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorExtendedSolutionSolveTest.java b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorExtendedSolutionSolveTest.java index 2f440b00ac7..bfe8f2235f0 100644 --- a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorExtendedSolutionSolveTest.java +++ b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorExtendedSolutionSolveTest.java @@ -42,7 +42,7 @@ class TimefoldProcessorExtendedSolutionSolveTest { @Inject SolverFactory solverFactory; @Inject - SolverManager solverManager; + SolverManager solverManager; @Inject SolutionManager solutionManager; @@ -54,7 +54,7 @@ void singletonSolverFactory() { ((DefaultSolutionManager) solutionManager).getScoreDirectorFactory()); assertNotNull(solverManager); // There is only one SolverFactory instance - assertSame(solverFactory, ((DefaultSolverManager) solverManager).getSolverFactory()); + assertSame(solverFactory, ((DefaultSolverManager) solverManager).getSolverFactory()); } @Test diff --git a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorGizmoKitchenSinkTest.java b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorGizmoKitchenSinkTest.java index b85af150903..cbb74fe576a 100644 --- a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorGizmoKitchenSinkTest.java +++ b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorGizmoKitchenSinkTest.java @@ -42,7 +42,7 @@ class TimefoldProcessorGizmoKitchenSinkTest { @Inject SolverFactory solverFactory; @Inject - SolverManager solverManager; + SolverManager solverManager; @Inject SolutionManager solutionManager; @@ -54,7 +54,7 @@ void singletonSolverFactory() { ((DefaultSolutionManager) solutionManager).getScoreDirectorFactory()); assertNotNull(solverManager); // There is only one SolverFactory instance - assertSame(solverFactory, ((DefaultSolverManager) solverManager).getSolverFactory()); + assertSame(solverFactory, ((DefaultSolverManager) solverManager).getSolverFactory()); } @Test @@ -66,7 +66,7 @@ void solve() throws ExecutionException, InterruptedException { Collections.emptyList(), HardSoftScore.ZERO); - SolverJob solverJob = solverManager.solve(1L, problem); + SolverJob solverJob = solverManager.solve(1L, problem); TestDataKitchenSinkSolution solution = solverJob.getFinalBestSolution(); assertNotNull(solution); assertEquals(1, solution.getPlanningEntityProperty().testGetIntVariable()); diff --git a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorInvalidTest.java b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorInvalidTest.java index 18dd8ceb651..c1ff7302b7f 100644 --- a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorInvalidTest.java +++ b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorInvalidTest.java @@ -50,7 +50,7 @@ in a class (%s) \ @Inject SolverFactory solverFactory; @Inject - SolverManager solverManager; + SolverManager solverManager; @Inject SolutionManager solutionManager; diff --git a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorMultipleSolversInvalidConstraintClassTest.java b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorMultipleSolversInvalidConstraintClassTest.java index c60a3082bcf..8f5a8b5ffd8 100644 --- a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorMultipleSolversInvalidConstraintClassTest.java +++ b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorMultipleSolversInvalidConstraintClassTest.java @@ -235,11 +235,11 @@ class TimefoldProcessorMultipleSolversInvalidConstraintClassTest { @Inject @Named("solver1") - SolverManager solverManager1; + SolverManager solverManager1; @Inject @Named("solver2") - SolverManager solverManager2; + SolverManager solverManager2; @Test void test() { diff --git a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorMultipleSolversInvalidEntityClassTest.java b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorMultipleSolversInvalidEntityClassTest.java index bf50ec78706..51f8d3ad881 100644 --- a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorMultipleSolversInvalidEntityClassTest.java +++ b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorMultipleSolversInvalidEntityClassTest.java @@ -32,11 +32,11 @@ class TimefoldProcessorMultipleSolversInvalidEntityClassTest { @Inject @Named("solver1") - SolverManager solverManager1; + SolverManager solverManager1; @Inject @Named("solver2") - SolverManager solverManager2; + SolverManager solverManager2; @Test void test() { diff --git a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorMultipleSolversInvalidSolutionClassTest.java b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorMultipleSolversInvalidSolutionClassTest.java index d4172bf76a9..335207ca9f6 100644 --- a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorMultipleSolversInvalidSolutionClassTest.java +++ b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorMultipleSolversInvalidSolutionClassTest.java @@ -82,11 +82,11 @@ class TimefoldProcessorMultipleSolversInvalidSolutionClassTest { @Inject @Named("solver1") - SolverManager solverManager1; + SolverManager solverManager1; @Inject @Named("solver2") - SolverManager solverManager2; + SolverManager solverManager2; @Test void test() { diff --git a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorMultipleSolversPropertiesTest.java b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorMultipleSolversPropertiesTest.java index 5ecb08ae5d6..b9af7945993 100644 --- a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorMultipleSolversPropertiesTest.java +++ b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorMultipleSolversPropertiesTest.java @@ -31,11 +31,11 @@ class TimefoldProcessorMultipleSolversPropertiesTest { @Inject @Named("solver1") - SolverManager solverManager1; + SolverManager solverManager1; @Inject @Named("solver2") - SolverManager solverManager2; + SolverManager solverManager2; @Test void solverProperties() { diff --git a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorMultipleSolversYamlTest.java b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorMultipleSolversYamlTest.java index aea304bb0e3..41a0db7493f 100644 --- a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorMultipleSolversYamlTest.java +++ b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorMultipleSolversYamlTest.java @@ -30,11 +30,11 @@ class TimefoldProcessorMultipleSolversYamlTest { @Inject @Named("solver1") - SolverManager solverManager1; + SolverManager solverManager1; @Inject @Named("solver2") - SolverManager solverManager2; + SolverManager solverManager2; @Test void solverProperties() { diff --git a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorOnlyMultiConstructorTest.java b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorOnlyMultiConstructorTest.java index 80479652960..65103275767 100644 --- a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorOnlyMultiConstructorTest.java +++ b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorOnlyMultiConstructorTest.java @@ -32,7 +32,7 @@ class TimefoldProcessorOnlyMultiConstructorTest { ") must have a no-args constructor so it can be constructed by Timefold.")); @Inject - SolverManager solverManager; + SolverManager solverManager; @Test void canConstructBeansWithPrivateConstructors() { diff --git a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorPrivateConstructorTest.java b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorPrivateConstructorTest.java index 06cf24344d7..77a2ef2a23f 100644 --- a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorPrivateConstructorTest.java +++ b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorPrivateConstructorTest.java @@ -30,7 +30,7 @@ class TimefoldProcessorPrivateConstructorTest { PrivateNoArgsConstructorEntity.class)); @Inject - SolverManager solverManager; + SolverManager solverManager; @Test void canConstructBeansWithPrivateConstructors() throws ExecutionException, InterruptedException { diff --git a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorShadowVariableSolveTest.java b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorShadowVariableSolveTest.java index a010b2ab511..ccfcadf3784 100644 --- a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorShadowVariableSolveTest.java +++ b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorShadowVariableSolveTest.java @@ -42,7 +42,7 @@ class TimefoldProcessorShadowVariableSolveTest { @Inject SolverFactory solverFactory; @Inject - SolverManager solverManager; + SolverManager solverManager; @Inject SolutionManager solutionManager; @@ -55,7 +55,7 @@ void singletonSolverFactory() { assertNotNull(solverManager); // There is only one SolverFactory instance assertSame(solverFactory, - ((DefaultSolverManager) solverManager).getSolverFactory()); + ((DefaultSolverManager) solverManager).getSolverFactory()); assertNotNull(solutionManager); } @@ -68,7 +68,7 @@ void solve() throws ExecutionException, InterruptedException { problem.setEntityList(IntStream.range(1, 3) .mapToObj(i -> new TestdataQuarkusShadowVariableEntity()) .collect(Collectors.toList())); - SolverJob solverJob = solverManager.solve(1L, problem); + SolverJob solverJob = solverManager.solve(1L, problem); TestdataQuarkusShadowVariableSolution solution = solverJob.getFinalBestSolution(); assertNotNull(solution); assertTrue(solution.getScore().score() >= 0); diff --git a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorSolveTest.java b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorSolveTest.java index c9ce4e0da36..8757d373dd3 100644 --- a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorSolveTest.java +++ b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorSolveTest.java @@ -41,7 +41,7 @@ class TimefoldProcessorSolveTest { @Inject SolverFactory solverFactory; @Inject - SolverManager solverManager; + SolverManager solverManager; @Inject SolutionManager solutionManager; @@ -52,7 +52,7 @@ void singletonSolverFactory() { ((DefaultSolutionManager) solutionManager).getScoreDirectorFactory()); assertNotNull(solverManager); // There is only one SolverFactory instance - assertSame(solverFactory, ((DefaultSolverManager) solverManager).getSolverFactory()); + assertSame(solverFactory, ((DefaultSolverManager) solverManager).getSolverFactory()); assertNotNull(solutionManager); } @@ -65,7 +65,7 @@ void solve() throws ExecutionException, InterruptedException { problem.setEntityList(IntStream.range(1, 3) .mapToObj(i -> new TestdataQuarkusEntity()) .collect(Collectors.toList())); - SolverJob solverJob = solverManager.solve(1L, problem); + SolverJob solverJob = solverManager.solve(1L, problem); TestdataQuarkusSolution solution = solverJob.getFinalBestSolution(); assertNotNull(solution); assertTrue(solution.getScore().score() >= 0); diff --git a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorSolverResourcesTest.java b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorSolverResourcesTest.java index 8af88f347b5..6ac9a5aab70 100644 --- a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorSolverResourcesTest.java +++ b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorSolverResourcesTest.java @@ -46,10 +46,6 @@ class TimefoldProcessorSolverResourcesTest { .addClasses(TestdataQuarkusEntity.class, TestdataQuarkusSolution.class, TestdataQuarkusConstraintProvider.class)); - @Inject - @Named("solver1") - SolverManager solverManager1; - @Inject ConstraintMetaModel constraintMetaModel; @@ -58,7 +54,8 @@ class TimefoldProcessorSolverResourcesTest { @Inject SolverFactory solver1Factory; @Inject - SolverManager solver1Manager; + @Named("solver1") + SolverManager solver1Manager; // SolutionManager per score type @Inject diff --git a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorSolverUnusedPropertiesTest.java b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorSolverUnusedPropertiesTest.java index 411d00ff44a..6f3299c21b1 100644 --- a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorSolverUnusedPropertiesTest.java +++ b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorSolverUnusedPropertiesTest.java @@ -77,7 +77,7 @@ class TimefoldProcessorSolverUnusedPropertiesTest { @Inject @Named("solver1") - SolverManager solverManager; + SolverManager solverManager; @Test void solve() { diff --git a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorSupplierShadowSolutionSimpleSolveTest.java b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorSupplierShadowSolutionSimpleSolveTest.java index a2abde2df3e..48367cddcce 100644 --- a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorSupplierShadowSolutionSimpleSolveTest.java +++ b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorSupplierShadowSolutionSimpleSolveTest.java @@ -31,7 +31,7 @@ class TimefoldProcessorSupplierShadowSolutionSimpleSolveTest { TestdataQuarkusSupplierVariableSimpleEntity.class, TestdataQuarkusSupplierVariableSimpleConstraintProvider.class)); @Inject - SolverManager solverManager; + SolverManager solverManager; @Test void solve() throws ExecutionException, InterruptedException { diff --git a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorUseGettersSettersTest.java b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorUseGettersSettersTest.java index 0414f7506ea..9927a85878c 100644 --- a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorUseGettersSettersTest.java +++ b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/TimefoldProcessorUseGettersSettersTest.java @@ -32,7 +32,7 @@ class TimefoldProcessorUseGettersSettersTest { TestdataQuarkusUseGetterSetterConstraintProvider.class)); @Inject - SolverManager solverManager; + SolverManager solverManager; @Test void solve() throws ExecutionException, InterruptedException { diff --git a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/rest/TestdataQuarkusShadowSolutionConfigResource.java b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/rest/TestdataQuarkusShadowSolutionConfigResource.java index d784d9716bc..65699032fa7 100644 --- a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/rest/TestdataQuarkusShadowSolutionConfigResource.java +++ b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/rest/TestdataQuarkusShadowSolutionConfigResource.java @@ -28,11 +28,11 @@ public class TestdataQuarkusShadowSolutionConfigResource { @Inject @Named("solver1") - SolverManager solver1; + SolverManager solver1; @Inject @Named("solver2") - SolverManager solver2; + SolverManager solver2; private static long count = 0; @@ -50,8 +50,8 @@ public String secondsSpentLimit() { problem.setEntityList(List.of(new TestdataQuarkusEntity())); SolverScope solverScopeSolver1 = mock(SolverScope.class); doReturn(500L).when(solverScopeSolver1).calculateTimeMillisSpentUpToNow(); - DefaultSolverJob jobSolver1 = - (DefaultSolverJob) solver1.solve(++count, problem); + DefaultSolverJob jobSolver1 = + (DefaultSolverJob) solver1.solve(++count, problem); double gradientTimeSolver1 = jobSolver1.getSolverTermination().calculateSolverTimeGradient(solverScopeSolver1); // Solver 2 @@ -60,8 +60,8 @@ public String secondsSpentLimit() { problemShadowVariable.setEntityList(List.of(new TestdataQuarkusShadowVariableEntity())); SolverScope solverScopeSolver2 = mock(SolverScope.class); doReturn(500L).when(solverScopeSolver2).calculateTimeMillisSpentUpToNow(); - DefaultSolverJob jobSolver2 = - (DefaultSolverJob) solver2.solve(++count, problemShadowVariable); + DefaultSolverJob jobSolver2 = + (DefaultSolverJob) solver2.solve(++count, problemShadowVariable); double gradientTimeSolver2 = jobSolver2.getSolverTermination().calculateSolverTimeGradient(solverScopeSolver2); return String.format("secondsSpentLimit=%s;secondsSpentLimit=%s", decimalFormat.format(gradientTimeSolver1), diff --git a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/rest/TestdataQuarkusSolutionConfigResource.java b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/rest/TestdataQuarkusSolutionConfigResource.java index 0f7d76783d5..238974d0a5c 100644 --- a/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/rest/TestdataQuarkusSolutionConfigResource.java +++ b/quarkus-integration/quarkus/deployment/src/test/java/ai/timefold/solver/quarkus/rest/TestdataQuarkusSolutionConfigResource.java @@ -26,11 +26,11 @@ public class TestdataQuarkusSolutionConfigResource { @Inject @Named("solver1") - SolverManager solver1; + SolverManager solver1; @Inject @Named("solver2") - SolverManager solver2; + SolverManager solver2; @GET @Path("/seconds-spent-limit") @@ -48,13 +48,13 @@ public String secondsSpentLimit() { problem.setEntityList(List.of(new TestdataQuarkusEntity())); // Solver 1 - DefaultSolverJob jobSolver1 = - (DefaultSolverJob) solver1.solve(1L, problem); + DefaultSolverJob jobSolver1 = + (DefaultSolverJob) solver1.solve(1L, problem); double gradientTimeSolver1 = jobSolver1.getSolverTermination().calculateSolverTimeGradient(solverScope); // Solver 2 - DefaultSolverJob jobSolver2 = - (DefaultSolverJob) solver2.solve(2L, problem); + DefaultSolverJob jobSolver2 = + (DefaultSolverJob) solver2.solve(2L, problem); double gradientTimeSolver2 = jobSolver2.getSolverTermination().calculateSolverTimeGradient(solverScope); return String.format("secondsSpentLimit=%s;secondsSpentLimit=%s", decimalFormat.format(gradientTimeSolver1), diff --git a/quarkus-integration/quarkus/devui-integration-test/src/test/java/ai/timefold/solver/quarkus/it/devui/TimefoldDevUIMultipleSolversTest.java b/quarkus-integration/quarkus/devui-integration-test/src/test/java/ai/timefold/solver/quarkus/it/devui/TimefoldDevUIMultipleSolversTest.java index 2afefac4092..af9d7704b7a 100644 --- a/quarkus-integration/quarkus/devui-integration-test/src/test/java/ai/timefold/solver/quarkus/it/devui/TimefoldDevUIMultipleSolversTest.java +++ b/quarkus-integration/quarkus/devui-integration-test/src/test/java/ai/timefold/solver/quarkus/it/devui/TimefoldDevUIMultipleSolversTest.java @@ -36,11 +36,11 @@ public static class TimefoldTestMultipleResource { @Inject @Named("solver1") - SolverManager solverManager; + SolverManager solverManager; @Inject @Named("solver2") - SolverManager solverManager2; + SolverManager solverManager2; } public TimefoldDevUIMultipleSolversTest() { diff --git a/quarkus-integration/quarkus/devui-integration-test/src/test/java/ai/timefold/solver/quarkus/it/devui/TimefoldDevUITest.java b/quarkus-integration/quarkus/devui-integration-test/src/test/java/ai/timefold/solver/quarkus/it/devui/TimefoldDevUITest.java index e6c6a2257db..d4a716a0967 100644 --- a/quarkus-integration/quarkus/devui-integration-test/src/test/java/ai/timefold/solver/quarkus/it/devui/TimefoldDevUITest.java +++ b/quarkus-integration/quarkus/devui-integration-test/src/test/java/ai/timefold/solver/quarkus/it/devui/TimefoldDevUITest.java @@ -41,7 +41,7 @@ public class TimefoldDevUITest extends DevUIJsonRPCTest { public static class TimefoldTestResource { @Inject - SolverManager solverManager; + SolverManager solverManager; @POST @Path("/solver-factory") @@ -52,7 +52,7 @@ public String solveWithSolverFactory() { new TestdataStringLengthShadowEntity(), new TestdataStringLengthShadowEntity())); planningProblem.setValueList(Arrays.asList("a", "bb", "ccc")); - SolverJob solverJob = solverManager.solve(1L, planningProblem); + SolverJob solverJob = solverManager.solve(1L, planningProblem); try { return solverJob.getFinalBestSolution().getScore().toString(); } catch (InterruptedException e) { diff --git a/quarkus-integration/quarkus/integration-test/src/main/java/ai/timefold/solver/quarkus/it/TimefoldTestResource.java b/quarkus-integration/quarkus/integration-test/src/main/java/ai/timefold/solver/quarkus/it/TimefoldTestResource.java index bd472b11da4..3b07c6287eb 100644 --- a/quarkus-integration/quarkus/integration-test/src/main/java/ai/timefold/solver/quarkus/it/TimefoldTestResource.java +++ b/quarkus-integration/quarkus/integration-test/src/main/java/ai/timefold/solver/quarkus/it/TimefoldTestResource.java @@ -27,10 +27,10 @@ @Path("/timefold/test") public class TimefoldTestResource { - private final SolverManager solverManager; + private final SolverManager solverManager; @Inject - public TimefoldTestResource(SolverManager solverManager) { + public TimefoldTestResource(SolverManager solverManager) { this.solverManager = solverManager; } @@ -72,7 +72,7 @@ public String solveWithOverriddenTime(@QueryParam("seconds") Integer seconds) { new SolverConfigOverride() .withTerminationConfig(new TerminationConfig() .withSpentLimit(Duration.ofSeconds(seconds)))); - var solverJob = (DefaultSolverJob) solverJobBuilder.run(); + var solverJob = (DefaultSolverJob) solverJobBuilder.run(); SolverScope customScope = new SolverScope<>() { @Override public long calculateTimeMillisSpentUpToNow() { diff --git a/quarkus-integration/quarkus/reflection-integration-test/src/main/java/ai/timefold/solver/quarkus/it/reflection/TimefoldTestResource.java b/quarkus-integration/quarkus/reflection-integration-test/src/main/java/ai/timefold/solver/quarkus/it/reflection/TimefoldTestResource.java index ce07c7a9dd2..b968a653245 100644 --- a/quarkus-integration/quarkus/reflection-integration-test/src/main/java/ai/timefold/solver/quarkus/it/reflection/TimefoldTestResource.java +++ b/quarkus-integration/quarkus/reflection-integration-test/src/main/java/ai/timefold/solver/quarkus/it/reflection/TimefoldTestResource.java @@ -17,10 +17,10 @@ @Path("/timefold/test") public class TimefoldTestResource { - private final SolverManager solverManager; + private final SolverManager solverManager; @Inject - public TimefoldTestResource(SolverManager solverManager) { + public TimefoldTestResource(SolverManager solverManager) { this.solverManager = solverManager; } @@ -34,7 +34,7 @@ public String solveWithSolverFactory() { new TestdataReflectionEntity())); planningProblem.setFieldValueList(Arrays.asList("a", "bb", "ccc")); planningProblem.setMethodValueList(Arrays.asList("a", "bb", "ccc", "ddd")); - SolverJob solverJob = solverManager.solve(1L, planningProblem); + SolverJob solverJob = solverManager.solve(1L, planningProblem); try { return solverJob.getFinalBestSolution().getScore().toString(); } catch (InterruptedException e) { diff --git a/quarkus-integration/quarkus/runtime/src/main/java/ai/timefold/solver/quarkus/TimefoldRecorder.java b/quarkus-integration/quarkus/runtime/src/main/java/ai/timefold/solver/quarkus/TimefoldRecorder.java index cb0d2b9b31b..6a5f10d14fd 100644 --- a/quarkus-integration/quarkus/runtime/src/main/java/ai/timefold/solver/quarkus/TimefoldRecorder.java +++ b/quarkus-integration/quarkus/runtime/src/main/java/ai/timefold/solver/quarkus/TimefoldRecorder.java @@ -88,7 +88,7 @@ public Supplier solverManagerConfig(final SolverManagerConf }; } - public Supplier> solverManager(final String solverName, + public Supplier> solverManager(final String solverName, final SolverConfig solverConfig, Map> generatedGizmoMemberAccessorMap, Map>> generatedGizmoSolutionClonerMap) { @@ -109,7 +109,7 @@ public Supplier> so var solverFactory = SolverFactory.create(solverConfig); - return (SolverManager) SolverManager.create(solverFactory, solverManagerConfig); + return (SolverManager) SolverManager.create(solverFactory, solverManagerConfig); }; } diff --git a/quarkus-integration/quarkus/runtime/src/main/java/ai/timefold/solver/quarkus/bean/DefaultTimefoldBeanProvider.java b/quarkus-integration/quarkus/runtime/src/main/java/ai/timefold/solver/quarkus/bean/DefaultTimefoldBeanProvider.java index 1065916729e..0de0f742c2c 100644 --- a/quarkus-integration/quarkus/runtime/src/main/java/ai/timefold/solver/quarkus/bean/DefaultTimefoldBeanProvider.java +++ b/quarkus-integration/quarkus/runtime/src/main/java/ai/timefold/solver/quarkus/bean/DefaultTimefoldBeanProvider.java @@ -34,7 +34,7 @@ public class DefaultTimefoldBeanProvider { private ConstraintMetaModel constraintMetaModel; - private SolverManager solverManager; + private SolverManager solverManager; private SolutionManager solutionManager; @@ -64,12 +64,12 @@ ConstraintMetaModel constraintProviderMetaModel(SolverFactory solverFactory) @DefaultBean @Dependent @Produces - SolverManager solverManager(SolverFactory solverFactory, + SolverManager solverManager(SolverFactory solverFactory, SolverManagerConfig solverManagerConfig) { if (solverManager == null) { solverManager = SolverManager.create(solverFactory, solverManagerConfig); } - return (SolverManager) solverManager; + return (SolverManager) solverManager; } // Quarkus-ARC-Weld can't deal with enum pattern generics such as Score>. diff --git a/quarkus-integration/quarkus/runtime/src/main/java/ai/timefold/solver/quarkus/bean/UnavailableTimefoldBeanProvider.java b/quarkus-integration/quarkus/runtime/src/main/java/ai/timefold/solver/quarkus/bean/UnavailableTimefoldBeanProvider.java index b48b49e9d3b..d5e525a1623 100644 --- a/quarkus-integration/quarkus/runtime/src/main/java/ai/timefold/solver/quarkus/bean/UnavailableTimefoldBeanProvider.java +++ b/quarkus-integration/quarkus/runtime/src/main/java/ai/timefold/solver/quarkus/bean/UnavailableTimefoldBeanProvider.java @@ -35,7 +35,7 @@ SolverFactory solverFactory() { @DefaultBean @Dependent @Produces - SolverManager solverManager() { + SolverManager solverManager() { throw createException(SolverManager.class); } diff --git a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAotFactory.java b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAotFactory.java index 5087078c25d..e095051be8b 100644 --- a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAotFactory.java +++ b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAotFactory.java @@ -25,7 +25,7 @@ public void setEnvironment(Environment environment) { this.timefoldProperties = result.orElseGet(TimefoldProperties::new); } - public SolverManager solverManagerSupplier(String solverConfigXml) { + public SolverManager solverManagerSupplier(String solverConfigXml) { SolverFactory solverFactory = SolverFactory.create(solverConfigSupplier(solverConfigXml)); SolverManagerConfig solverManagerConfig = new SolverManagerConfig(); SolverManagerProperties solverManagerProperties = timefoldProperties.getSolverManager(); diff --git a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverBeanFactory.java b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverBeanFactory.java index 96c34c717fc..d6d350fc055 100644 --- a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverBeanFactory.java +++ b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverBeanFactory.java @@ -94,7 +94,7 @@ public SolverFactory getSolverFactory() { @Bean @Lazy @ConditionalOnMissingBean - public SolverManager solverManager(SolverFactory solverFactory) { + public SolverManager solverManager(SolverFactory solverFactory) { // TODO supply ThreadFactory if (solverFactory == null) { return null; diff --git a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverMultipleSolverAutoConfigurationTest.java b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverMultipleSolverAutoConfigurationTest.java index 1a958bc95fd..23cb458b416 100644 --- a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverMultipleSolverAutoConfigurationTest.java +++ b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverMultipleSolverAutoConfigurationTest.java @@ -101,9 +101,9 @@ void solverConfigXml_none() { .withClassLoader(allDefaultsFilteredClassLoader) .run(context -> { var solver1 = - (SolverManager) context.getBean("solver1"); + (SolverManager) context.getBean("solver1"); var solver2 = - (SolverManager) context.getBean("solver2"); + (SolverManager) context.getBean("solver2"); assertThat(solver1).isNotNull(); assertThat(solver2).isNotNull(); var problem = new TestdataSpringSolution(); @@ -124,11 +124,11 @@ public long calculateTimeMillisSpentUpToNow() { customScope.setStartingInitializedScore(HardSoftScore.of(-1, -1)); customScope.setInitializedBestScore(HardSoftScore.of(-1, -1)); var gradientTimeDefaultSolver1 = - ((DefaultSolverJob) solver1.solve(1L, problem)).getSolverTermination() + ((DefaultSolverJob) solver1.solve(1L, problem)).getSolverTermination() .calculateSolverTimeGradient(customScope); assertThat(gradientTimeDefaultSolver1).isEqualTo(0.5); var gradientTimeSolver2 = - ((DefaultSolverJob) solver2.solve(1L, problem)).getSolverTermination() + ((DefaultSolverJob) solver2.solve(1L, problem)).getSolverTermination() .calculateSolverTimeGradient(customScope); assertThat(gradientTimeSolver2).isEqualTo(0.25); }); @@ -143,9 +143,9 @@ void solverConfigXml_property() { "timefold.solver.solver2.solver-config-xml=ai/timefold/solver/spring/boot/autoconfigure/customSolver2Config.xml") .run(context -> { var solver1 = - (SolverManager) context.getBean("solver1"); + (SolverManager) context.getBean("solver1"); var solver2 = - (SolverManager) context.getBean("solver2"); + (SolverManager) context.getBean("solver2"); assertThat(solver1).isNotNull(); assertThat(solver2).isNotNull(); var problem = new TestdataSpringSolution(); @@ -166,11 +166,11 @@ public long calculateTimeMillisSpentUpToNow() { customScope.setStartingInitializedScore(HardSoftScore.of(-1, -1)); customScope.setInitializedBestScore(HardSoftScore.of(-1, -1)); var gradientTimeDefaultSolver1 = - ((DefaultSolverJob) solver1.solve(1L, problem)).getSolverTermination() + ((DefaultSolverJob) solver1.solve(1L, problem)).getSolverTermination() .calculateSolverTimeGradient(customScope); assertThat(gradientTimeDefaultSolver1).isEqualTo(0.5); var gradientTimeSolver2 = - ((DefaultSolverJob) solver2.solve(1L, problem)).getSolverTermination() + ((DefaultSolverJob) solver2.solve(1L, problem)).getSolverTermination() .calculateSolverTimeGradient(customScope); assertThat(gradientTimeSolver2).isEqualTo(0.25); }); @@ -185,9 +185,9 @@ void solverConfigXml_property_noGlobalTermination() { "timefold.solver.solver2.solver-config-xml=ai/timefold/solver/spring/boot/autoconfigure/solverConfigWithoutGlobalTermination.xml") .run(context -> { var solver1 = - (SolverManager) context.getBean("solver1"); + (SolverManager) context.getBean("solver1"); var solver2 = - (SolverManager) context.getBean("solver2"); + (SolverManager) context.getBean("solver2"); assertThat(solver1).isNotNull(); assertThat(solver2).isNotNull(); }); @@ -200,9 +200,9 @@ void solverProperties() { .withPropertyValues("timefold.solver.solver2.environment-mode=TRACKED_FULL_ASSERT") .run(context -> { var solver1 = - (SolverManager) context.getBean("solver1"); + (SolverManager) context.getBean("solver1"); var solver2 = - (SolverManager) context.getBean("solver2"); + (SolverManager) context.getBean("solver2"); assertThat(solver1).isNotNull(); assertThat(solver2).isNotNull(); }); @@ -211,9 +211,9 @@ void solverProperties() { .withPropertyValues("timefold.solver.solver2.daemon=false") .run(context -> { var solver1 = - (SolverManager) context.getBean("solver1"); + (SolverManager) context.getBean("solver1"); var solver2 = - (SolverManager) context.getBean("solver2"); + (SolverManager) context.getBean("solver2"); assertThat(solver1).isNotNull(); assertThat(solver2).isNotNull(); }); @@ -257,9 +257,9 @@ void solverPropertiesWithoutLoadingBenchmark() { .withUserConfiguration(TimefoldBenchmarkAutoConfiguration.class) // We load the configuration, but get no bean .run(context -> { var solver1 = - (SolverManager) context.getBean("solver1"); + (SolverManager) context.getBean("solver1"); var solver2 = - (SolverManager) context.getBean("solver2"); + (SolverManager) context.getBean("solver2"); assertThat(solver1).isNotNull(); assertThat(solver2).isNotNull(); }); @@ -282,7 +282,7 @@ void solve() { for (var solverName : List.of("solver1", "solver2")) { var solver = - (SolverManager) context.getBean(solverName); + (SolverManager) context.getBean(solverName); var solverJob = solver.solve(1L, problem); var solution = solverJob.getFinalBestSolution(); assertThat(solution).isNotNull(); @@ -308,7 +308,7 @@ void solverWithYaml() { for (var solverName : List.of("solver1", "solver2")) { var solver = - (SolverManager) context.getBean(solverName); + (SolverManager) context.getBean(solverName); var solverJob = solver.solve(1L, problem); var solution = solverJob.getFinalBestSolution(); assertThat(solution).isNotNull(); @@ -350,9 +350,9 @@ void solveWithTimeOverride() { .toList()); for (var solverName : List.of("solver1", "solver2")) { var solverManager = - (SolverManager) context.getBean(solverName); + (SolverManager) context.getBean(solverName); var solverJob = - (DefaultSolverJob) solverManager.solveBuilder() + (DefaultSolverJob) solverManager.solveBuilder() .withProblemId(1L) .withProblem(problem) .withConfigOverride( @@ -389,7 +389,7 @@ void multimoduleSolve() { .run(context -> { for (var solverName : List.of("solver1", "solver2")) { var solverManager = - (SolverManager) context.getBean(solverName); + (SolverManager) context.getBean(solverName); var problem = new TestdataSpringSolution(); problem.setValueList(IntStream.range(1, 3) .mapToObj(i -> "v" + i) diff --git a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverSingleSolverAutoConfigurationTest.java b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverSingleSolverAutoConfigurationTest.java index bb26eaf0aee..e9451949716 100644 --- a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverSingleSolverAutoConfigurationTest.java +++ b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverSingleSolverAutoConfigurationTest.java @@ -103,7 +103,7 @@ void solve() { .withClassLoader(allDefaultsFilteredClassLoader) .withPropertyValues("timefold.solver.termination.best-score-limit=0") .run(context -> { - SolverManager solverManager = context.getBean(SolverManager.class); + SolverManager solverManager = context.getBean(SolverManager.class); var problem = new TestdataSpringSolution(); problem.setValueList(IntStream.range(1, 3) .mapToObj(i -> "v" + i) @@ -158,7 +158,7 @@ void solveWithTimeOverride() { .mapToObj(i -> new TestdataSpringEntity()) .toList()); var solverJob = - (DefaultSolverJob) solverManager.solveBuilder() + (DefaultSolverJob) solverManager.solveBuilder() .withProblemId(1L) .withProblem(problem) .withConfigOverride( @@ -191,7 +191,7 @@ void multimoduleSolve() { .withClassLoader(allDefaultsFilteredClassLoader) .withPropertyValues("timefold.solver.termination.best-score-limit=0") .run(context -> { - SolverManager solverManager = context.getBean(SolverManager.class); + SolverManager solverManager = context.getBean(SolverManager.class); var problem = new TestdataSpringSolution(); problem.setValueList(IntStream.range(1, 3) .mapToObj(i -> "v" + i) @@ -213,7 +213,7 @@ void solveSupplierVariables() { .withPropertyValues( "timefold.solver.termination.best-score-limit=0") .run(context -> { - SolverManager solverManager = + SolverManager solverManager = context.getBean(SolverManager.class); var problem = new TestdataSpringSupplierVariableSolution(); problem.setValueList(List.of("a", "b")); diff --git a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverWithSolverConfigXmlAutoConfigurationTest.java b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverWithSolverConfigXmlAutoConfigurationTest.java index ff7815cb2bd..97fc3a6e7af 100644 --- a/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverWithSolverConfigXmlAutoConfigurationTest.java +++ b/spring-integration/spring-boot-autoconfigure/src/test/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverWithSolverConfigXmlAutoConfigurationTest.java @@ -368,7 +368,7 @@ void singletonSolverFactory() { var solverManager = context.getBean(SolverManager.class); assertThat(solverManager).isNotNull(); // There is only one SolverFactory instance - assertThat(((DefaultSolverManager) solverManager).getSolverFactory()) + assertThat(((DefaultSolverManager) solverManager).getSolverFactory()) .isSameAs(solverFactory); }); } From 1be82c854859af5ffd40e7a9b5033e457ebddf0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Petrovick=C3=BD?= Date: Wed, 18 Feb 2026 14:06:40 +0100 Subject: [PATCH 2/4] refactor: validate constraint name --- .../api/score/constraint/ConstraintRef.java | 4 +- .../api/score/stream/ConstraintBuilder.java | 26 +++--- .../score/stream/bi/BiConstraintBuilder.java | 9 +- .../stream/quad/QuadConstraintBuilder.java | 10 +-- .../stream/tri/TriConstraintBuilder.java | 10 +-- .../stream/uni/UniConstraintBuilder.java | 10 +-- .../common/AbstractConstraintBuilder.java | 38 +++++++-- .../common/bi/BiConstraintBuilderImpl.java | 28 +++---- .../quad/QuadConstraintBuilderImpl.java | 30 ++++--- .../common/tri/TriConstraintBuilderImpl.java | 29 +++---- .../common/uni/UniConstraintBuilderImpl.java | 28 +++---- .../stream/AbstractConstraintBuilderTest.java | 82 +++++++++++++++++++ .../common/bi/BiConstraintBuilderTest.java | 28 +++++++ .../quad/QuadConstraintBuilderTest.java | 28 +++++++ .../common/tri/TriConstraintBuilderTest.java | 28 +++++++ .../common/uni/UniConstraintBuilderTest.java | 28 +++++++ docs/TODO.md | 1 + 17 files changed, 316 insertions(+), 101 deletions(-) create mode 100644 core/src/test/java/ai/timefold/solver/core/impl/score/stream/AbstractConstraintBuilderTest.java create mode 100644 core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/bi/BiConstraintBuilderTest.java create mode 100644 core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/quad/QuadConstraintBuilderTest.java create mode 100644 core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/tri/TriConstraintBuilderTest.java create mode 100644 core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/uni/UniConstraintBuilderTest.java diff --git a/core/src/main/java/ai/timefold/solver/core/api/score/constraint/ConstraintRef.java b/core/src/main/java/ai/timefold/solver/core/api/score/constraint/ConstraintRef.java index f86f5459c6c..ed17f06bf21 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/score/constraint/ConstraintRef.java +++ b/core/src/main/java/ai/timefold/solver/core/api/score/constraint/ConstraintRef.java @@ -1,6 +1,6 @@ package ai.timefold.solver.core.api.score.constraint; -import java.util.Objects; +import ai.timefold.solver.core.impl.score.stream.common.AbstractConstraintBuilder; import org.jspecify.annotations.NullMarked; @@ -24,7 +24,7 @@ public static ConstraintRef of(String constraintName) { } public ConstraintRef { - var sanitized = Objects.requireNonNull(constraintName).trim(); + var sanitized = AbstractConstraintBuilder.sanitize("constraintName", constraintName); if (sanitized.isEmpty()) { throw new IllegalArgumentException("The %s cannot be empty." .formatted("constraint name")); diff --git a/core/src/main/java/ai/timefold/solver/core/api/score/stream/ConstraintBuilder.java b/core/src/main/java/ai/timefold/solver/core/api/score/stream/ConstraintBuilder.java index a986e411988..c8cbe7b36bc 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/score/stream/ConstraintBuilder.java +++ b/core/src/main/java/ai/timefold/solver/core/api/score/stream/ConstraintBuilder.java @@ -2,8 +2,9 @@ import ai.timefold.solver.core.api.score.constraint.ConstraintMatchTotal; -import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; +@NullMarked public interface ConstraintBuilder { /** @@ -12,29 +13,34 @@ public interface ConstraintBuilder { * * @param constraintName shows up in {@link ConstraintMatchTotal} during score justification */ - default @NonNull Constraint asConstraint(@NonNull String constraintName) { + default Constraint asConstraint(String constraintName) { return asConstraintDescribed(constraintName, ""); } /** - * Builds a {@link Constraint} from the constraint stream. - * The constraint will be placed in the {@link Constraint#DEFAULT_CONSTRAINT_GROUP default constraint group}. + * As defined by {@link #asConstraintDescribed(String, String, String)}, + * placing the constraint in the {@link Constraint#DEFAULT_CONSTRAINT_GROUP default constraint group}. * * @param constraintName shows up in {@link ConstraintMatchTotal} during score justification */ - @NonNull - default Constraint asConstraintDescribed(@NonNull String constraintName, @NonNull String constraintDescription) { + default Constraint asConstraintDescribed(String constraintName, String constraintDescription) { return asConstraintDescribed(constraintName, constraintDescription, Constraint.DEFAULT_CONSTRAINT_GROUP); } /** * Builds a {@link Constraint} from the constraint stream. + * Both the constraint name and the constraint group are only allowed + * to contain alphanumeric characters, " ", "-" or "_". + * The constraint description can contain any character, but it is recommended to keep it short and concise. + *

+ * Unlike the constraint name and group, + * the constraint description is unlikely to be used externally as an identifier, + * and therefore doesn't need to be URL-friendly, or protected against injection attacks. * * @param constraintName shows up in {@link ConstraintMatchTotal} during score justification - * @param constraintGroup only allows alphanumeric characters, "-" and "_" + * @param constraintGroup not used by the solver directly, but may be used by external tools to group constraints together, + * such as by their source or by their purpose */ - @NonNull - Constraint asConstraintDescribed(@NonNull String constraintName, @NonNull String constraintDescription, - @NonNull String constraintGroup); + Constraint asConstraintDescribed(String constraintName, String constraintDescription, String constraintGroup); } diff --git a/core/src/main/java/ai/timefold/solver/core/api/score/stream/bi/BiConstraintBuilder.java b/core/src/main/java/ai/timefold/solver/core/api/score/stream/bi/BiConstraintBuilder.java index 29d352124f1..1c05aeee2bd 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/score/stream/bi/BiConstraintBuilder.java +++ b/core/src/main/java/ai/timefold/solver/core/api/score/stream/bi/BiConstraintBuilder.java @@ -12,7 +12,7 @@ import ai.timefold.solver.core.api.score.stream.ConstraintBuilder; import ai.timefold.solver.core.api.score.stream.ConstraintJustification; -import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; /** * Used to build a {@link Constraint} out of a {@link BiConstraintStream}, applying optional configuration. @@ -25,6 +25,7 @@ * Unless {@link #indictWith(BiFunction)} is called, the default indicted objects' mapping will be used. * The function takes the input arguments and converts them into a {@link java.util.List}. */ +@NullMarked public interface BiConstraintBuilder> extends ConstraintBuilder { /** @@ -33,9 +34,8 @@ public interface BiConstraintBuilder> extends * @see ConstraintMatch * @return this */ - @NonNull BiConstraintBuilder justifyWith( - @NonNull TriFunction justificationMapping); + TriFunction justificationMapping); /** * Sets a custom function to mark any object returned by it as responsible for causing the constraint to match. @@ -44,7 +44,6 @@ BiConstraintBuilder indictWith(@NonNull BiFunction> indictedObjectsMapping); + BiConstraintBuilder indictWith(BiFunction> indictedObjectsMapping); } diff --git a/core/src/main/java/ai/timefold/solver/core/api/score/stream/quad/QuadConstraintBuilder.java b/core/src/main/java/ai/timefold/solver/core/api/score/stream/quad/QuadConstraintBuilder.java index 1a691375791..f5b8734b21c 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/score/stream/quad/QuadConstraintBuilder.java +++ b/core/src/main/java/ai/timefold/solver/core/api/score/stream/quad/QuadConstraintBuilder.java @@ -12,7 +12,7 @@ import ai.timefold.solver.core.api.score.stream.ConstraintBuilder; import ai.timefold.solver.core.api.score.stream.ConstraintJustification; -import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; /** * Used to build a {@link Constraint} out of a {@link QuadConstraintStream}, applying optional configuration. @@ -24,6 +24,7 @@ * Unless {@link #indictWith(QuadFunction)} is called, the default indicted objects' mapping will be used. * The function takes the input arguments and converts them into a {@link java.util.List}. */ +@NullMarked public interface QuadConstraintBuilder> extends ConstraintBuilder { /** @@ -32,8 +33,8 @@ public interface QuadConstraintBuilder> * @return this * @see ConstraintMatch */ - @NonNull QuadConstraintBuilder justifyWith( - @NonNull PentaFunction justificationMapping); + QuadConstraintBuilder justifyWith( + PentaFunction justificationMapping); /** * Sets a custom function to mark any object returned by it as responsible for causing the constraint to match. @@ -42,8 +43,7 @@ public interface QuadConstraintBuilder> * * @return this */ - @NonNull QuadConstraintBuilder indictWith( - @NonNull QuadFunction> indictedObjectsMapping); + QuadFunction> indictedObjectsMapping); } diff --git a/core/src/main/java/ai/timefold/solver/core/api/score/stream/tri/TriConstraintBuilder.java b/core/src/main/java/ai/timefold/solver/core/api/score/stream/tri/TriConstraintBuilder.java index f015f2c4c71..64783749a79 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/score/stream/tri/TriConstraintBuilder.java +++ b/core/src/main/java/ai/timefold/solver/core/api/score/stream/tri/TriConstraintBuilder.java @@ -12,7 +12,7 @@ import ai.timefold.solver.core.api.score.stream.ConstraintBuilder; import ai.timefold.solver.core.api.score.stream.ConstraintJustification; -import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; /** * Used to build a {@link Constraint} out of a {@link TriConstraintStream}, applying optional configuration. @@ -24,6 +24,7 @@ * Unless {@link #indictWith(TriFunction)} is called, the default indicted objects' mapping will be used. * The function takes the input arguments and converts them into a {@link java.util.List}. */ +@NullMarked public interface TriConstraintBuilder> extends ConstraintBuilder { /** @@ -32,8 +33,8 @@ public interface TriConstraintBuilder> ext * @see ConstraintMatch * @return this */ - @NonNull TriConstraintBuilder justifyWith( - @NonNull QuadFunction justificationMapping); + TriConstraintBuilder justifyWith( + QuadFunction justificationMapping); /** * Sets a custom function to mark any object returned by it as responsible for causing the constraint to match. @@ -42,7 +43,6 @@ public interface TriConstraintBuilder> ext * * @return this */ - @NonNull - TriConstraintBuilder indictWith(@NonNull TriFunction> indictedObjectsMapping); + TriConstraintBuilder indictWith(TriFunction> indictedObjectsMapping); } diff --git a/core/src/main/java/ai/timefold/solver/core/api/score/stream/uni/UniConstraintBuilder.java b/core/src/main/java/ai/timefold/solver/core/api/score/stream/uni/UniConstraintBuilder.java index 6cde6334446..557fabab18a 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/score/stream/uni/UniConstraintBuilder.java +++ b/core/src/main/java/ai/timefold/solver/core/api/score/stream/uni/UniConstraintBuilder.java @@ -12,7 +12,7 @@ import ai.timefold.solver.core.api.score.stream.ConstraintBuilder; import ai.timefold.solver.core.api.score.stream.ConstraintJustification; -import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; /** * Used to build a {@link Constraint} out of a {@link UniConstraintStream}, applying optional configuration. @@ -24,6 +24,7 @@ * Unless {@link #indictWith(Function)} is called, the default indicted objects' mapping will be used. * The function takes the input arguments and converts them into a {@link java.util.List}. */ +@NullMarked public interface UniConstraintBuilder> extends ConstraintBuilder { /** @@ -34,8 +35,8 @@ public interface UniConstraintBuilder> extends C * @return this * @see ConstraintMatch */ - @NonNull UniConstraintBuilder justifyWith( - @NonNull BiFunction justificationMapping); + UniConstraintBuilder justifyWith( + BiFunction justificationMapping); /** * Sets a custom function to mark any object returned by it as responsible for causing the constraint to match. @@ -44,7 +45,6 @@ public interface UniConstraintBuilder> extends C * * @return this */ - @NonNull - UniConstraintBuilder indictWith(@NonNull Function> indictedObjectsMapping); + UniConstraintBuilder indictWith(Function> indictedObjectsMapping); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/AbstractConstraintBuilder.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/AbstractConstraintBuilder.java index e91b322671d..e2773ae1911 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/AbstractConstraintBuilder.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/AbstractConstraintBuilder.java @@ -6,10 +6,13 @@ import ai.timefold.solver.core.api.score.stream.Constraint; import ai.timefold.solver.core.api.score.stream.ConstraintBuilder; -import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; @SuppressWarnings("rawtypes") +@NullMarked public abstract class AbstractConstraintBuilder> implements ConstraintBuilder { + private final ConstraintConstructor constraintConstructor; private final ScoreImpactType impactType; private final Score_ constraintWeight; @@ -21,16 +24,37 @@ protected AbstractConstraintBuilder(ConstraintConstructor constraintConstructor, this.constraintWeight = constraintWeight; } - protected abstract JustificationMapping_ getJustificationMapping(); + protected abstract @Nullable JustificationMapping_ getJustificationMapping(); - protected abstract IndictedObjectsMapping_ getIndictedObjectsMapping(); + protected abstract @Nullable IndictedObjectsMapping_ getIndictedObjectsMapping(); @SuppressWarnings("unchecked") @Override - public final @NonNull Constraint asConstraintDescribed(@NonNull String constraintName, - @NonNull String constraintDescription, @NonNull String constraintGroup) { - return constraintConstructor.apply(constraintName, constraintDescription, constraintGroup, constraintWeight, - impactType, getJustificationMapping(), getIndictedObjectsMapping()); + public final Constraint asConstraintDescribed(String constraintName, String constraintDescription, String constraintGroup) { + return constraintConstructor.apply(sanitize("constraintName", constraintName), constraintDescription, + sanitize("constraintGroup", constraintGroup), constraintWeight, impactType, getJustificationMapping(), + getIndictedObjectsMapping()); + } + + public static String sanitize(String fieldName, String fieldValue) { + if (fieldValue == null || fieldValue.equalsIgnoreCase("null") || fieldValue.equalsIgnoreCase("nil")) { + throw new IllegalArgumentException("The %s cannot be null.".formatted(fieldName)); + } + if (!fieldValue.matches("^[a-zA-Z0-9]+[a-zA-Z0-9 _.'-]*$")) { + throw new IllegalArgumentException( + """ + The %s (%s) must only contain alphanumeric characters, spaces, underscores, hyphens, apostrophes ("'") or full stops ("."). + It must start with an alphanumeric character. + Names "null" and "nil" are not allowed either. + """ + .formatted(fieldName, fieldValue)); + } + var trimmed = fieldValue.trim(); + if (trimmed.isEmpty()) { + throw new IllegalArgumentException( + "The %s (%s) must not be empty or only contain whitespace.".formatted(fieldName, fieldName)); + } + return trimmed; } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/bi/BiConstraintBuilderImpl.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/bi/BiConstraintBuilderImpl.java index 56d3b302b32..fd14be3baba 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/bi/BiConstraintBuilderImpl.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/bi/BiConstraintBuilderImpl.java @@ -11,14 +11,15 @@ import ai.timefold.solver.core.impl.score.stream.common.AbstractConstraintBuilder; import ai.timefold.solver.core.impl.score.stream.common.ScoreImpactType; -import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; -public final class BiConstraintBuilderImpl> - extends AbstractConstraintBuilder +@NullMarked +public final class BiConstraintBuilderImpl> extends AbstractConstraintBuilder implements BiConstraintBuilder { - private TriFunction justificationMapping; - private BiFunction> indictedObjectsMapping; + private @Nullable TriFunction justificationMapping; + private @Nullable BiFunction> indictedObjectsMapping; public BiConstraintBuilderImpl(BiConstraintConstructor constraintConstructor, ScoreImpactType impactType, Score_ constraintWeight) { @@ -26,18 +27,17 @@ public BiConstraintBuilderImpl(BiConstraintConstructor constraintC } @Override - protected TriFunction getJustificationMapping() { + protected @Nullable TriFunction getJustificationMapping() { return justificationMapping; } @Override - public @NonNull BiConstraintBuilder justifyWith( - @NonNull TriFunction justificationMapping) { + public BiConstraintBuilder + justifyWith(TriFunction justificationMapping) { if (this.justificationMapping != null) { throw new IllegalStateException(""" Justification mapping already set (%s). - Maybe the constraint calls justifyWith() twice?""" - .formatted(justificationMapping)); + Maybe the constraint calls justifyWith() twice?""".formatted(justificationMapping)); } this.justificationMapping = (TriFunction) Objects.requireNonNull(justificationMapping); @@ -45,18 +45,16 @@ Maybe the constraint calls justifyWith() twice?""" } @Override - protected BiFunction> getIndictedObjectsMapping() { + protected @Nullable BiFunction> getIndictedObjectsMapping() { return indictedObjectsMapping; } @Override - public @NonNull BiConstraintBuilder - indictWith(@NonNull BiFunction> indictedObjectsMapping) { + public BiConstraintBuilder indictWith(BiFunction> indictedObjectsMapping) { if (this.indictedObjectsMapping != null) { throw new IllegalStateException(""" Indicted objects' mapping already set (%s). - Maybe the constraint calls indictWith() twice?""" - .formatted(indictedObjectsMapping)); + Maybe the constraint calls indictWith() twice?""".formatted(indictedObjectsMapping)); } this.indictedObjectsMapping = Objects.requireNonNull(indictedObjectsMapping); return this; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/quad/QuadConstraintBuilderImpl.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/quad/QuadConstraintBuilderImpl.java index a8877e12848..1507d0bfaf5 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/quad/QuadConstraintBuilderImpl.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/quad/QuadConstraintBuilderImpl.java @@ -11,14 +11,15 @@ import ai.timefold.solver.core.impl.score.stream.common.AbstractConstraintBuilder; import ai.timefold.solver.core.impl.score.stream.common.ScoreImpactType; -import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; -public final class QuadConstraintBuilderImpl> - extends AbstractConstraintBuilder +@NullMarked +public final class QuadConstraintBuilderImpl> extends AbstractConstraintBuilder implements QuadConstraintBuilder { - private PentaFunction justificationMapping; - private QuadFunction> indictedObjectsMapping; + private @Nullable PentaFunction justificationMapping; + private @Nullable QuadFunction> indictedObjectsMapping; public QuadConstraintBuilderImpl(QuadConstraintConstructor constraintConstructor, ScoreImpactType impactType, Score_ constraintWeight) { @@ -26,19 +27,17 @@ public QuadConstraintBuilderImpl(QuadConstraintConstructor c } @Override - protected PentaFunction getJustificationMapping() { + protected @Nullable PentaFunction getJustificationMapping() { return justificationMapping; } @Override - public @NonNull QuadConstraintBuilder - justifyWith( - @NonNull PentaFunction justificationMapping) { + public QuadConstraintBuilder + justifyWith(PentaFunction justificationMapping) { if (this.justificationMapping != null) { throw new IllegalStateException(""" Justification mapping already set (%s). - Maybe the constraint calls justifyWith() twice?""" - .formatted(justificationMapping)); + Maybe the constraint calls justifyWith() twice?""".formatted(justificationMapping)); } this.justificationMapping = (PentaFunction) Objects.requireNonNull(justificationMapping); @@ -46,18 +45,17 @@ Maybe the constraint calls justifyWith() twice?""" } @Override - protected QuadFunction> getIndictedObjectsMapping() { + protected @Nullable QuadFunction> getIndictedObjectsMapping() { return indictedObjectsMapping; } @Override - public @NonNull QuadConstraintBuilder - indictWith(@NonNull QuadFunction> indictedObjectsMapping) { + public QuadConstraintBuilder + indictWith(QuadFunction> indictedObjectsMapping) { if (this.indictedObjectsMapping != null) { throw new IllegalStateException(""" Indicted objects' mapping already set (%s). - Maybe the constraint calls indictWith() twice?""" - .formatted(indictedObjectsMapping)); + Maybe the constraint calls indictWith() twice?""".formatted(indictedObjectsMapping)); } this.indictedObjectsMapping = Objects.requireNonNull(indictedObjectsMapping); return this; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/tri/TriConstraintBuilderImpl.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/tri/TriConstraintBuilderImpl.java index d268c68302b..d4f284b1ee9 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/tri/TriConstraintBuilderImpl.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/tri/TriConstraintBuilderImpl.java @@ -11,14 +11,15 @@ import ai.timefold.solver.core.impl.score.stream.common.AbstractConstraintBuilder; import ai.timefold.solver.core.impl.score.stream.common.ScoreImpactType; -import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; -public final class TriConstraintBuilderImpl> - extends AbstractConstraintBuilder +@NullMarked +public final class TriConstraintBuilderImpl> extends AbstractConstraintBuilder implements TriConstraintBuilder { - private QuadFunction justificationMapping; - private TriFunction> indictedObjectsMapping; + private @Nullable QuadFunction justificationMapping; + private @Nullable TriFunction> indictedObjectsMapping; public TriConstraintBuilderImpl(TriConstraintConstructor constraintConstructor, ScoreImpactType impactType, Score_ constraintWeight) { @@ -26,19 +27,17 @@ public TriConstraintBuilderImpl(TriConstraintConstructor constr } @Override - protected QuadFunction getJustificationMapping() { + protected @Nullable QuadFunction getJustificationMapping() { return justificationMapping; } @Override - public @NonNull TriConstraintBuilder - justifyWith( - @NonNull QuadFunction justificationMapping) { + public TriConstraintBuilder + justifyWith(QuadFunction justificationMapping) { if (this.justificationMapping != null) { throw new IllegalStateException(""" Justification mapping already set (%s). - Maybe the constraint calls justifyWith() twice?""" - .formatted(justificationMapping)); + Maybe the constraint calls justifyWith() twice?""".formatted(justificationMapping)); } this.justificationMapping = (QuadFunction) Objects.requireNonNull(justificationMapping); @@ -46,18 +45,16 @@ Maybe the constraint calls justifyWith() twice?""" } @Override - protected TriFunction> getIndictedObjectsMapping() { + protected @Nullable TriFunction> getIndictedObjectsMapping() { return indictedObjectsMapping; } @Override - public @NonNull TriConstraintBuilder - indictWith(@NonNull TriFunction> indictedObjectsMapping) { + public TriConstraintBuilder indictWith(TriFunction> indictedObjectsMapping) { if (this.indictedObjectsMapping != null) { throw new IllegalStateException(""" Indicted objects' mapping already set (%s). - Maybe the constraint calls indictWith() twice?""" - .formatted(indictedObjectsMapping)); + Maybe the constraint calls indictWith() twice?""".formatted(indictedObjectsMapping)); } this.indictedObjectsMapping = Objects.requireNonNull(indictedObjectsMapping); return this; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/uni/UniConstraintBuilderImpl.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/uni/UniConstraintBuilderImpl.java index 76f594e9b75..c96fd7b63e2 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/uni/UniConstraintBuilderImpl.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/uni/UniConstraintBuilderImpl.java @@ -11,14 +11,15 @@ import ai.timefold.solver.core.impl.score.stream.common.AbstractConstraintBuilder; import ai.timefold.solver.core.impl.score.stream.common.ScoreImpactType; -import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; -public final class UniConstraintBuilderImpl> - extends AbstractConstraintBuilder +@NullMarked +public final class UniConstraintBuilderImpl> extends AbstractConstraintBuilder implements UniConstraintBuilder { - private BiFunction justificationMapping; - private Function> indictedObjectsMapping; + private @Nullable BiFunction justificationMapping; + private @Nullable Function> indictedObjectsMapping; public UniConstraintBuilderImpl(UniConstraintConstructor constraintConstructor, ScoreImpactType impactType, Score_ constraintWeight) { @@ -26,18 +27,17 @@ public UniConstraintBuilderImpl(UniConstraintConstructor constraintCo } @Override - protected BiFunction getJustificationMapping() { + protected @Nullable BiFunction getJustificationMapping() { return justificationMapping; } @Override - public @NonNull UniConstraintBuilder justifyWith( - @NonNull BiFunction justificationMapping) { + public UniConstraintBuilder + justifyWith(BiFunction justificationMapping) { if (this.justificationMapping != null) { throw new IllegalStateException(""" Justification mapping already set (%s). - Maybe the constraint calls justifyWith() twice?""" - .formatted(justificationMapping)); + Maybe the constraint calls justifyWith() twice?""".formatted(justificationMapping)); } this.justificationMapping = (BiFunction) Objects.requireNonNull(justificationMapping); @@ -45,18 +45,16 @@ Maybe the constraint calls justifyWith() twice?""" } @Override - protected Function> getIndictedObjectsMapping() { + protected @Nullable Function> getIndictedObjectsMapping() { return indictedObjectsMapping; } @Override - public @NonNull UniConstraintBuilder - indictWith(@NonNull Function> indictedObjectsMapping) { + public UniConstraintBuilder indictWith(Function> indictedObjectsMapping) { if (this.indictedObjectsMapping != null) { throw new IllegalStateException(""" Indicted objects' mapping already set (%s). - Maybe the constraint calls indictWith() twice?""" - .formatted(indictedObjectsMapping)); + Maybe the constraint calls indictWith() twice?""".formatted(indictedObjectsMapping)); } this.indictedObjectsMapping = Objects.requireNonNull(indictedObjectsMapping); return this; diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/AbstractConstraintBuilderTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/AbstractConstraintBuilderTest.java new file mode 100644 index 00000000000..b78f3ad8cf5 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/AbstractConstraintBuilderTest.java @@ -0,0 +1,82 @@ +package ai.timefold.solver.core.impl.score.stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.UUID; + +import ai.timefold.solver.core.api.score.SimpleScore; +import ai.timefold.solver.core.impl.score.stream.common.AbstractConstraintBuilder; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +public abstract class AbstractConstraintBuilderTest { + + protected abstract AbstractConstraintBuilder of(String constraintName, String constraintGroup); + + @CsvSource(""" + name, true + Name and spaces, true + Name and numb3rs, true + name_and_numb3r5, true + name-and-numb3r5, true + Let's allow a complete sentence., true + name${something}name, false + name$1name, false + name%23name, false + name/name, false + name//name, false + name\\/name, false + name\\/\\/name, false + null, false + nil, false + , false + -, false + _, false""") + @ParameterizedTest + void nameSanitized(String name, boolean correct) { + if (correct) { + var constraint = of(name, "group") + .asConstraintDescribed(name, UUID.randomUUID().toString()); + assertThat(constraint) + .isNotNull(); + } else { + assertThatThrownBy(() -> of(name, "group").asConstraintDescribed(name, UUID.randomUUID().toString())) + .isExactlyInstanceOf(IllegalArgumentException.class); + } + } + + @CsvSource(""" + name, true + Name and spaces, true + Name and numb3rs, true + name_and_numb3r5, true + name-and-numb3r5, true + Let's allow a complete sentence., true + name${something}name, false + name$1name, false + name%23name, false + name/name, false + name//name, false + name\\/name, false + name\\/\\/name, false + null, false + nil, false + , false + -, false + _, false""") + @ParameterizedTest + void groupSanitized(String group, boolean correct) { + if (correct) { + var constraint = of("name", group) + .asConstraintDescribed(group, UUID.randomUUID().toString()); + assertThat(constraint) + .isNotNull(); + } else { + assertThatThrownBy(() -> of("name", group).asConstraintDescribed(group, UUID.randomUUID().toString())) + .isExactlyInstanceOf(IllegalArgumentException.class); + } + } + +} diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/bi/BiConstraintBuilderTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/bi/BiConstraintBuilderTest.java new file mode 100644 index 00000000000..ddd1ec723cf --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/bi/BiConstraintBuilderTest.java @@ -0,0 +1,28 @@ +package ai.timefold.solver.core.impl.score.stream.common.bi; + +import ai.timefold.solver.core.api.score.SimpleScore; +import ai.timefold.solver.core.api.score.constraint.ConstraintRef; +import ai.timefold.solver.core.config.solver.EnvironmentMode; +import ai.timefold.solver.core.impl.score.stream.AbstractConstraintBuilderTest; +import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraint; +import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.common.AbstractConstraintBuilder; +import ai.timefold.solver.core.impl.score.stream.common.ScoreImpactType; +import ai.timefold.solver.core.testdomain.TestdataSolution; + +class BiConstraintBuilderTest extends AbstractConstraintBuilderTest { + + private static final BavetConstraintFactory CONSTRAINT_FACTORY = + new BavetConstraintFactory<>(TestdataSolution.buildSolutionDescriptor(), EnvironmentMode.FULL_ASSERT); + + @Override + protected AbstractConstraintBuilder of(String constraintName, String constraintGroup) { + return new BiConstraintBuilderImpl<>( + (constraintName1, constraintDescription, constraintGroup1, constraintWeight, impactType, + objectSimpleScoreObjectBiFunction, + objectCollectionFunction) -> new BavetConstraint<>(CONSTRAINT_FACTORY, + ConstraintRef.of(constraintName1), constraintDescription, constraintGroup1, constraintWeight, + impactType, objectSimpleScoreObjectBiFunction, objectCollectionFunction, null), + ScoreImpactType.PENALTY, SimpleScore.ONE); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/quad/QuadConstraintBuilderTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/quad/QuadConstraintBuilderTest.java new file mode 100644 index 00000000000..f9f0751111f --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/quad/QuadConstraintBuilderTest.java @@ -0,0 +1,28 @@ +package ai.timefold.solver.core.impl.score.stream.common.quad; + +import ai.timefold.solver.core.api.score.SimpleScore; +import ai.timefold.solver.core.api.score.constraint.ConstraintRef; +import ai.timefold.solver.core.config.solver.EnvironmentMode; +import ai.timefold.solver.core.impl.score.stream.AbstractConstraintBuilderTest; +import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraint; +import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.common.AbstractConstraintBuilder; +import ai.timefold.solver.core.impl.score.stream.common.ScoreImpactType; +import ai.timefold.solver.core.testdomain.TestdataSolution; + +class QuadConstraintBuilderTest extends AbstractConstraintBuilderTest { + + private static final BavetConstraintFactory CONSTRAINT_FACTORY = + new BavetConstraintFactory<>(TestdataSolution.buildSolutionDescriptor(), EnvironmentMode.FULL_ASSERT); + + @Override + protected AbstractConstraintBuilder of(String constraintName, String constraintGroup) { + return new QuadConstraintBuilderImpl<>( + (constraintName1, constraintDescription, constraintGroup1, constraintWeight, impactType, + objectSimpleScoreObjectBiFunction, + objectCollectionFunction) -> new BavetConstraint<>(CONSTRAINT_FACTORY, + ConstraintRef.of(constraintName1), constraintDescription, constraintGroup1, constraintWeight, + impactType, objectSimpleScoreObjectBiFunction, objectCollectionFunction, null), + ScoreImpactType.PENALTY, SimpleScore.ONE); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/tri/TriConstraintBuilderTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/tri/TriConstraintBuilderTest.java new file mode 100644 index 00000000000..9b6dad39061 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/tri/TriConstraintBuilderTest.java @@ -0,0 +1,28 @@ +package ai.timefold.solver.core.impl.score.stream.common.tri; + +import ai.timefold.solver.core.api.score.SimpleScore; +import ai.timefold.solver.core.api.score.constraint.ConstraintRef; +import ai.timefold.solver.core.config.solver.EnvironmentMode; +import ai.timefold.solver.core.impl.score.stream.AbstractConstraintBuilderTest; +import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraint; +import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.common.AbstractConstraintBuilder; +import ai.timefold.solver.core.impl.score.stream.common.ScoreImpactType; +import ai.timefold.solver.core.testdomain.TestdataSolution; + +class TriConstraintBuilderTest extends AbstractConstraintBuilderTest { + + private static final BavetConstraintFactory CONSTRAINT_FACTORY = + new BavetConstraintFactory<>(TestdataSolution.buildSolutionDescriptor(), EnvironmentMode.FULL_ASSERT); + + @Override + protected AbstractConstraintBuilder of(String constraintName, String constraintGroup) { + return new TriConstraintBuilderImpl<>( + (constraintName1, constraintDescription, constraintGroup1, constraintWeight, impactType, + objectSimpleScoreObjectBiFunction, + objectCollectionFunction) -> new BavetConstraint<>(CONSTRAINT_FACTORY, + ConstraintRef.of(constraintName1), constraintDescription, constraintGroup1, constraintWeight, + impactType, objectSimpleScoreObjectBiFunction, objectCollectionFunction, null), + ScoreImpactType.PENALTY, SimpleScore.ONE); + } +} diff --git a/core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/uni/UniConstraintBuilderTest.java b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/uni/UniConstraintBuilderTest.java new file mode 100644 index 00000000000..4940ec452c1 --- /dev/null +++ b/core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/uni/UniConstraintBuilderTest.java @@ -0,0 +1,28 @@ +package ai.timefold.solver.core.impl.score.stream.common.uni; + +import ai.timefold.solver.core.api.score.SimpleScore; +import ai.timefold.solver.core.api.score.constraint.ConstraintRef; +import ai.timefold.solver.core.config.solver.EnvironmentMode; +import ai.timefold.solver.core.impl.score.stream.AbstractConstraintBuilderTest; +import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraint; +import ai.timefold.solver.core.impl.score.stream.bavet.BavetConstraintFactory; +import ai.timefold.solver.core.impl.score.stream.common.AbstractConstraintBuilder; +import ai.timefold.solver.core.impl.score.stream.common.ScoreImpactType; +import ai.timefold.solver.core.testdomain.TestdataSolution; + +class UniConstraintBuilderTest extends AbstractConstraintBuilderTest { + + private static final BavetConstraintFactory CONSTRAINT_FACTORY = + new BavetConstraintFactory<>(TestdataSolution.buildSolutionDescriptor(), EnvironmentMode.FULL_ASSERT); + + @Override + protected AbstractConstraintBuilder of(String constraintName, String constraintGroup) { + return new UniConstraintBuilderImpl<>( + (constraintName1, constraintDescription, constraintGroup1, constraintWeight, impactType, + objectSimpleScoreObjectBiFunction, + objectCollectionFunction) -> new BavetConstraint<>(CONSTRAINT_FACTORY, + ConstraintRef.of(constraintName1), constraintDescription, constraintGroup1, constraintWeight, + impactType, objectSimpleScoreObjectBiFunction, objectCollectionFunction, null), + ScoreImpactType.PENALTY, SimpleScore.ONE); + } +} diff --git a/docs/TODO.md b/docs/TODO.md index faa3c38dfd4..5fe76194f1d 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -37,5 +37,6 @@ - [ ] `Rebaser` renamed to `Lookup` and made public. - [ ] `ProblemChangeDirector#lookUpWorkingObject()` no longer returns `Optional`. - [ ] `AutoDiscoverMemberType` is gone +- [ ] Constraint name and group now forces strict validation. Remove this file when done. \ No newline at end of file From 546dadefbf3cb70650e44188da98761b47664ae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Petrovick=C3=BD?= Date: Sat, 21 Feb 2026 11:23:23 +0100 Subject: [PATCH 3/4] refactor: remove use of SecurityManager --- .../thread/DefaultSolverThreadFactory.java | 31 +++++++------------ 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/core/src/main/java/ai/timefold/solver/core/impl/solver/thread/DefaultSolverThreadFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/solver/thread/DefaultSolverThreadFactory.java index 59e4e18e428..f0ea3f0ee16 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/solver/thread/DefaultSolverThreadFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/solver/thread/DefaultSolverThreadFactory.java @@ -4,39 +4,32 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; +import org.jspecify.annotations.NullMarked; + /** * Similar to {@link Executors}'s DefaultThreadFactory, but allows settings a namePrefix. */ -public class DefaultSolverThreadFactory implements ThreadFactory { +@NullMarked +public final class DefaultSolverThreadFactory implements ThreadFactory { private static final AtomicInteger poolNumber = new AtomicInteger(1); - private final ThreadGroup group; private final AtomicInteger threadNumber = new AtomicInteger(1); private final String namePrefix; - public DefaultSolverThreadFactory() { - this("Solver"); - } - public DefaultSolverThreadFactory(String threadPrefix) { - SecurityManager s = System.getSecurityManager(); - group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); - namePrefix = "Timefold-" + poolNumber.getAndIncrement() + "-" + threadPrefix + "-"; + namePrefix = "Timefold-%d-%s-" + .formatted(poolNumber.incrementAndGet(), threadPrefix); } @Override public Thread newThread(Runnable r) { - Thread t = new Thread(group, r, - namePrefix + threadNumber.getAndIncrement(), - 0); - if (t.isDaemon()) { - t.setDaemon(false); - } - if (t.getPriority() != Thread.NORM_PRIORITY) { - t.setPriority(Thread.NORM_PRIORITY); - } - return t; + return Thread.ofPlatform() + .name(namePrefix + threadNumber.getAndIncrement()) + .group(Thread.currentThread().getThreadGroup()) + .priority(Thread.NORM_PRIORITY) + .daemon(false) + .unstarted(r); } } From 0e7b8ecad007ebad2b6487bfc790537a12d8486d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Petrovick=C3=BD?= Date: Sat, 21 Feb 2026 12:10:02 +0100 Subject: [PATCH 4/4] refactor: remove uses of 3rd-party deprecated code --- build/build-parent/pom.xml | 13 +++---- .../gizmo/GizmoSolutionClonerImplementor.java | 2 +- .../io/jaxb/adapter/JaxbLocaleAdapter.java | 4 +- .../core/impl/util/MutableReference.java | 4 ++ .../core/api/solver/SolverManagerTest.java | 12 +++--- .../solver/termination/TerminationTest.java | 4 +- .../solver/core/testutil/PlannerAssert.java | 9 +++-- ...ConstraintWeightOverridesDeserializer.java | 2 +- .../PolymorphicScoreJacksonDeserializer.java | 2 +- .../jackson/TimefoldJacksonModuleTest.java | 37 ++++++++++++------- .../common/LoadBalanceRoundTripTest.java | 3 +- .../stream/common/SequenceRoundTripTest.java | 3 +- .../GizmoMemberAccessorEntityEnhancer.java | 2 +- ...secondDurationNumberFormatFactoryTest.java | 4 +- .../testBenchmarkConfigWithNamespace.xml | 2 +- .../testBenchmarkConfigWithoutNamespace.xml | 2 +- 16 files changed, 61 insertions(+), 44 deletions(-) diff --git a/build/build-parent/pom.xml b/build/build-parent/pom.xml index 5b0345774f8..28f7ca8239c 100644 --- a/build/build-parent/pom.xml +++ b/build/build-parent/pom.xml @@ -447,14 +447,11 @@ ${maven.compiler.release} ${project.build.sourceEncoding} - - -Xmaxwarns - 100 - -Xlint - -Xlint:-rawtypes - -Xlint:-serial - -Xlint:-unchecked + + -Xlint:none diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/cloner/gizmo/GizmoSolutionClonerImplementor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/cloner/gizmo/GizmoSolutionClonerImplementor.java index 5e44186283a..e01c81142fb 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/cloner/gizmo/GizmoSolutionClonerImplementor.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/solution/cloner/gizmo/GizmoSolutionClonerImplementor.java @@ -272,7 +272,7 @@ private static void createCloneSolutionRun(ClonerDescriptor clonerDescriptor, blockCreator.withObject(thisObj).getClass_()); for (Class solutionSubclass : sortedSolutionClassList) { var solutionSubclassConst = Const.of(solutionSubclass); - var isSubclass = blockCreator.objEquals(solutionSubclassConst, thisObjClass); + var isSubclass = blockCreator.exprEquals(solutionSubclassConst, thisObjClass); blockCreator.if_(isSubclass, isExactMatchBranch -> { // Note: it appears Gizmo2 does not have a way to cast expressions, so we need to // use an ifInstanceOf to get a casted version diff --git a/core/src/main/java/ai/timefold/solver/core/impl/io/jaxb/adapter/JaxbLocaleAdapter.java b/core/src/main/java/ai/timefold/solver/core/impl/io/jaxb/adapter/JaxbLocaleAdapter.java index 79773c34488..14ee2b8a0f8 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/io/jaxb/adapter/JaxbLocaleAdapter.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/io/jaxb/adapter/JaxbLocaleAdapter.java @@ -11,7 +11,7 @@ public Locale unmarshal(String localeString) { if (localeString == null) { return null; } - return new Locale(localeString); + return Locale.forLanguageTag(localeString); } @Override @@ -19,6 +19,6 @@ public String marshal(Locale locale) { if (locale == null) { return null; } - return locale.toString(); + return locale.toLanguageTag(); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/util/MutableReference.java b/core/src/main/java/ai/timefold/solver/core/impl/util/MutableReference.java index 988aadd0ad1..0c9d92fbdad 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/util/MutableReference.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/util/MutableReference.java @@ -10,6 +10,10 @@ public final class MutableReference { private @Nullable Value_ value; + public MutableReference() { + this(null); + } + public MutableReference(@Nullable Value_ value) { this.value = value; } 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 e843640f07e..7c662df0b7e 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 @@ -568,7 +568,7 @@ void testStartJobConsumer() throws ExecutionException, InterruptedException { .withSolverJobStartedEventConsumer(event -> started.increment()) .run(); solverJob.getFinalBestSolution(); - assertThat(started.getValue()).isOne(); + assertThat(started.intValue()).isOne(); } } @@ -825,7 +825,7 @@ void solveWithBuilder() throws InterruptedException, BrokenBarrierException { .run(); startedBarrier.await(); - assertThat(finalBestSolution.getValue()).isNotNull(); + assertThat(finalBestSolution.get()).isNotNull(); } } @@ -860,9 +860,9 @@ void solveAndListenWithBuilder() throws InterruptedException, BrokenBarrierExcep .run(); startedBarrier.await(); - assertThat(finalBestSolution.getValue()).isNotNull(); - assertThat(bestSolution.getValue()).isNotNull(); - assertThat(producerId.getValue()).isNotNull(); + assertThat(finalBestSolution.get()).isNotNull(); + assertThat(bestSolution.get()).isNotNull(); + assertThat(producerId.get()).isNotNull(); } } @@ -1325,7 +1325,7 @@ void readFromEvent(FirstInitializedSolutionEvent event) { } boolean isInitialized() { - return isInitializedRef.getValue(); + return isInitializedRef.get(); } EventProducerId producerId() { diff --git a/core/src/test/java/ai/timefold/solver/core/impl/solver/termination/TerminationTest.java b/core/src/test/java/ai/timefold/solver/core/impl/solver/termination/TerminationTest.java index 436ab66cbff..cca00b66eb5 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/solver/termination/TerminationTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/solver/termination/TerminationTest.java @@ -42,6 +42,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ArgumentsProvider; import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.jupiter.params.support.ParameterDeclarations; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -350,7 +351,8 @@ void mixedSolverPhaseTerminations() { static class TerminationArgumentSource implements ArgumentsProvider { @Override - public Stream provideArguments(ExtensionContext context) throws Exception { + public Stream provideArguments(ParameterDeclarations declarations, ExtensionContext context) + throws Exception { return Stream.of( Arguments.of(new TerminationConfig().withStepCountLimit(10000)), Arguments.of(new TerminationConfig().withScoreCalculationCountLimit(10000L)), diff --git a/core/src/test/java/ai/timefold/solver/core/testutil/PlannerAssert.java b/core/src/test/java/ai/timefold/solver/core/testutil/PlannerAssert.java index 501cdc24d71..09bf665d2c0 100644 --- a/core/src/test/java/ai/timefold/solver/core/testutil/PlannerAssert.java +++ b/core/src/test/java/ai/timefold/solver/core/testutil/PlannerAssert.java @@ -135,7 +135,7 @@ public static void assertCompareToEquals(Comparator comparator, T... obje // PhaseLifecycleListener methods // ************************************************************************ - public static void verifyPhaseLifecycle(PhaseLifecycleListener phaseLifecycleListener, + public static void verifyPhaseLifecycle(PhaseLifecycleListener phaseLifecycleListener, int solvingCount, int phaseCount, int stepCount) { verify(phaseLifecycleListener, times(solvingCount)).solvingStarted(any(SolverScope.class)); verify(phaseLifecycleListener, times(phaseCount)).phaseStarted(any(AbstractPhaseScope.class)); @@ -145,7 +145,8 @@ public static void verifyPhaseLifecycle(PhaseLifecycleListener phaseLifecycleLis verify(phaseLifecycleListener, times(solvingCount)).solvingEnded(any(SolverScope.class)); } - public static void verifyPhaseLifecycle(ConstructionHeuristicPhaseLifecycleListener phaseLifecycleListener, + public static void verifyPhaseLifecycle( + ConstructionHeuristicPhaseLifecycleListener phaseLifecycleListener, int solvingCount, int phaseCount, int stepCount) { verify(phaseLifecycleListener, times(solvingCount)).solvingStarted(any(SolverScope.class)); verify(phaseLifecycleListener, times(phaseCount)).phaseStarted(any(ConstructionHeuristicPhaseScope.class)); @@ -155,7 +156,7 @@ public static void verifyPhaseLifecycle(ConstructionHeuristicPhaseLifecycleListe verify(phaseLifecycleListener, times(solvingCount)).solvingEnded(any(SolverScope.class)); } - public static void verifyPhaseLifecycle(LocalSearchPhaseLifecycleListener phaseLifecycleListener, + public static void verifyPhaseLifecycle(LocalSearchPhaseLifecycleListener phaseLifecycleListener, int solvingCount, int phaseCount, int stepCount) { verify(phaseLifecycleListener, times(solvingCount)).solvingStarted(any(SolverScope.class)); verify(phaseLifecycleListener, times(phaseCount)).phaseStarted(any(LocalSearchPhaseScope.class)); @@ -444,7 +445,7 @@ public static void assertSolutionInitialized(TestdataSolution solution) { public static E extractSingleton(List singletonList) { assertThat(singletonList).hasSize(1); - return singletonList.get(0); + return singletonList.getFirst(); } private PlannerAssert() { diff --git a/quarkus-integration/quarkus-jackson/runtime/src/main/java/ai/timefold/solver/quarkus/jackson/domain/solution/AbstractConstraintWeightOverridesDeserializer.java b/quarkus-integration/quarkus-jackson/runtime/src/main/java/ai/timefold/solver/quarkus/jackson/domain/solution/AbstractConstraintWeightOverridesDeserializer.java index 2fdbf6de08a..fc1641705e5 100644 --- a/quarkus-integration/quarkus-jackson/runtime/src/main/java/ai/timefold/solver/quarkus/jackson/domain/solution/AbstractConstraintWeightOverridesDeserializer.java +++ b/quarkus-integration/quarkus-jackson/runtime/src/main/java/ai/timefold/solver/quarkus/jackson/domain/solution/AbstractConstraintWeightOverridesDeserializer.java @@ -23,7 +23,7 @@ public abstract class AbstractConstraintWeightOverridesDeserializer deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { var resultMap = new LinkedHashMap(); JsonNode node = p.readValueAsTree(); - node.fields().forEachRemaining(entry -> { + node.properties().forEach(entry -> { var constraintName = entry.getKey(); var weight = parseScore(entry.getValue().asText()); resultMap.put(constraintName, weight); diff --git a/quarkus-integration/quarkus-jackson/runtime/src/main/java/ai/timefold/solver/quarkus/jackson/score/PolymorphicScoreJacksonDeserializer.java b/quarkus-integration/quarkus-jackson/runtime/src/main/java/ai/timefold/solver/quarkus/jackson/score/PolymorphicScoreJacksonDeserializer.java index 6e1fa21432d..8beef02fdbb 100644 --- a/quarkus-integration/quarkus-jackson/runtime/src/main/java/ai/timefold/solver/quarkus/jackson/score/PolymorphicScoreJacksonDeserializer.java +++ b/quarkus-integration/quarkus-jackson/runtime/src/main/java/ai/timefold/solver/quarkus/jackson/score/PolymorphicScoreJacksonDeserializer.java @@ -33,7 +33,7 @@ public class PolymorphicScoreJacksonDeserializer extends JsonDeserializer @Override public Score deserialize(JsonParser parser, DeserializationContext context) throws IOException { parser.nextToken(); - String scoreClassSimpleName = parser.getCurrentName(); + String scoreClassSimpleName = parser.currentName(); parser.nextToken(); String scoreString = parser.getValueAsString(); if (scoreClassSimpleName.equals(SimpleScore.class.getSimpleName())) { diff --git a/quarkus-integration/quarkus-jackson/runtime/src/test/java/ai/timefold/solver/quarkus/jackson/TimefoldJacksonModuleTest.java b/quarkus-integration/quarkus-jackson/runtime/src/test/java/ai/timefold/solver/quarkus/jackson/TimefoldJacksonModuleTest.java index da98be90572..7c785aad26e 100644 --- a/quarkus-integration/quarkus-jackson/runtime/src/test/java/ai/timefold/solver/quarkus/jackson/TimefoldJacksonModuleTest.java +++ b/quarkus-integration/quarkus-jackson/runtime/src/test/java/ai/timefold/solver/quarkus/jackson/TimefoldJacksonModuleTest.java @@ -29,7 +29,6 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.MapperFeature; -import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.type.TypeFactory; @@ -67,9 +66,12 @@ void polymorphicScore() { @Test void scoreAnalysisWithoutMatches() throws JsonProcessingException { - var objectMapper = new ObjectMapper(); - objectMapper.setSerializationInclusion(JsonInclude.Include.NON_DEFAULT); - objectMapper.registerModule(TimefoldJacksonModule.createModule()); + var objectMapper = JsonMapper.builder() + .enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) + .defaultPropertyInclusion( + JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, JsonInclude.Include.NON_NULL)) + .addModule(TimefoldJacksonModule.createModule()) + .build(); var constraintRef1 = ConstraintRef.of("constraint1"); var constraintRef2 = ConstraintRef.of("constraint2"); @@ -107,9 +109,12 @@ void scoreAnalysisWithoutMatches() throws JsonProcessingException { @Test void scoreAnalysisWithMatches() throws JsonProcessingException { - var objectMapper = new ObjectMapper(); - objectMapper.setSerializationInclusion(JsonInclude.Include.NON_DEFAULT); - objectMapper.registerModule(TimefoldJacksonModule.createModule()); + var objectMapper = JsonMapper.builder() + .enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) + .defaultPropertyInclusion( + JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, JsonInclude.Include.NON_NULL)) + .addModule(TimefoldJacksonModule.createModule()) + .build(); var originalScoreAnalysis = getScoreAnalysis(); var serialized = objectMapper.writeValueAsString(originalScoreAnalysis); @@ -179,9 +184,12 @@ private static String getSerializedScoreAnalysis() { @Test void recommendedAssignment() throws JsonProcessingException { - var objectMapper = new ObjectMapper(); - objectMapper.setSerializationInclusion(JsonInclude.Include.NON_DEFAULT); - objectMapper.registerModule(TimefoldJacksonModule.createModule()); + var objectMapper = JsonMapper.builder() + .enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) + .defaultPropertyInclusion( + JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, JsonInclude.Include.NON_NULL)) + .addModule(TimefoldJacksonModule.createModule()) + .build(); var proposition = new Pair<>("A", "1"); var originalScoreAnalysis = getScoreAnalysis(); @@ -211,9 +219,12 @@ void recommendedAssignment() throws JsonProcessingException { @Test void constraintWeightOverrides() throws JsonProcessingException { - var objectMapper = new ObjectMapper(); - objectMapper.setSerializationInclusion(JsonInclude.Include.NON_DEFAULT); - objectMapper.registerModule(TimefoldJacksonModule.createModule()); + var objectMapper = JsonMapper.builder() + .enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) + .defaultPropertyInclusion( + JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, JsonInclude.Include.NON_NULL)) + .addModule(TimefoldJacksonModule.createModule()) + .build(); var constraintWeightOverrides = ConstraintWeightOverrides.of( Map.of( diff --git a/quarkus-integration/quarkus-jackson/runtime/src/test/java/ai/timefold/solver/quarkus/jackson/score/stream/common/LoadBalanceRoundTripTest.java b/quarkus-integration/quarkus-jackson/runtime/src/test/java/ai/timefold/solver/quarkus/jackson/score/stream/common/LoadBalanceRoundTripTest.java index dc7cfd3a2ea..88027043e4d 100644 --- a/quarkus-integration/quarkus-jackson/runtime/src/test/java/ai/timefold/solver/quarkus/jackson/score/stream/common/LoadBalanceRoundTripTest.java +++ b/quarkus-integration/quarkus-jackson/runtime/src/test/java/ai/timefold/solver/quarkus/jackson/score/stream/common/LoadBalanceRoundTripTest.java @@ -46,7 +46,8 @@ void roundTrip() throws JsonProcessingException { ObjectMapper objectMapper = JsonMapper.builder() .enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) - .serializationInclusion(JsonInclude.Include.NON_NULL) + .defaultPropertyInclusion( + JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, JsonInclude.Include.NON_NULL)) .addModule(TimefoldJacksonModule.createModule()) .build(); diff --git a/quarkus-integration/quarkus-jackson/runtime/src/test/java/ai/timefold/solver/quarkus/jackson/score/stream/common/SequenceRoundTripTest.java b/quarkus-integration/quarkus-jackson/runtime/src/test/java/ai/timefold/solver/quarkus/jackson/score/stream/common/SequenceRoundTripTest.java index 72566c3e696..77dbdea69f7 100644 --- a/quarkus-integration/quarkus-jackson/runtime/src/test/java/ai/timefold/solver/quarkus/jackson/score/stream/common/SequenceRoundTripTest.java +++ b/quarkus-integration/quarkus-jackson/runtime/src/test/java/ai/timefold/solver/quarkus/jackson/score/stream/common/SequenceRoundTripTest.java @@ -55,7 +55,8 @@ void roundTrip() throws JsonProcessingException { ObjectMapper objectMapper = JsonMapper.builder() .enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY) - .serializationInclusion(JsonInclude.Include.NON_NULL) + .defaultPropertyInclusion( + JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, JsonInclude.Include.NON_NULL)) .addModule(TimefoldJacksonModule.createModule()) .build(); diff --git a/quarkus-integration/quarkus/deployment/src/main/java/ai/timefold/solver/quarkus/deployment/GizmoMemberAccessorEntityEnhancer.java b/quarkus-integration/quarkus/deployment/src/main/java/ai/timefold/solver/quarkus/deployment/GizmoMemberAccessorEntityEnhancer.java index 32c210d14a2..44ebbadf82e 100644 --- a/quarkus-integration/quarkus/deployment/src/main/java/ai/timefold/solver/quarkus/deployment/GizmoMemberAccessorEntityEnhancer.java +++ b/quarkus-integration/quarkus/deployment/src/main/java/ai/timefold/solver/quarkus/deployment/GizmoMemberAccessorEntityEnhancer.java @@ -441,7 +441,7 @@ public void generateGizmoBeanFactory(ClassOutput classOutput, Set> bean } makeConstructorAccessible(beanClass, transformers); var beanClassHandle = Const.of(beanClass); - blockCreator.if_(blockCreator.objEquals(beanClassHandle, query), + blockCreator.if_(blockCreator.exprEquals(beanClassHandle, query), isQueryBranch -> isQueryBranch.return_(isQueryBranch.new_(beanClass))); } blockCreator.returnNull(); diff --git a/tools/benchmark/src/test/java/ai/timefold/solver/benchmark/impl/report/MillisecondDurationNumberFormatFactoryTest.java b/tools/benchmark/src/test/java/ai/timefold/solver/benchmark/impl/report/MillisecondDurationNumberFormatFactoryTest.java index 440567412bb..69bc547f5cf 100644 --- a/tools/benchmark/src/test/java/ai/timefold/solver/benchmark/impl/report/MillisecondDurationNumberFormatFactoryTest.java +++ b/tools/benchmark/src/test/java/ai/timefold/solver/benchmark/impl/report/MillisecondDurationNumberFormatFactoryTest.java @@ -21,13 +21,13 @@ class MillisecondDurationNumberFormatFactoryTest { private static final Locale DEFAULT_LOCALE = Locale.getDefault(); private static final TemplateNumberFormat NUMBER_FORMAT = - new MillisecondDurationNumberFormat(Locale.forLanguageTag("en_US")); + new MillisecondDurationNumberFormat(Locale.forLanguageTag("en-US")); private final TemplateNumberModel templateNumberModel = mock(TemplateNumberModel.class); @BeforeAll static void setExpectedLocale() { - Locale.setDefault(Locale.forLanguageTag("cs_CZ")); + Locale.setDefault(Locale.forLanguageTag("cs-CZ")); } @AfterAll diff --git a/tools/benchmark/src/test/resources/ai/timefold/solver/benchmark/config/testBenchmarkConfigWithNamespace.xml b/tools/benchmark/src/test/resources/ai/timefold/solver/benchmark/config/testBenchmarkConfigWithNamespace.xml index f59500f6796..b895173a77b 100644 --- a/tools/benchmark/src/test/resources/ai/timefold/solver/benchmark/config/testBenchmarkConfigWithNamespace.xml +++ b/tools/benchmark/src/test/resources/ai/timefold/solver/benchmark/config/testBenchmarkConfigWithNamespace.xml @@ -4,7 +4,7 @@ AUTO 0 - cs_cz + cs-CZ TOTAL_SCORE diff --git a/tools/benchmark/src/test/resources/ai/timefold/solver/benchmark/config/testBenchmarkConfigWithoutNamespace.xml b/tools/benchmark/src/test/resources/ai/timefold/solver/benchmark/config/testBenchmarkConfigWithoutNamespace.xml index 73d3745b762..33ec28a13a6 100644 --- a/tools/benchmark/src/test/resources/ai/timefold/solver/benchmark/config/testBenchmarkConfigWithoutNamespace.xml +++ b/tools/benchmark/src/test/resources/ai/timefold/solver/benchmark/config/testBenchmarkConfigWithoutNamespace.xml @@ -4,7 +4,7 @@ AUTO 0 - cs_cz + cs-CZ TOTAL_SCORE