From e8d40f00c4767000faaa8c951697c62e3db2c327 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Petrovick=C3=BD?= Date: Tue, 12 Nov 2024 12:23:18 +0100 Subject: [PATCH] chore: Address Sonar issues post-nullness (Part 3) --- .../impl/DefaultPlannerBenchmarkFactory.java | 21 +- .../impl/ProblemBenchmarksFactory.java | 59 ++--- .../impl/SolverBenchmarkFactory.java | 77 +++--- .../ranking/ResilientScoreComparator.java | 17 +- .../impl/report/BenchmarkReportFactory.java | 50 ++-- .../solver/core/api/score/stream/Joiners.java | 1 - ...aultConstructionHeuristicPhaseFactory.java | 35 +-- .../placer/QueuedEntityPlacerFactory.java | 132 ++++++----- .../placer/QueuedValuePlacerFactory.java | 44 ++-- .../DefaultExhaustiveSearchPhaseFactory.java | 39 ++- .../entity/EntitySelectorFactory.java | 209 ++++++++--------- .../selector/list/SubListSelectorFactory.java | 20 +- .../move/AbstractMoveSelectorFactory.java | 88 ++++--- .../composite/UnionMoveSelectorFactory.java | 14 +- .../factory/MoveIteratorFactoryFactory.java | 10 +- .../move/factory/MoveListFactoryFactory.java | 9 +- .../generic/ChangeMoveSelectorFactory.java | 28 ++- .../PillarChangeMoveSelectorFactory.java | 22 +- .../list/ListChangeMoveSelectorFactory.java | 139 +++++------ .../SubListChangeMoveSelectorFactory.java | 136 +++++------ .../selector/value/ValueSelectorFactory.java | 222 +++++++++--------- .../DefaultLocalSearchPhaseFactory.java | 40 ++-- .../decider/acceptor/AcceptorFactory.java | 48 ++-- .../forager/LocalSearchForagerFactory.java | 7 +- .../custom/DefaultCustomPhaseFactory.java | 42 ++-- .../director/ScoreDirectorFactoryFactory.java | 42 ++-- .../director/easy/EasyScoreDirector.java | 22 +- .../easy/EasyScoreDirectorFactory.java | 7 +- ...tConstraintStreamScoreDirectorFactory.java | 13 +- .../stream/common/InnerConstraintFactory.java | 46 ++-- .../impl/solver/DefaultSolverFactory.java | 27 ++- .../impl/solver/DefaultSolverManager.java | 17 +- .../termination/TerminationFactory.java | 23 +- .../migration/v8/AsConstraintRecipe.java | 12 +- .../ConstraintWeightOverridesSerializer.java | 3 +- .../jpyinterpreter/types/PythonLikeType.java | 12 +- .../jpyinterpreter/types/PythonString.java | 8 +- .../types/collections/PythonLikeDict.java | 6 +- .../collections/PythonLikeFrozenSet.java | 6 +- .../types/collections/PythonLikeList.java | 5 +- .../types/collections/PythonLikeSet.java | 3 +- .../types/collections/PythonLikeTuple.java | 5 +- .../types/collections/view/DictItemView.java | 3 +- .../types/wrappers/JavaObjectWrapper.java | 8 +- .../types/wrappers/PythonObjectWrapper.java | 9 +- .../util/ConcurrentWeakIdentityHashMap.java | 4 +- .../quarkus/TimefoldBenchmarkRecorder.java | 30 ++- .../quarkus/deployment/TimefoldProcessor.java | 22 +- .../TimefoldBenchmarkAutoConfiguration.java | 24 +- .../TimefoldSolverAutoConfiguration.java | 10 +- 50 files changed, 926 insertions(+), 950 deletions(-) diff --git a/benchmark/src/main/java/ai/timefold/solver/benchmark/impl/DefaultPlannerBenchmarkFactory.java b/benchmark/src/main/java/ai/timefold/solver/benchmark/impl/DefaultPlannerBenchmarkFactory.java index f1bb3bb562..6b54801067 100644 --- a/benchmark/src/main/java/ai/timefold/solver/benchmark/impl/DefaultPlannerBenchmarkFactory.java +++ b/benchmark/src/main/java/ai/timefold/solver/benchmark/impl/DefaultPlannerBenchmarkFactory.java @@ -14,7 +14,6 @@ import ai.timefold.solver.benchmark.api.PlannerBenchmarkFactory; import ai.timefold.solver.benchmark.config.PlannerBenchmarkConfig; import ai.timefold.solver.benchmark.config.SolverBenchmarkConfig; -import ai.timefold.solver.benchmark.config.blueprint.SolverBenchmarkBluePrintConfig; import ai.timefold.solver.benchmark.config.report.BenchmarkReportConfig; import ai.timefold.solver.benchmark.impl.report.BenchmarkReport; import ai.timefold.solver.benchmark.impl.report.BenchmarkReportFactory; @@ -148,20 +147,22 @@ protected void generateSolverBenchmarkConfigNames() { } protected List buildEffectiveSolverBenchmarkConfigList() { - List effectiveSolverBenchmarkConfigList = new ArrayList<>(0); - if (plannerBenchmarkConfig.getSolverBenchmarkConfigList() != null) { - effectiveSolverBenchmarkConfigList.addAll(plannerBenchmarkConfig.getSolverBenchmarkConfigList()); + var effectiveSolverBenchmarkConfigList = new ArrayList(0); + var solverBenchmarkConfigList = plannerBenchmarkConfig.getSolverBenchmarkConfigList(); + if (solverBenchmarkConfigList != null) { + effectiveSolverBenchmarkConfigList.addAll(solverBenchmarkConfigList); } - if (plannerBenchmarkConfig.getSolverBenchmarkBluePrintConfigList() != null) { - for (SolverBenchmarkBluePrintConfig solverBenchmarkBluePrintConfig : plannerBenchmarkConfig - .getSolverBenchmarkBluePrintConfigList()) { + var solverBenchmarkBluePrintConfigList = plannerBenchmarkConfig.getSolverBenchmarkBluePrintConfigList(); + if (solverBenchmarkBluePrintConfigList != null) { + for (var solverBenchmarkBluePrintConfig : solverBenchmarkBluePrintConfigList) { effectiveSolverBenchmarkConfigList.addAll(solverBenchmarkBluePrintConfig.buildSolverBenchmarkConfigList()); } } - if (plannerBenchmarkConfig.getInheritedSolverBenchmarkConfig() != null) { - for (SolverBenchmarkConfig solverBenchmarkConfig : effectiveSolverBenchmarkConfigList) { + var inheritedSolverBenchmarkConfig = plannerBenchmarkConfig.getInheritedSolverBenchmarkConfig(); + if (inheritedSolverBenchmarkConfig != null) { + for (var solverBenchmarkConfig : effectiveSolverBenchmarkConfigList) { // Side effect: changes the unmarshalled solverBenchmarkConfig - solverBenchmarkConfig.inherit(plannerBenchmarkConfig.getInheritedSolverBenchmarkConfig()); + solverBenchmarkConfig.inherit(inheritedSolverBenchmarkConfig); } } return effectiveSolverBenchmarkConfigList; diff --git a/benchmark/src/main/java/ai/timefold/solver/benchmark/impl/ProblemBenchmarksFactory.java b/benchmark/src/main/java/ai/timefold/solver/benchmark/impl/ProblemBenchmarksFactory.java index 7f067728a8..af71d8004b 100644 --- a/benchmark/src/main/java/ai/timefold/solver/benchmark/impl/ProblemBenchmarksFactory.java +++ b/benchmark/src/main/java/ai/timefold/solver/benchmark/impl/ProblemBenchmarksFactory.java @@ -4,10 +4,10 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import ai.timefold.solver.benchmark.api.PlannerBenchmarkFactory; import ai.timefold.solver.benchmark.config.ProblemBenchmarksConfig; -import ai.timefold.solver.benchmark.config.statistic.ProblemStatisticType; import ai.timefold.solver.benchmark.config.statistic.SingleStatisticType; import ai.timefold.solver.benchmark.impl.loader.FileProblemProvider; import ai.timefold.solver.benchmark.impl.loader.InstanceProblemProvider; @@ -30,6 +30,7 @@ public ProblemBenchmarksFactory(ProblemBenchmarksConfig config) { this.config = config; } + @SuppressWarnings({ "rawtypes", "unchecked" }) public void buildProblemBenchmarkList(SolverBenchmarkResult solverBenchmarkResult, Solution_[] extraProblems) { PlannerBenchmarkResult plannerBenchmarkResult = solverBenchmarkResult.getPlannerBenchmarkResult(); @@ -61,9 +62,8 @@ private List> buildProblemProviderList( + "Or maybe pass at least one problem to " + PlannerBenchmarkFactory.class.getSimpleName() + ".buildPlannerBenchmark()."); } - List> problemProviderList = new ArrayList<>( - extraProblems.length - + (config.getInputSolutionFileList() == null ? 0 : config.getInputSolutionFileList().size())); + List> problemProviderList = new ArrayList<>(extraProblems.length + + Objects.requireNonNullElse(config.getInputSolutionFileList(), Collections.emptyList()).size()); DefaultSolverFactory defaultSolverFactory = new DefaultSolverFactory<>(solverBenchmarkResult.getSolverConfig()); SolutionDescriptor solutionDescriptor = defaultSolverFactory.getSolutionDescriptor(); @@ -94,42 +94,47 @@ private List> buildProblemProviderList( return problemProviderList; } + @SuppressWarnings("unchecked") private SolutionFileIO buildSolutionFileIO() { - if (config.getSolutionFileIOClass() == null) { - throw new IllegalArgumentException( - "The solutionFileIOClass (" + config.getSolutionFileIOClass() + ") cannot be null."); + var solutionFileIOClass = config.getSolutionFileIOClass(); + if (solutionFileIOClass == null) { + throw new IllegalArgumentException("The solutionFileIOClass cannot be null."); } - return (SolutionFileIO) ConfigUtils.newInstance(config, "solutionFileIOClass", - config.getSolutionFileIOClass()); + return (SolutionFileIO) ConfigUtils.newInstance(config, "solutionFileIOClass", solutionFileIOClass); } - private ProblemBenchmarkResult buildProblemBenchmark( - PlannerBenchmarkResult plannerBenchmarkResult, ProblemProvider problemProvider) { + @SuppressWarnings("rawtypes") + private ProblemBenchmarkResult buildProblemBenchmark(PlannerBenchmarkResult plannerBenchmarkResult, + ProblemProvider problemProvider) { ProblemBenchmarkResult problemBenchmarkResult = new ProblemBenchmarkResult<>(plannerBenchmarkResult); problemBenchmarkResult.setName(problemProvider.getProblemName()); problemBenchmarkResult.setProblemProvider(problemProvider); - problemBenchmarkResult.setWriteOutputSolutionEnabled( - config.getWriteOutputSolutionEnabled() == null ? false : config.getWriteOutputSolutionEnabled()); - List problemStatisticList; - if (config.getProblemStatisticEnabled() != null && !config.getProblemStatisticEnabled()) { + problemBenchmarkResult + .setWriteOutputSolutionEnabled(Objects.requireNonNullElse(config.getWriteOutputSolutionEnabled(), false)); + List problemStatisticList = getProblemStatisticList(problemBenchmarkResult); + problemBenchmarkResult.setProblemStatisticList(problemStatisticList); + problemBenchmarkResult.setSingleBenchmarkResultList(new ArrayList<>()); + return problemBenchmarkResult; + } + + @SuppressWarnings("rawtypes") + private List getProblemStatisticList(ProblemBenchmarkResult problemBenchmarkResult) { + var problemStatisticEnabled = config.getProblemStatisticEnabled(); + if (problemStatisticEnabled != null && !problemStatisticEnabled) { if (!ConfigUtils.isEmptyCollection(config.getProblemStatisticTypeList())) { - throw new IllegalArgumentException("The problemStatisticEnabled (" + config.getProblemStatisticEnabled() - + ") and problemStatisticTypeList (" + config.getProblemStatisticTypeList() - + ") cannot be used together."); + throw new IllegalArgumentException( + "The problemStatisticEnabled (%b) and problemStatisticTypeList (%s) cannot be used together." + .formatted(problemStatisticEnabled, config.getProblemStatisticTypeList())); } - problemStatisticList = Collections.emptyList(); + return Collections.emptyList(); } else { - List problemStatisticTypeList_ = config.determineProblemStatisticTypeList(); - problemStatisticList = new ArrayList<>(problemStatisticTypeList_.size()); - for (ProblemStatisticType problemStatisticType : problemStatisticTypeList_) { - problemStatisticList.add(problemStatisticType.buildProblemStatistic(problemBenchmarkResult)); - } + return config.determineProblemStatisticTypeList().stream() + .map(problemStatisticType -> problemStatisticType.buildProblemStatistic(problemBenchmarkResult)) + .toList(); } - problemBenchmarkResult.setProblemStatisticList(problemStatisticList); - problemBenchmarkResult.setSingleBenchmarkResultList(new ArrayList<>()); - return problemBenchmarkResult; } + @SuppressWarnings({ "rawtypes", "unchecked" }) private void buildSingleBenchmark(SolverBenchmarkResult solverBenchmarkResult, ProblemBenchmarkResult problemBenchmarkResult) { SingleBenchmarkResult singleBenchmarkResult = new SingleBenchmarkResult(solverBenchmarkResult, problemBenchmarkResult); diff --git a/benchmark/src/main/java/ai/timefold/solver/benchmark/impl/SolverBenchmarkFactory.java b/benchmark/src/main/java/ai/timefold/solver/benchmark/impl/SolverBenchmarkFactory.java index 9e33a06a0f..18c25f043b 100644 --- a/benchmark/src/main/java/ai/timefold/solver/benchmark/impl/SolverBenchmarkFactory.java +++ b/benchmark/src/main/java/ai/timefold/solver/benchmark/impl/SolverBenchmarkFactory.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.Optional; import ai.timefold.solver.benchmark.config.ProblemBenchmarksConfig; @@ -15,7 +16,6 @@ import ai.timefold.solver.core.config.solver.monitoring.MonitoringConfig; import ai.timefold.solver.core.config.solver.monitoring.SolverMetric; import ai.timefold.solver.core.config.util.ConfigUtils; -import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; import ai.timefold.solver.core.impl.solver.DefaultSolverFactory; public class SolverBenchmarkFactory { @@ -28,59 +28,60 @@ public SolverBenchmarkFactory(SolverBenchmarkConfig config) { public void buildSolverBenchmark(ClassLoader classLoader, PlannerBenchmarkResult plannerBenchmark, Solution_[] extraProblems) { validate(); - SolverBenchmarkResult solverBenchmarkResult = new SolverBenchmarkResult(plannerBenchmark); + var solverBenchmarkResult = new SolverBenchmarkResult(plannerBenchmark); solverBenchmarkResult.setName(config.getName()); solverBenchmarkResult.setSubSingleCount(ConfigUtils.inheritOverwritableProperty(config.getSubSingleCount(), 1)); - if (config.getSolverConfig().getClassLoader() == null) { - config.getSolverConfig().setClassLoader(classLoader); + var solverConfig = Objects.requireNonNullElseGet(config.getSolverConfig(), SolverConfig::new); + if (solverConfig.getClassLoader() == null) { + solverConfig.setClassLoader(classLoader); } - if (config.getSolverConfig().getMonitoringConfig() != null && - config.getSolverConfig().getMonitoringConfig().getSolverMetricList() != null && - !config.getSolverConfig().getMonitoringConfig().getSolverMetricList().isEmpty()) { - throw new IllegalArgumentException( - "The solverBenchmarkConfig (" + config + ") has a " + SolverConfig.class.getSimpleName() + - " (" + config.getSolverConfig() + " ) with a non-empty " + MonitoringConfig.class.getSimpleName() + - " (" + config.getSolverConfig().getMonitoringConfig() + ")."); + var monitoringConfig = solverConfig.getMonitoringConfig(); + var monitoringSolverMetricList = + monitoringConfig == null ? Collections. emptyList() : monitoringConfig.getSolverMetricList(); + if (monitoringConfig != null && monitoringSolverMetricList != null && !monitoringSolverMetricList.isEmpty()) { + throw new IllegalArgumentException("The solverBenchmarkConfig (%s) has a %s (%s) with a non-empty %s (%s)." + .formatted(config, SolverConfig.class.getSimpleName(), solverConfig, MonitoringConfig.class.getSimpleName(), + monitoringConfig)); } - List solverMetricList = getSolverMetrics(config.getProblemBenchmarksConfig()); - solverBenchmarkResult.setSolverConfig(config.getSolverConfig() - .copyConfig().withMonitoringConfig( - new MonitoringConfig() - .withSolverMetricList(solverMetricList))); - DefaultSolverFactory defaultSolverFactory = new DefaultSolverFactory<>(config.getSolverConfig()); - SolutionDescriptor solutionDescriptor = defaultSolverFactory.getSolutionDescriptor(); - for (Solution_ extraProblem : extraProblems) { + var solverMetricList = getSolverMetrics(config.getProblemBenchmarksConfig()); + solverBenchmarkResult.setSolverConfig( + solverConfig.copyConfig().withMonitoringConfig(new MonitoringConfig().withSolverMetricList(solverMetricList))); + var defaultSolverFactory = new DefaultSolverFactory(solverConfig); + var solutionDescriptor = defaultSolverFactory.getSolutionDescriptor(); + for (var extraProblem : extraProblems) { if (!solutionDescriptor.getSolutionClass().isInstance(extraProblem)) { - throw new IllegalArgumentException("The solverBenchmark name (" + config.getName() - + ") for solution class (" + solutionDescriptor.getSolutionClass() - + ") cannot solve a problem (" + extraProblem - + ") of class (" + (extraProblem == null ? null : extraProblem.getClass()) + ")."); + throw new IllegalArgumentException( + "The solverBenchmark name (%s) for solution class (%s) cannot solve a problem (%s) of class (%s)." + .formatted(config.getName(), solutionDescriptor.getSolutionClass(), extraProblem, + extraProblem == null ? null : extraProblem.getClass())); } } solverBenchmarkResult.setScoreDefinition(solutionDescriptor.getScoreDefinition()); solverBenchmarkResult.setSingleBenchmarkResultList(new ArrayList<>()); - ProblemBenchmarksConfig problemBenchmarksConfig_ = - config.getProblemBenchmarksConfig() == null ? new ProblemBenchmarksConfig() - : config.getProblemBenchmarksConfig(); + var problemBenchmarksConfig = + Objects.requireNonNullElseGet(config.getProblemBenchmarksConfig(), ProblemBenchmarksConfig::new); plannerBenchmark.getSolverBenchmarkResultList().add(solverBenchmarkResult); - ProblemBenchmarksFactory problemBenchmarksFactory = new ProblemBenchmarksFactory(problemBenchmarksConfig_); + var problemBenchmarksFactory = new ProblemBenchmarksFactory(problemBenchmarksConfig); problemBenchmarksFactory.buildProblemBenchmarkList(solverBenchmarkResult, extraProblems); } protected void validate() { - if (!DefaultPlannerBenchmarkFactory.VALID_NAME_PATTERN.matcher(config.getName()).matches()) { - throw new IllegalStateException("The solverBenchmark name (" + config.getName() - + ") is invalid because it does not follow the nameRegex (" - + DefaultPlannerBenchmarkFactory.VALID_NAME_PATTERN.pattern() + ")" + - " which might cause an illegal filename."); + var configName = config.getName(); + if (configName == null || !DefaultPlannerBenchmarkFactory.VALID_NAME_PATTERN.matcher(configName).matches()) { + throw new IllegalStateException( + "The solverBenchmark name (%s) is invalid because it does not follow the nameRegex (%s) which might cause an illegal filename." + .formatted(configName, DefaultPlannerBenchmarkFactory.VALID_NAME_PATTERN.pattern())); } - if (!config.getName().trim().equals(config.getName())) { - throw new IllegalStateException("The solverBenchmark name (" + config.getName() - + ") is invalid because it starts or ends with whitespace."); + if (!configName.trim().equals(configName)) { + throw new IllegalStateException( + "The solverBenchmark name (%s) is invalid because it starts or ends with whitespace." + .formatted(configName)); } - if (config.getSubSingleCount() != null && config.getSubSingleCount() < 1) { - throw new IllegalStateException("The solverBenchmark name (" + config.getName() - + ") is invalid because the subSingleCount (" + config.getSubSingleCount() + ") must be greater than 1."); + var subSingleCount = config.getSubSingleCount(); + if (subSingleCount != null && subSingleCount < 1) { + throw new IllegalStateException( + "The solverBenchmark name (%s) is invalid because the subSingleCount (%d) must be greater than 1." + .formatted(configName, subSingleCount)); } } diff --git a/benchmark/src/main/java/ai/timefold/solver/benchmark/impl/ranking/ResilientScoreComparator.java b/benchmark/src/main/java/ai/timefold/solver/benchmark/impl/ranking/ResilientScoreComparator.java index e5ba77fd70..c77d033fe8 100644 --- a/benchmark/src/main/java/ai/timefold/solver/benchmark/impl/ranking/ResilientScoreComparator.java +++ b/benchmark/src/main/java/ai/timefold/solver/benchmark/impl/ranking/ResilientScoreComparator.java @@ -16,6 +16,7 @@ public ResilientScoreComparator(ScoreDefinition aScoreDefinition) { this.aScoreDefinition = aScoreDefinition; } + @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public int compare(Score a, Score b) { if (a == null) { @@ -25,14 +26,16 @@ public int compare(Score a, Score b) { } if (!aScoreDefinition.isCompatibleArithmeticArgument(a) || !aScoreDefinition.isCompatibleArithmeticArgument(b)) { - Number[] aNumbers = a.toLevelNumbers(); - Number[] bNumbers = b.toLevelNumbers(); - for (int i = 0; i < aNumbers.length || i < bNumbers.length; i++) { - Number aToken = i < aNumbers.length ? aNumbers[i] : 0; - Number bToken = i < bNumbers.length ? bNumbers[i] : 0; + var aNumbers = a.toLevelNumbers(); + var bNumbers = b.toLevelNumbers(); + for (var i = 0; i < aNumbers.length || i < bNumbers.length; i++) { + var aToken = i < aNumbers.length ? aNumbers[i] : 0; + var bToken = i < bNumbers.length ? bNumbers[i] : 0; int comparison; - if (aToken.getClass().equals(bToken.getClass())) { - comparison = ((Comparable) aToken).compareTo(bToken); + if (aToken.getClass().equals(bToken.getClass()) + && aToken instanceof Comparable aTokenComparable + && bToken instanceof Comparable bTokenComparable) { + comparison = aTokenComparable.compareTo(bTokenComparable); } else { comparison = Double.compare(aToken.doubleValue(), bToken.doubleValue()); } diff --git a/benchmark/src/main/java/ai/timefold/solver/benchmark/impl/report/BenchmarkReportFactory.java b/benchmark/src/main/java/ai/timefold/solver/benchmark/impl/report/BenchmarkReportFactory.java index 0bf8ace598..5303b5bf5c 100644 --- a/benchmark/src/main/java/ai/timefold/solver/benchmark/impl/report/BenchmarkReportFactory.java +++ b/benchmark/src/main/java/ai/timefold/solver/benchmark/impl/report/BenchmarkReportFactory.java @@ -29,26 +29,26 @@ public BenchmarkReport buildBenchmarkReport(PlannerBenchmarkResult plannerBenchm } protected void supplySolverRanking(BenchmarkReport benchmarkReport) { - if (config.getSolverRankingType() != null && config.getSolverRankingComparatorClass() != null) { - throw new IllegalStateException("The PlannerBenchmark cannot have" - + " a solverRankingType (" + config.getSolverRankingType() - + ") and a solverRankingComparatorClass (" + config.getSolverRankingComparatorClass().getName() - + ") at the same time."); - } else if (config.getSolverRankingType() != null && config.getSolverRankingWeightFactoryClass() != null) { - throw new IllegalStateException("The PlannerBenchmark cannot have" - + " a solverRankingType (" + config.getSolverRankingType() - + ") and a solverRankingWeightFactoryClass (" - + config.getSolverRankingWeightFactoryClass().getName() + ") at the same time."); - } else if (config.getSolverRankingComparatorClass() != null && config.getSolverRankingWeightFactoryClass() != null) { - throw new IllegalStateException("The PlannerBenchmark cannot have" - + " a solverRankingComparatorClass (" + config.getSolverRankingComparatorClass().getName() - + ") and a solverRankingWeightFactoryClass (" + config.getSolverRankingWeightFactoryClass().getName() - + ") at the same time."); + var solverRankingType = config.getSolverRankingType(); + var solverRankingComparatorClass = config.getSolverRankingComparatorClass(); + var solverRankingWeightFactoryClass = config.getSolverRankingWeightFactoryClass(); + if (solverRankingType != null && solverRankingComparatorClass != null) { + throw new IllegalStateException( + "The PlannerBenchmark cannot have a solverRankingType (%s) and a solverRankingComparatorClass (%s) at the same time." + .formatted(solverRankingType, solverRankingComparatorClass.getName())); + } else if (solverRankingType != null && solverRankingWeightFactoryClass != null) { + throw new IllegalStateException( + "The PlannerBenchmark cannot have a solverRankingType (%s) and a solverRankingWeightFactoryClass (%s) at the same time." + .formatted(solverRankingType, solverRankingWeightFactoryClass.getName())); + } else if (solverRankingComparatorClass != null && solverRankingWeightFactoryClass != null) { + throw new IllegalStateException( + "The PlannerBenchmark cannot have a solverRankingComparatorClass (%s) and a solverRankingWeightFactoryClass (%s) at the same time." + .formatted(solverRankingComparatorClass.getName(), solverRankingWeightFactoryClass.getName())); } Comparator solverRankingComparator = null; SolverRankingWeightFactory solverRankingWeightFactory = null; - if (config.getSolverRankingType() != null) { - switch (config.getSolverRankingType()) { + if (solverRankingType != null) { + switch (solverRankingType) { case TOTAL_SCORE: solverRankingComparator = new TotalScoreSolverRankingComparator(); break; @@ -59,17 +59,17 @@ protected void supplySolverRanking(BenchmarkReport benchmarkReport) { solverRankingWeightFactory = new TotalRankSolverRankingWeightFactory(); break; default: - throw new IllegalStateException("The solverRankingType (" - + config.getSolverRankingType() + ") is not implemented."); + throw new IllegalStateException("The solverRankingType (%s) is not implemented." + .formatted(solverRankingType)); } } - if (config.getSolverRankingComparatorClass() != null) { - solverRankingComparator = ConfigUtils.newInstance(config, - "solverRankingComparatorClass", config.getSolverRankingComparatorClass()); + if (solverRankingComparatorClass != null) { + solverRankingComparator = + ConfigUtils.newInstance(config, "solverRankingComparatorClass", solverRankingComparatorClass); } - if (config.getSolverRankingWeightFactoryClass() != null) { - solverRankingWeightFactory = ConfigUtils.newInstance(config, - "solverRankingWeightFactoryClass", config.getSolverRankingWeightFactoryClass()); + if (solverRankingWeightFactoryClass != null) { + solverRankingWeightFactory = + ConfigUtils.newInstance(config, "solverRankingWeightFactoryClass", solverRankingWeightFactoryClass); } if (solverRankingComparator != null) { benchmarkReport.setSolverRankingComparator(solverRankingComparator); diff --git a/core/src/main/java/ai/timefold/solver/core/api/score/stream/Joiners.java b/core/src/main/java/ai/timefold/solver/core/api/score/stream/Joiners.java index a2a2f11d86..72b737efbf 100644 --- a/core/src/main/java/ai/timefold/solver/core/api/score/stream/Joiners.java +++ b/core/src/main/java/ai/timefold/solver/core/api/score/stream/Joiners.java @@ -64,7 +64,6 @@ public final class Joiners { /** * Joins every A and B that share a property. * These are exactly the pairs where {@code leftMapping.apply(A).equals(rightMapping.apply(B))}. - * * For example, on a cartesian product of list {@code [Ann(age = 20), Beth(age = 25), Eric(age = 20)]} * with both leftMapping and rightMapping being {@code Person::getAge}, * this joiner will produce pairs {@code (Ann, Ann), (Ann, Eric), (Beth, Beth), (Eric, Ann), (Eric, Eric)}. diff --git a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhaseFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhaseFactory.java index 0ddd53a95f..4bb4c36b75 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhaseFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhaseFactory.java @@ -93,20 +93,20 @@ public ConstructionHeuristicPhase buildPhase(int phaseIndex, boolean } private Optional> getValidEntityPlacerConfig() { - EntityPlacerConfig entityPlacerConfig = phaseConfig.getEntityPlacerConfig(); + var entityPlacerConfig = phaseConfig.getEntityPlacerConfig(); if (entityPlacerConfig == null) { return Optional.empty(); } if (phaseConfig.getConstructionHeuristicType() != null) { throw new IllegalArgumentException( - "The constructionHeuristicType (" + phaseConfig.getConstructionHeuristicType() - + ") must not be configured if the entityPlacerConfig (" + entityPlacerConfig - + ") is explicitly configured."); + "The constructionHeuristicType (%s) must not be configured if the entityPlacerConfig (%s) is explicitly configured." + .formatted(phaseConfig.getConstructionHeuristicType(), entityPlacerConfig)); } - if (phaseConfig.getMoveSelectorConfigList() != null) { - throw new IllegalArgumentException("The moveSelectorConfigList (" + phaseConfig.getMoveSelectorConfigList() - + ") cannot be configured if the entityPlacerConfig (" + entityPlacerConfig - + ") is explicitly configured."); + var moveSelectorConfigList = phaseConfig.getMoveSelectorConfigList(); + if (moveSelectorConfigList != null) { + throw new IllegalArgumentException( + "The moveSelectorConfigList (%s) cannot be configured if the entityPlacerConfig (%s) is explicitly configured." + .formatted(moveSelectorConfigList, entityPlacerConfig)); } return Optional.of(entityPlacerConfig); } @@ -208,16 +208,17 @@ private EntityPlacerConfig buildUnfoldedEntityPlacerConfig(HeuristicConfigPol }; } - private MoveSelectorConfig checkSingleMoveSelectorConfig() { - if (phaseConfig.getMoveSelectorConfigList().size() != 1) { - throw new IllegalArgumentException("For the constructionHeuristicType (" - + phaseConfig.getConstructionHeuristicType() + "), the moveSelectorConfigList (" - + phaseConfig.getMoveSelectorConfigList() - + ") must be a singleton. Use a single " + UnionMoveSelectorConfig.class.getSimpleName() - + " or " + CartesianProductMoveSelectorConfig.class.getSimpleName() - + " element to nest multiple MoveSelectors."); + private MoveSelectorConfig checkSingleMoveSelectorConfig() { // Non-null guaranteed by the caller. + var moveSelectorConfigList = Objects.requireNonNull(phaseConfig.getMoveSelectorConfigList()); + if (moveSelectorConfigList.size() != 1) { + throw new IllegalArgumentException(""" + For the constructionHeuristicType (%s), the moveSelectorConfigList (%s) must be a singleton. + Use a single %s or %s element to nest multiple MoveSelectors.""" + .formatted(phaseConfig.getConstructionHeuristicType(), phaseConfig.getMoveSelectorConfigList(), + UnionMoveSelectorConfig.class.getSimpleName(), + CartesianProductMoveSelectorConfig.class.getSimpleName())); } - return phaseConfig.getMoveSelectorConfigList().get(0); + return moveSelectorConfigList.get(0); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/QueuedEntityPlacerFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/QueuedEntityPlacerFactory.java index 0bc5f7300e..e8cec325dc 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/QueuedEntityPlacerFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/QueuedEntityPlacerFactory.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import ai.timefold.solver.core.config.constructionheuristic.placer.QueuedEntityPlacerConfig; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; @@ -12,46 +13,49 @@ import ai.timefold.solver.core.config.heuristic.selector.move.composite.CartesianProductMoveSelectorConfig; import ai.timefold.solver.core.config.heuristic.selector.move.generic.ChangeMoveSelectorConfig; import ai.timefold.solver.core.config.util.ConfigUtils; -import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor; -import ai.timefold.solver.core.impl.domain.variable.descriptor.GenuineVariableDescriptor; import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy; import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelectorFactory; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelectorFactory; +import org.jspecify.annotations.NonNull; + public class QueuedEntityPlacerFactory extends AbstractEntityPlacerFactory { + @SuppressWarnings({ "rawtypes", "unchecked" }) public static QueuedEntityPlacerConfig unfoldNew(HeuristicConfigPolicy configPolicy, List templateMoveSelectorConfigList) { - QueuedEntityPlacerConfig config = new QueuedEntityPlacerConfig(); - config.setEntitySelectorConfig(new QueuedEntityPlacerFactory(config) - .buildEntitySelectorConfig(configPolicy)); - config.setMoveSelectorConfigList(new ArrayList<>(templateMoveSelectorConfigList.size())); - List leafMoveSelectorConfigList = new ArrayList<>(templateMoveSelectorConfigList.size()); - for (MoveSelectorConfig templateMoveSelectorConfig : templateMoveSelectorConfigList) { - MoveSelectorConfig moveSelectorConfig = (MoveSelectorConfig) templateMoveSelectorConfig.copyConfig(); + var config = new QueuedEntityPlacerConfig(); + var entitySelectorConfig = new QueuedEntityPlacerFactory(config) + .buildEntitySelectorConfig(configPolicy); + config.setEntitySelectorConfig(entitySelectorConfig); + var moveSelectorConfigList = new ArrayList(templateMoveSelectorConfigList.size()); + config.setMoveSelectorConfigList(moveSelectorConfigList); + var leafMoveSelectorConfigList = new ArrayList(templateMoveSelectorConfigList.size()); + for (var templateMoveSelectorConfig : templateMoveSelectorConfigList) { + var moveSelectorConfig = (MoveSelectorConfig) templateMoveSelectorConfig.copyConfig(); moveSelectorConfig.extractLeafMoveSelectorConfigsIntoList(leafMoveSelectorConfigList); - config.getMoveSelectorConfigList().add(moveSelectorConfig); + moveSelectorConfigList.add(moveSelectorConfig); } - for (MoveSelectorConfig leafMoveSelectorConfig : leafMoveSelectorConfigList) { - if (!(leafMoveSelectorConfig instanceof ChangeMoveSelectorConfig)) { - throw new IllegalStateException("The contains a moveSelector (" - + leafMoveSelectorConfig + ") that isn't a , a " - + " or a .\n" - + "Maybe you're using a moveSelector in " - + " that's only supported for ."); + for (var leafMoveSelectorConfig : leafMoveSelectorConfigList) { + if (!(leafMoveSelectorConfig instanceof ChangeMoveSelectorConfig changeMoveSelectorConfig)) { + throw new IllegalStateException( + """ + The contains a moveSelector (%s) that isn't a , \ + a or a . + Maybe you're using a moveSelector in that's only supported for .""" + .formatted(leafMoveSelectorConfig)); } - ChangeMoveSelectorConfig changeMoveSelectorConfig = (ChangeMoveSelectorConfig) leafMoveSelectorConfig; - if (changeMoveSelectorConfig.getEntitySelectorConfig() != null) { - throw new IllegalStateException("The contains a changeMoveSelector (" - + changeMoveSelectorConfig + ") that contains an entitySelector (" - + changeMoveSelectorConfig.getEntitySelectorConfig() - + ") without explicitly configuring the ."); + var changeMoveEntitySelectorConfig = changeMoveSelectorConfig.getEntitySelectorConfig(); + if (changeMoveEntitySelectorConfig != null) { + throw new IllegalStateException( + "The contains a changeMoveSelector (%s) that contains an entitySelector (%s) without explicitly configuring the ." + .formatted(changeMoveSelectorConfig, changeMoveEntitySelectorConfig)); } changeMoveSelectorConfig.setEntitySelectorConfig( - EntitySelectorConfig.newMimicSelectorConfig(config.getEntitySelectorConfig().getId())); + EntitySelectorConfig.newMimicSelectorConfig(entitySelectorConfig.getId())); } return config; } @@ -62,56 +66,54 @@ public QueuedEntityPlacerFactory(QueuedEntityPlacerConfig placerConfig) { @Override public QueuedEntityPlacer buildEntityPlacer(HeuristicConfigPolicy configPolicy) { - EntitySelectorConfig entitySelectorConfig_ = buildEntitySelectorConfig(configPolicy); - EntitySelector entitySelector = - EntitySelectorFactory. create(entitySelectorConfig_) - .buildEntitySelector(configPolicy, SelectionCacheType.PHASE, SelectionOrder.ORIGINAL); + var entitySelectorConfig_ = buildEntitySelectorConfig(configPolicy); + var entitySelector = EntitySelectorFactory. create(entitySelectorConfig_).buildEntitySelector(configPolicy, + SelectionCacheType.PHASE, SelectionOrder.ORIGINAL); - List moveSelectorConfigList_; - if (ConfigUtils.isEmptyCollection(config.getMoveSelectorConfigList())) { - EntityDescriptor entityDescriptor = entitySelector.getEntityDescriptor(); - List> variableDescriptorList = - entityDescriptor.getGenuineVariableDescriptorList(); - List subMoveSelectorConfigList = new ArrayList<>(variableDescriptorList.size()); - for (GenuineVariableDescriptor variableDescriptor : variableDescriptorList) { - subMoveSelectorConfigList - .add(buildChangeMoveSelectorConfig(configPolicy, entitySelectorConfig_.getId(), variableDescriptor)); - } - MoveSelectorConfig subMoveSelectorConfig; - if (subMoveSelectorConfigList.size() > 1) { - // Default to cartesian product (not a queue) of planning variables. - subMoveSelectorConfig = new CartesianProductMoveSelectorConfig(subMoveSelectorConfigList); - } else { - subMoveSelectorConfig = subMoveSelectorConfigList.get(0); - } - moveSelectorConfigList_ = Collections.singletonList(subMoveSelectorConfig); - } else { - moveSelectorConfigList_ = config.getMoveSelectorConfigList(); - } - List> moveSelectorList = new ArrayList<>(moveSelectorConfigList_.size()); - for (MoveSelectorConfig moveSelectorConfig : moveSelectorConfigList_) { - MoveSelector moveSelector = MoveSelectorFactory. create(moveSelectorConfig) + var moveSelectorConfigList_ = getMoveSelectorConfigs(configPolicy, entitySelector, entitySelectorConfig_); + var moveSelectorList = new ArrayList>(moveSelectorConfigList_.size()); + for (var moveSelectorConfig : moveSelectorConfigList_) { + var moveSelector = MoveSelectorFactory. create(moveSelectorConfig) .buildMoveSelector(configPolicy, SelectionCacheType.JUST_IN_TIME, SelectionOrder.ORIGINAL, false); moveSelectorList.add(moveSelector); } return new QueuedEntityPlacer<>(entitySelector, moveSelectorList); } - public EntitySelectorConfig buildEntitySelectorConfig(HeuristicConfigPolicy configPolicy) { - EntitySelectorConfig entitySelectorConfig_; - if (config.getEntitySelectorConfig() == null) { - EntityDescriptor entityDescriptor = getTheOnlyEntityDescriptor(configPolicy.getSolutionDescriptor()); - entitySelectorConfig_ = getDefaultEntitySelectorConfigForEntity(configPolicy, entityDescriptor); + @SuppressWarnings("rawtypes") + private @NonNull List getMoveSelectorConfigs(HeuristicConfigPolicy configPolicy, + EntitySelector entitySelector, EntitySelectorConfig entitySelectorConfig_) { + var moveSelectorConfigList = config.getMoveSelectorConfigList(); + if (!ConfigUtils.isEmptyCollection(moveSelectorConfigList)) { + return moveSelectorConfigList; + } + var entityDescriptor = entitySelector.getEntityDescriptor(); + var variableDescriptorList = entityDescriptor.getGenuineVariableDescriptorList(); + var subMoveSelectorConfigList = new ArrayList(variableDescriptorList.size()); + for (var variableDescriptor : variableDescriptorList) { + subMoveSelectorConfigList + .add(buildChangeMoveSelectorConfig(configPolicy, entitySelectorConfig_.getId(), variableDescriptor)); + } + if (subMoveSelectorConfigList.size() > 1) { + // Default to cartesian product (not a queue) of planning variables. + return Collections.singletonList(new CartesianProductMoveSelectorConfig(subMoveSelectorConfigList)); } else { - entitySelectorConfig_ = config.getEntitySelectorConfig(); + return Collections.singletonList(subMoveSelectorConfigList.get(0)); } - if (entitySelectorConfig_.getCacheType() != null - && entitySelectorConfig_.getCacheType().compareTo(SelectionCacheType.PHASE) < 0) { - throw new IllegalArgumentException("The queuedEntityPlacer (" + config - + ") cannot have an entitySelectorConfig (" + entitySelectorConfig_ - + ") with a cacheType (" + entitySelectorConfig_.getCacheType() - + ") lower than " + SelectionCacheType.PHASE + "."); + } + + public EntitySelectorConfig buildEntitySelectorConfig(HeuristicConfigPolicy configPolicy) { + var entitySelectorConfig = + Objects.requireNonNullElseGet(config.getEntitySelectorConfig(), () -> { + var entityDescriptor = getTheOnlyEntityDescriptor(configPolicy.getSolutionDescriptor()); + return getDefaultEntitySelectorConfigForEntity(configPolicy, entityDescriptor); + }); + var cacheType = entitySelectorConfig.getCacheType(); + if (cacheType != null && cacheType.compareTo(SelectionCacheType.PHASE) < 0) { + throw new IllegalArgumentException( + "The queuedEntityPlacer (%s) cannot have an entitySelectorConfig (%s) with a cacheType (%s) lower than %s." + .formatted(config, entitySelectorConfig, cacheType, SelectionCacheType.PHASE)); } - return entitySelectorConfig_; + return entitySelectorConfig; } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/QueuedValuePlacerFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/QueuedValuePlacerFactory.java index e798ad47d9..be3ddcc051 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/QueuedValuePlacerFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/constructionheuristic/placer/QueuedValuePlacerFactory.java @@ -1,5 +1,7 @@ package ai.timefold.solver.core.impl.constructionheuristic.placer; +import java.util.Objects; + import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; import ai.timefold.solver.core.config.constructionheuristic.placer.QueuedValuePlacerConfig; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; @@ -58,29 +60,27 @@ public QueuedValuePlacer buildEntityPlacer(HeuristicConfigPolicy configPolicy, EntityDescriptor entityDescriptor) { - ValueSelectorConfig valueSelectorConfig_; - if (config.getValueSelectorConfig() == null) { - Class entityClass = entityDescriptor.getEntityClass(); - GenuineVariableDescriptor variableDescriptor = getTheOnlyVariableDescriptor(entityDescriptor); - valueSelectorConfig_ = new ValueSelectorConfig() - .withId(entityClass.getName() + "." + variableDescriptor.getVariableName()) - .withVariableName(variableDescriptor.getVariableName()); - if (ValueSelectorConfig.hasSorter(configPolicy.getValueSorterManner(), variableDescriptor)) { - valueSelectorConfig_ = valueSelectorConfig_.withCacheType(SelectionCacheType.PHASE) - .withSelectionOrder(SelectionOrder.SORTED) - .withSorterManner(configPolicy.getValueSorterManner()); - } - } else { - valueSelectorConfig_ = config.getValueSelectorConfig(); - } - if (valueSelectorConfig_.getCacheType() != null - && valueSelectorConfig_.getCacheType().compareTo(SelectionCacheType.PHASE) < 0) { - throw new IllegalArgumentException("The queuedValuePlacer (" + this - + ") cannot have a valueSelectorConfig (" + valueSelectorConfig_ - + ") with a cacheType (" + valueSelectorConfig_.getCacheType() - + ") lower than " + SelectionCacheType.PHASE + "."); + var result = Objects.requireNonNullElseGet(config.getValueSelectorConfig(), + () -> { + var entityClass = entityDescriptor.getEntityClass(); + var variableDescriptor = getTheOnlyVariableDescriptor(entityDescriptor); + var valueSelectorConfig = new ValueSelectorConfig() + .withId(entityClass.getName() + "." + variableDescriptor.getVariableName()) + .withVariableName(variableDescriptor.getVariableName()); + if (ValueSelectorConfig.hasSorter(configPolicy.getValueSorterManner(), variableDescriptor)) { + valueSelectorConfig = valueSelectorConfig.withCacheType(SelectionCacheType.PHASE) + .withSelectionOrder(SelectionOrder.SORTED) + .withSorterManner(configPolicy.getValueSorterManner()); + } + return valueSelectorConfig; + }); + var cacheType = result.getCacheType(); + if (cacheType != null && cacheType.compareTo(SelectionCacheType.PHASE) < 0) { + throw new IllegalArgumentException( + "The queuedValuePlacer (%s) cannot have a valueSelectorConfig (%s) with a cacheType (%s) lower than %s." + .formatted(this, result, cacheType, SelectionCacheType.PHASE)); } - return valueSelectorConfig_; + return result; } @Override diff --git a/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/DefaultExhaustiveSearchPhaseFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/DefaultExhaustiveSearchPhaseFactory.java index 2db2995072..8b7f8b6da1 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/DefaultExhaustiveSearchPhaseFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/exhaustivesearch/DefaultExhaustiveSearchPhaseFactory.java @@ -102,27 +102,26 @@ EntitySelectorFactory. create(entitySelectorConfig_) } private EntitySelectorConfig buildEntitySelectorConfig(HeuristicConfigPolicy configPolicy) { - EntitySelectorConfig entitySelectorConfig_; - if (phaseConfig.getEntitySelectorConfig() == null) { - EntityDescriptor entityDescriptor = deduceEntityDescriptor(configPolicy.getSolutionDescriptor()); - entitySelectorConfig_ = new EntitySelectorConfig() - .withEntityClass(entityDescriptor.getEntityClass()); - if (EntitySelectorConfig.hasSorter(configPolicy.getEntitySorterManner(), entityDescriptor)) { - entitySelectorConfig_ = entitySelectorConfig_.withCacheType(SelectionCacheType.PHASE) - .withSelectionOrder(SelectionOrder.SORTED) - .withSorterManner(configPolicy.getEntitySorterManner()); - } - } else { - entitySelectorConfig_ = phaseConfig.getEntitySelectorConfig(); - } - if (entitySelectorConfig_.getCacheType() != null - && entitySelectorConfig_.getCacheType().compareTo(SelectionCacheType.PHASE) < 0) { - throw new IllegalArgumentException("The phaseConfig (" + phaseConfig - + ") cannot have an entitySelectorConfig (" + entitySelectorConfig_ - + ") with a cacheType (" + entitySelectorConfig_.getCacheType() - + ") lower than " + SelectionCacheType.PHASE + "."); + var result = Objects.requireNonNullElseGet( + phaseConfig.getEntitySelectorConfig(), + () -> { + var entityDescriptor = deduceEntityDescriptor(configPolicy.getSolutionDescriptor()); + var entitySelectorConfig = new EntitySelectorConfig() + .withEntityClass(entityDescriptor.getEntityClass()); + if (EntitySelectorConfig.hasSorter(configPolicy.getEntitySorterManner(), entityDescriptor)) { + entitySelectorConfig = entitySelectorConfig.withCacheType(SelectionCacheType.PHASE) + .withSelectionOrder(SelectionOrder.SORTED) + .withSorterManner(configPolicy.getEntitySorterManner()); + } + return entitySelectorConfig; + }); + var cacheType = result.getCacheType(); + if (cacheType != null && cacheType.compareTo(SelectionCacheType.PHASE) < 0) { + throw new IllegalArgumentException( + "The phaseConfig (%s) cannot have an entitySelectorConfig (%s) with a cacheType (%s) lower than %s." + .formatted(phaseConfig, result, cacheType, SelectionCacheType.PHASE)); } - return entitySelectorConfig_; + return result; } protected EntityDescriptor deduceEntityDescriptor(SolutionDescriptor solutionDescriptor) { diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java index 841cc1d440..bf2ff72a45 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/entity/EntitySelectorFactory.java @@ -2,8 +2,8 @@ import java.util.ArrayList; import java.util.Comparator; -import java.util.List; import java.util.Objects; +import java.util.function.Function; import java.util.stream.Stream; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; @@ -14,7 +14,6 @@ import ai.timefold.solver.core.config.heuristic.selector.entity.EntitySelectorConfig; import ai.timefold.solver.core.enterprise.TimefoldSolverEnterpriseService; import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor; -import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy; import ai.timefold.solver.core.impl.heuristic.selector.AbstractSelectorFactory; import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorSelectionSorter; @@ -29,7 +28,6 @@ import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.SelectedCountLimitEntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.ShufflingEntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.entity.decorator.SortingEntitySelector; -import ai.timefold.solver.core.impl.heuristic.selector.entity.mimic.EntityMimicRecorder; import ai.timefold.solver.core.impl.heuristic.selector.entity.mimic.MimicRecordingEntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.entity.mimic.MimicReplayingEntitySelector; import ai.timefold.solver.core.impl.solver.ClassInstanceCache; @@ -45,21 +43,22 @@ public EntitySelectorFactory(EntitySelectorConfig entitySelectorConfig) { } public EntityDescriptor extractEntityDescriptor(HeuristicConfigPolicy configPolicy) { - if (config.getEntityClass() != null) { - SolutionDescriptor solutionDescriptor = configPolicy.getSolutionDescriptor(); - EntityDescriptor entityDescriptor = - solutionDescriptor.getEntityDescriptorStrict(config.getEntityClass()); + var entityClass = config.getEntityClass(); + var mimicSelectorRef = config.getMimicSelectorRef(); + if (entityClass != null) { + var solutionDescriptor = configPolicy.getSolutionDescriptor(); + var entityDescriptor = solutionDescriptor.getEntityDescriptorStrict(entityClass); if (entityDescriptor == null) { - throw new IllegalArgumentException("The selectorConfig (" + config - + ") has an entityClass (" + config.getEntityClass() + ") that is not a known planning entity.\n" - + "Check your solver configuration. If that class (" + config.getEntityClass().getSimpleName() - + ") is not in the entityClassSet (" + solutionDescriptor.getEntityClassSet() - + "), check your @" + PlanningSolution.class.getSimpleName() - + " implementation's annotated methods too."); + throw new IllegalArgumentException(""" + The selectorConfig (%s) has an entityClass (%s) that is not a known planning entity. + Check your solver configuration. If that class (%s) is not in the entityClassSet (%s), \ + check your @%s implementation's annotated methods too.""" + .formatted(config, entityClass, entityClass.getSimpleName(), + solutionDescriptor.getEntityClassSet(), PlanningSolution.class.getSimpleName())); } return entityDescriptor; - } else if (config.getMimicSelectorRef() != null) { - return configPolicy.getEntityMimicRecorder(config.getMimicSelectorRef()).getEntityDescriptor(); + } else if (mimicSelectorRef != null) { + return configPolicy.getEntityMimicRecorder(mimicSelectorRef).getEntityDescriptor(); } else { return null; } @@ -78,12 +77,13 @@ public EntitySelector buildEntitySelector(HeuristicConfigPolicy entityDescriptor = deduceEntityDescriptor(configPolicy, config.getEntityClass()); - SelectionCacheType resolvedCacheType = SelectionCacheType.resolve(config.getCacheType(), minimumCacheType); - SelectionOrder resolvedSelectionOrder = SelectionOrder.resolve(config.getSelectionOrder(), inheritedSelectionOrder); + var entityDescriptor = deduceEntityDescriptor(configPolicy, config.getEntityClass()); + var resolvedCacheType = SelectionCacheType.resolve(config.getCacheType(), minimumCacheType); + var resolvedSelectionOrder = SelectionOrder.resolve(config.getSelectionOrder(), inheritedSelectionOrder); - if (config.getNearbySelectionConfig() != null) { - config.getNearbySelectionConfig().validateNearby(resolvedCacheType, resolvedSelectionOrder); + var nearbySelectionConfig = config.getNearbySelectionConfig(); + if (nearbySelectionConfig != null) { + nearbySelectionConfig.validateNearby(resolvedCacheType, resolvedSelectionOrder); } validateCacheTypeVersusSelectionOrder(resolvedCacheType, resolvedSelectionOrder); validateSorting(resolvedSelectionOrder); @@ -91,16 +91,16 @@ public EntitySelector buildEntitySelector(HeuristicConfigPolicy entitySelector = buildBaseEntitySelector(entityDescriptor, baseSelectionCacheType, + var baseRandomSelection = determineBaseRandomSelection(entityDescriptor, resolvedCacheType, resolvedSelectionOrder); + var baseSelectionCacheType = SelectionCacheType.max(minimumCacheType, resolvedCacheType); + var entitySelector = buildBaseEntitySelector(entityDescriptor, baseSelectionCacheType, baseRandomSelection); - if (config.getNearbySelectionConfig() != null) { + if (nearbySelectionConfig != null) { // TODO Static filtering (such as movableEntitySelectionFilter) should affect nearbySelection - entitySelector = applyNearbySelection(configPolicy, config.getNearbySelectionConfig(), minimumCacheType, + entitySelector = applyNearbySelection(configPolicy, nearbySelectionConfig, minimumCacheType, resolvedSelectionOrder, entitySelector); } - ClassInstanceCache instanceCache = configPolicy.getClassInstanceCache(); + var instanceCache = configPolicy.getClassInstanceCache(); entitySelector = applyFiltering(entitySelector, instanceCache); entitySelector = applySorting(resolvedCacheType, resolvedSelectionOrder, entitySelector, instanceCache); entitySelector = applyProbability(resolvedCacheType, resolvedSelectionOrder, entitySelector, instanceCache); @@ -112,44 +112,39 @@ public EntitySelector buildEntitySelector(HeuristicConfigPolicy buildMimicReplaying(HeuristicConfigPolicy configPolicy) { - final boolean anyConfigurationParameterDefined = Stream + final var anyConfigurationParameterDefined = Stream .of(config.getId(), config.getEntityClass(), config.getCacheType(), config.getSelectionOrder(), config.getNearbySelectionConfig(), config.getFilterClass(), config.getSorterManner(), config.getSorterComparatorClass(), config.getSorterWeightFactoryClass(), config.getSorterOrder(), config.getSorterClass(), config.getProbabilityWeightFactoryClass(), config.getSelectedCountLimit()) .anyMatch(Objects::nonNull); if (anyConfigurationParameterDefined) { - throw new IllegalArgumentException("The entitySelectorConfig (" + config - + ") with mimicSelectorRef (" + config.getMimicSelectorRef() - + ") has another property that is not null."); + throw new IllegalArgumentException( + "The entitySelectorConfig (%s) with mimicSelectorRef (%s) has another property that is not null." + .formatted(config, config.getMimicSelectorRef())); } - EntityMimicRecorder entityMimicRecorder = configPolicy.getEntityMimicRecorder(config.getMimicSelectorRef()); + var entityMimicRecorder = configPolicy.getEntityMimicRecorder(config.getMimicSelectorRef()); if (entityMimicRecorder == null) { - throw new IllegalArgumentException("The entitySelectorConfig (" + config - + ") has a mimicSelectorRef (" + config.getMimicSelectorRef() - + ") for which no entitySelector with that id exists (in its solver phase)."); + throw new IllegalArgumentException( + "The entitySelectorConfig (%s) has a mimicSelectorRef (%s) for which no entitySelector with that id exists (in its solver phase)." + .formatted(config, config.getMimicSelectorRef())); } return new MimicReplayingEntitySelector<>(entityMimicRecorder); } protected boolean determineBaseRandomSelection(EntityDescriptor entityDescriptor, SelectionCacheType resolvedCacheType, SelectionOrder resolvedSelectionOrder) { - switch (resolvedSelectionOrder) { - case ORIGINAL: - return false; - case SORTED: - case SHUFFLED: - case PROBABILISTIC: + return switch (resolvedSelectionOrder) { + case ORIGINAL, SORTED, SHUFFLED, PROBABILISTIC -> // baseValueSelector and lower should be ORIGINAL if they are going to get cached completely - return false; - case RANDOM: + false; + case RANDOM -> // Predict if caching will occur - return resolvedCacheType.isNotCached() + resolvedCacheType.isNotCached() || (isBaseInherentlyCached() && !hasFiltering(entityDescriptor)); - default: - throw new IllegalStateException("The selectionOrder (" + resolvedSelectionOrder - + ") is not implemented."); - } + default -> throw new IllegalStateException("The selectionOrder (%s) is not implemented." + .formatted(resolvedSelectionOrder)); + }; } protected boolean isBaseInherentlyCached() { @@ -161,8 +156,8 @@ private EntitySelector buildBaseEntitySelector(EntityDescriptor(entityDescriptor, minimumCacheType, randomSelection); @@ -182,12 +177,13 @@ private EntitySelector applyNearbySelection(HeuristicConfigPolicy applyFiltering(EntitySelector entitySelector, ClassInstanceCache instanceCache) { - EntityDescriptor entityDescriptor = entitySelector.getEntityDescriptor(); + var entityDescriptor = entitySelector.getEntityDescriptor(); if (hasFiltering(entityDescriptor)) { - List> filterList = new ArrayList<>(config.getFilterClass() == null ? 1 : 2); - if (config.getFilterClass() != null) { + var filterClass = config.getFilterClass(); + var filterList = new ArrayList>(filterClass == null ? 1 : 2); + if (filterClass != null) { SelectionFilter selectionFilter = - instanceCache.newInstance(config, "filterClass", config.getFilterClass()); + instanceCache.newInstance(config, "filterClass", filterClass); filterList.add(selectionFilter); } // Filter out pinned entities @@ -203,58 +199,51 @@ private EntitySelector applyFiltering(EntitySelector entit } protected void validateSorting(SelectionOrder resolvedSelectionOrder) { - if ((config.getSorterManner() != null || config.getSorterComparatorClass() != null + var sorterManner = config.getSorterManner(); + if ((sorterManner != null || config.getSorterComparatorClass() != null || config.getSorterWeightFactoryClass() != null || config.getSorterOrder() != null || config.getSorterClass() != null) && resolvedSelectionOrder != SelectionOrder.SORTED) { - throw new IllegalArgumentException("The entitySelectorConfig (" + config - + ") with sorterManner (" + config.getSorterManner() - + ") and sorterComparatorClass (" + config.getSorterComparatorClass() - + ") and sorterWeightFactoryClass (" + config.getSorterWeightFactoryClass() - + ") and sorterOrder (" + config.getSorterOrder() - + ") and sorterClass (" + config.getSorterClass() - + ") has a resolvedSelectionOrder (" + resolvedSelectionOrder - + ") that is not " + SelectionOrder.SORTED + "."); - } - if (config.getSorterManner() != null && config.getSorterComparatorClass() != null) { - throw new IllegalArgumentException("The entitySelectorConfig (" + config - + ") has both a sorterManner (" + config.getSorterManner() - + ") and a sorterComparatorClass (" + config.getSorterComparatorClass() + ")."); - } - if (config.getSorterManner() != null && config.getSorterWeightFactoryClass() != null) { - throw new IllegalArgumentException("The entitySelectorConfig (" + config - + ") has both a sorterManner (" + config.getSorterManner() - + ") and a sorterWeightFactoryClass (" + config.getSorterWeightFactoryClass() + ")."); - } - if (config.getSorterManner() != null && config.getSorterClass() != null) { - throw new IllegalArgumentException("The entitySelectorConfig (" + config - + ") has both a sorterManner (" + config.getSorterManner() - + ") and a sorterClass (" + config.getSorterClass() + ")."); - } - if (config.getSorterManner() != null && config.getSorterOrder() != null) { - throw new IllegalArgumentException("The entitySelectorConfig (" + config - + ") with sorterManner (" + config.getSorterManner() - + ") has a non-null sorterOrder (" + config.getSorterOrder() + ")."); + throw new IllegalArgumentException(""" + The entitySelectorConfig (%s) with sorterManner (%s) \ + and sorterComparatorClass (%s) and sorterWeightFactoryClass (%s) and sorterOrder (%s) and sorterClass (%s) \ + has a resolvedSelectionOrder (%s) that is not %s.""" + .formatted(config, sorterManner, config.getSorterComparatorClass(), + config.getSorterWeightFactoryClass(), config.getSorterOrder(), config.getSorterClass(), + resolvedSelectionOrder, SelectionOrder.SORTED)); } + assertNotSorterMannerAnd(config, "sorterComparatorClass", EntitySelectorConfig::getSorterComparatorClass); + assertNotSorterMannerAnd(config, "sorterWeightFactoryClass", EntitySelectorConfig::getSorterWeightFactoryClass); + assertNotSorterMannerAnd(config, "sorterClass", EntitySelectorConfig::getSorterClass); + assertNotSorterMannerAnd(config, "sorterOrder", EntitySelectorConfig::getSorterOrder); + assertNotSorterClassAnd(config, "sorterComparatorClass", EntitySelectorConfig::getSorterComparatorClass); + assertNotSorterClassAnd(config, "sorterWeightFactoryClass", EntitySelectorConfig::getSorterWeightFactoryClass); + assertNotSorterClassAnd(config, "sorterOrder", EntitySelectorConfig::getSorterOrder); if (config.getSorterComparatorClass() != null && config.getSorterWeightFactoryClass() != null) { - throw new IllegalArgumentException("The entitySelectorConfig (" + config - + ") has both a sorterComparatorClass (" + config.getSorterComparatorClass() - + ") and a sorterWeightFactoryClass (" + config.getSorterWeightFactoryClass() + ")."); - } - if (config.getSorterComparatorClass() != null && config.getSorterClass() != null) { - throw new IllegalArgumentException("The entitySelectorConfig (" + config - + ") has both a sorterComparatorClass (" + config.getSorterComparatorClass() - + ") and a sorterClass (" + config.getSorterClass() + ")."); + throw new IllegalArgumentException( + "The entitySelectorConfig (%s) has both a sorterComparatorClass (%s) and a sorterWeightFactoryClass (%s)." + .formatted(config, config.getSorterComparatorClass(), config.getSorterWeightFactoryClass())); } - if (config.getSorterWeightFactoryClass() != null && config.getSorterClass() != null) { - throw new IllegalArgumentException("The entitySelectorConfig (" + config - + ") has both a sorterWeightFactoryClass (" + config.getSorterWeightFactoryClass() - + ") and a sorterClass (" + config.getSorterClass() + ")."); + } + + private void assertNotSorterMannerAnd(EntitySelectorConfig config, String propertyName, + Function propertyAccessor) { + var sorterManner = config.getSorterManner(); + var property = propertyAccessor.apply(config); + if (sorterManner != null && property != null) { + throw new IllegalArgumentException("The entitySelectorConfig (%s) has both a sorterManner (%s) and a %s (%s)." + .formatted(config, sorterManner, propertyName, property)); } - if (config.getSorterClass() != null && config.getSorterOrder() != null) { - throw new IllegalArgumentException("The entitySelectorConfig (" + config - + ") with sorterClass (" + config.getSorterClass() - + ") has a non-null sorterOrder (" + config.getSorterOrder() + ")."); + } + + private void assertNotSorterClassAnd(EntitySelectorConfig config, String propertyName, + Function propertyAccessor) { + var sorterClass = config.getSorterClass(); + var property = propertyAccessor.apply(config); + if (sorterClass != null && property != null) { + throw new IllegalArgumentException( + "The entitySelectorConfig (%s) with sorterClass (%s) has a non-null %s (%s)." + .formatted(config, sorterClass, propertyName, property)); } } @@ -262,12 +251,13 @@ protected EntitySelector applySorting(SelectionCacheType resolvedCach SelectionOrder resolvedSelectionOrder, EntitySelector entitySelector, ClassInstanceCache instanceCache) { if (resolvedSelectionOrder == SelectionOrder.SORTED) { SelectionSorter sorter; - if (config.getSorterManner() != null) { - EntityDescriptor entityDescriptor = entitySelector.getEntityDescriptor(); - if (!EntitySelectorConfig.hasSorter(config.getSorterManner(), entityDescriptor)) { + var sorterManner = config.getSorterManner(); + if (sorterManner != null) { + var entityDescriptor = entitySelector.getEntityDescriptor(); + if (!EntitySelectorConfig.hasSorter(sorterManner, entityDescriptor)) { return entitySelector; } - sorter = EntitySelectorConfig.determineSorter(config.getSorterManner(), entityDescriptor); + sorter = EntitySelectorConfig.determineSorter(sorterManner, entityDescriptor); } else if (config.getSorterComparatorClass() != null) { Comparator sorterComparator = instanceCache.newInstance(config, "sorterComparatorClass", config.getSorterComparatorClass()); @@ -283,7 +273,7 @@ protected EntitySelector applySorting(SelectionCacheType resolvedCach } else { throw new IllegalArgumentException("The entitySelectorConfig (" + config + ") with resolvedSelectionOrder (" + resolvedSelectionOrder - + ") needs a sorterManner (" + config.getSorterManner() + + ") needs a sorterManner (" + sorterManner + ") or a sorterComparatorClass (" + config.getSorterComparatorClass() + ") or a sorterWeightFactoryClass (" + config.getSorterWeightFactoryClass() + ") or a sorterClass (" + config.getSorterClass() + ")."); @@ -357,14 +347,13 @@ private EntitySelector applySelectedLimit(SelectionOrder resolvedSele private EntitySelector applyMimicRecording(HeuristicConfigPolicy configPolicy, EntitySelector entitySelector) { - if (config.getId() != null) { - if (config.getId().isEmpty()) { - throw new IllegalArgumentException("The entitySelectorConfig (" + config - + ") has an empty id (" + config.getId() + ")."); + var id = config.getId(); + if (id != null) { + if (id.isEmpty()) { + throw new IllegalArgumentException("The entitySelectorConfig (%s) has an empty id (%s).".formatted(config, id)); } - MimicRecordingEntitySelector mimicRecordingEntitySelector = - new MimicRecordingEntitySelector<>(entitySelector); - configPolicy.addEntityMimicRecorder(config.getId(), mimicRecordingEntitySelector); + var mimicRecordingEntitySelector = new MimicRecordingEntitySelector<>(entitySelector); + configPolicy.addEntityMimicRecorder(id, mimicRecordingEntitySelector); entitySelector = mimicRecordingEntitySelector; } return entitySelector; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/SubListSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/SubListSelectorFactory.java index a994e33791..81c3dddc99 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/SubListSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/list/SubListSelectorFactory.java @@ -36,10 +36,8 @@ public static SubListSelectorFactory create(SubListSelect return new SubListSelectorFactory<>(subListSelectorConfig); } - public SubListSelector buildSubListSelector( - HeuristicConfigPolicy configPolicy, - EntitySelector entitySelector, - SelectionCacheType minimumCacheType, + public SubListSelector buildSubListSelector(HeuristicConfigPolicy configPolicy, + EntitySelector entitySelector, SelectionCacheType minimumCacheType, SelectionOrder inheritedSelectionOrder) { if (config.getMimicSelectorRef() != null) { return buildMimicReplaying(configPolicy); @@ -88,14 +86,14 @@ SubListSelector buildMimicReplaying(HeuristicConfigPolicy private SubListSelector applyMimicRecording(HeuristicConfigPolicy configPolicy, SubListSelector subListSelector) { - if (config.getId() != null) { - if (config.getId().isEmpty()) { - throw new IllegalArgumentException("The subListSelectorConfig (" + config - + ") has an empty id (" + config.getId() + ")."); + var id = config.getId(); + if (id != null) { + if (id.isEmpty()) { + throw new IllegalArgumentException( + "The subListSelectorConfig (%s) has an empty id (%s).".formatted(config, id)); } - MimicRecordingSubListSelector mimicRecordingSubListSelector = - new MimicRecordingSubListSelector<>(subListSelector); - configPolicy.addSubListMimicRecorder(config.getId(), mimicRecordingSubListSelector); + var mimicRecordingSubListSelector = new MimicRecordingSubListSelector<>(subListSelector); + configPolicy.addSubListMimicRecorder(id, mimicRecordingSubListSelector); subListSelector = mimicRecordingSubListSelector; } return subListSelector; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java index 0ad1d48bb6..ef3f58f0dc 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/AbstractMoveSelectorFactory.java @@ -90,11 +90,12 @@ protected MoveSelectorConfig buildUnfoldedMoveSelectorConfig( return null; } - protected static void checkUnfolded(String configPropertyName, Object configProperty) { + protected static T checkUnfolded(String configPropertyName, T configProperty) { if (configProperty == null) { - throw new IllegalStateException("The " + configPropertyName + " (" + configProperty - + ") should haven been initialized during unfolding."); + throw new IllegalStateException("The %s (%s) should haven been initialized during unfolding." + .formatted(configPropertyName, configProperty)); } + return configProperty; } private void validateResolvedCacheType(SelectionCacheType resolvedCacheType, MoveSelector moveSelector) { @@ -107,31 +108,22 @@ private void validateResolvedCacheType(SelectionCacheType resolvedCacheType, Mov protected boolean determineBaseRandomSelection(SelectionCacheType resolvedCacheType, SelectionOrder resolvedSelectionOrder) { - switch (resolvedSelectionOrder) { - case ORIGINAL: - return false; - case SORTED: - case SHUFFLED: - case PROBABILISTIC: + return switch (resolvedSelectionOrder) { + case ORIGINAL, SORTED, SHUFFLED, PROBABILISTIC -> // baseValueSelector and lower should be ORIGINAL if they are going to get cached completely - return false; - case RANDOM: + false; + case RANDOM -> // Predict if caching will occur - return resolvedCacheType.isNotCached() || (isBaseInherentlyCached() && !hasFiltering()); - default: - throw new IllegalStateException("The selectionOrder (" + resolvedSelectionOrder - + ") is not implemented."); - } + resolvedCacheType.isNotCached() || isBaseInherentlyCached() && config.getFilterClass() == null; + default -> throw new IllegalStateException("The selectionOrder (" + resolvedSelectionOrder + + ") is not implemented."); + }; } protected boolean isBaseInherentlyCached() { return false; } - private boolean hasFiltering() { - return config.getFilterClass() != null; - } - private MoveSelector applyFiltering(MoveSelector moveSelector, boolean skipNonDoableMoves) { /* * Do not filter out pointless moves in Construction Heuristics and Exhaustive Search, @@ -142,9 +134,10 @@ private MoveSelector applyFiltering(MoveSelector moveSelec SelectionFilter> baseFilter = skipNonDoableMoves ? DoableMoveSelectionFilter.INSTANCE : null; - if (hasFiltering()) { + var filterClass = config.getFilterClass(); + if (filterClass != null) { SelectionFilter> selectionFilter = - ConfigUtils.newInstance(config, "filterClass", config.getFilterClass()); + ConfigUtils.newInstance(config, "filterClass", filterClass); SelectionFilter> finalFilter = baseFilter == null ? selectionFilter : SelectionFilter.compose(baseFilter, selectionFilter); return FilteringMoveSelector.of(moveSelector, finalFilter); @@ -193,25 +186,26 @@ protected MoveSelector applySorting(SelectionCacheType resolvedCacheT SelectionOrder resolvedSelectionOrder, MoveSelector moveSelector) { if (resolvedSelectionOrder == SelectionOrder.SORTED) { SelectionSorter> sorter; - if (config.getSorterComparatorClass() != null) { - Comparator> sorterComparator = ConfigUtils.newInstance(config, - "sorterComparatorClass", config.getSorterComparatorClass()); + var sorterComparatorClass = config.getSorterComparatorClass(); + var sorterWeightFactoryClass = config.getSorterWeightFactoryClass(); + var sorterClass = config.getSorterClass(); + if (sorterComparatorClass != null) { + Comparator> sorterComparator = + ConfigUtils.newInstance(config, "sorterComparatorClass", sorterComparatorClass); sorter = new ComparatorSelectionSorter<>(sorterComparator, SelectionSorterOrder.resolve(config.getSorterOrder())); - } else if (config.getSorterWeightFactoryClass() != null) { + } else if (sorterWeightFactoryClass != null) { SelectionSorterWeightFactory> sorterWeightFactory = - ConfigUtils.newInstance(config, "sorterWeightFactoryClass", - config.getSorterWeightFactoryClass()); + ConfigUtils.newInstance(config, "sorterWeightFactoryClass", sorterWeightFactoryClass); sorter = new WeightFactorySelectionSorter<>(sorterWeightFactory, SelectionSorterOrder.resolve(config.getSorterOrder())); - } else if (config.getSorterClass() != null) { - sorter = ConfigUtils.newInstance(config, "sorterClass", config.getSorterClass()); + } else if (sorterClass != null) { + sorter = ConfigUtils.newInstance(config, "sorterClass", sorterClass); } else { - throw new IllegalArgumentException("The moveSelectorConfig (" + config - + ") with resolvedSelectionOrder (" + resolvedSelectionOrder - + ") needs a sorterComparatorClass (" + config.getSorterComparatorClass() - + ") or a sorterWeightFactoryClass (" + config.getSorterWeightFactoryClass() - + ") or a sorterClass (" + config.getSorterClass() + ")."); + throw new IllegalArgumentException( + "The moveSelectorConfig (%s) with resolvedSelectionOrder (%s) needs a sorterComparatorClass (%s) or a sorterWeightFactoryClass (%s) or a sorterClass (%s)." + .formatted(config, resolvedSelectionOrder, sorterComparatorClass, sorterWeightFactoryClass, + sorterClass)); } moveSelector = new SortingMoveSelector<>(moveSelector, resolvedCacheType, sorter); } @@ -219,26 +213,26 @@ protected MoveSelector applySorting(SelectionCacheType resolvedCacheT } private void validateProbability(SelectionOrder resolvedSelectionOrder) { - if (config.getProbabilityWeightFactoryClass() != null && resolvedSelectionOrder != SelectionOrder.PROBABILISTIC) { - throw new IllegalArgumentException("The moveSelectorConfig (" + config - + ") with probabilityWeightFactoryClass (" + config.getProbabilityWeightFactoryClass() - + ") has a resolvedSelectionOrder (" + resolvedSelectionOrder - + ") that is not " + SelectionOrder.PROBABILISTIC + "."); + var probabilityWeightFactoryClass = config.getProbabilityWeightFactoryClass(); + if (probabilityWeightFactoryClass != null && resolvedSelectionOrder != SelectionOrder.PROBABILISTIC) { + throw new IllegalArgumentException( + "The moveSelectorConfig (%s) with probabilityWeightFactoryClass (%s) has a resolvedSelectionOrder (%s) that is not %s." + .formatted(config, probabilityWeightFactoryClass, resolvedSelectionOrder, + SelectionOrder.PROBABILISTIC)); } } private MoveSelector applyProbability(SelectionCacheType resolvedCacheType, SelectionOrder resolvedSelectionOrder, MoveSelector moveSelector) { if (resolvedSelectionOrder == SelectionOrder.PROBABILISTIC) { - if (config.getProbabilityWeightFactoryClass() == null) { - throw new IllegalArgumentException("The moveSelectorConfig (" + config - + ") with resolvedSelectionOrder (" + resolvedSelectionOrder - + ") needs a probabilityWeightFactoryClass (" - + config.getProbabilityWeightFactoryClass() + ")."); + var probabilityWeightFactoryClass = config.getProbabilityWeightFactoryClass(); + if (probabilityWeightFactoryClass == null) { + throw new IllegalArgumentException( + "The moveSelectorConfig (%s) with resolvedSelectionOrder (%s) needs a probabilityWeightFactoryClass (%s)." + .formatted(config, resolvedSelectionOrder, probabilityWeightFactoryClass)); } SelectionProbabilityWeightFactory> probabilityWeightFactory = - ConfigUtils.newInstance(config, "probabilityWeightFactoryClass", - config.getProbabilityWeightFactoryClass()); + ConfigUtils.newInstance(config, "probabilityWeightFactoryClass", probabilityWeightFactoryClass); moveSelector = new ProbabilityMoveSelector<>(moveSelector, resolvedCacheType, probabilityWeightFactory); } return moveSelector; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/UnionMoveSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/UnionMoveSelectorFactory.java index 0e3d03182f..58ed734d1c 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/UnionMoveSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/composite/UnionMoveSelectorFactory.java @@ -53,15 +53,15 @@ The selector configuration (%s) already includes the Nearby Selection setting, m buildInnerMoveSelectors(moveSelectorConfigList, configPolicy, minimumCacheType, randomSelection); SelectionProbabilityWeightFactory> selectorProbabilityWeightFactory; - if (config.getSelectorProbabilityWeightFactoryClass() != null) { + var selectorProbabilityWeightFactoryClass = config.getSelectorProbabilityWeightFactoryClass(); + if (selectorProbabilityWeightFactoryClass != null) { if (!randomSelection) { - throw new IllegalArgumentException("The moveSelectorConfig (" + config - + ") with selectorProbabilityWeightFactoryClass (" - + config.getSelectorProbabilityWeightFactoryClass() - + ") has non-random randomSelection (" + randomSelection + ")."); + throw new IllegalArgumentException( + "The moveSelectorConfig (%s) with selectorProbabilityWeightFactoryClass (%s) has non-random randomSelection (%s)." + .formatted(configPolicy, selectorProbabilityWeightFactoryClass, randomSelection)); } - selectorProbabilityWeightFactory = ConfigUtils.newInstance(config, - "selectorProbabilityWeightFactoryClass", config.getSelectorProbabilityWeightFactoryClass()); + selectorProbabilityWeightFactory = ConfigUtils.newInstance(config, "selectorProbabilityWeightFactoryClass", + selectorProbabilityWeightFactoryClass); } else if (randomSelection) { Map, Double> fixedProbabilityWeightMap = new HashMap<>(moveSelectorConfigList.size()); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/factory/MoveIteratorFactoryFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/factory/MoveIteratorFactoryFactory.java index 725c947938..585a46c2d5 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/factory/MoveIteratorFactoryFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/factory/MoveIteratorFactoryFactory.java @@ -17,12 +17,12 @@ public MoveIteratorFactoryFactory(MoveIteratorFactoryConfig moveSelectorConfig) @Override public MoveSelector buildBaseMoveSelector(HeuristicConfigPolicy configPolicy, SelectionCacheType minimumCacheType, boolean randomSelection) { - if (config.getMoveIteratorFactoryClass() == null) { - throw new IllegalArgumentException("The moveIteratorFactoryConfig (" + config - + ") lacks a moveListFactoryClass (" + config.getMoveIteratorFactoryClass() + ")."); + var moveIteratorFactoryClass = config.getMoveIteratorFactoryClass(); + if (moveIteratorFactoryClass == null) { + throw new IllegalArgumentException("The moveIteratorFactoryConfig (%s) lacks a moveListFactoryClass (%s)." + .formatted(config, moveIteratorFactoryClass)); } - MoveIteratorFactory moveIteratorFactory = ConfigUtils.newInstance(config, - "moveIteratorFactoryClass", config.getMoveIteratorFactoryClass()); + var moveIteratorFactory = ConfigUtils.newInstance(config, "moveIteratorFactoryClass", moveIteratorFactoryClass); ConfigUtils.applyCustomProperties(moveIteratorFactory, "moveIteratorFactoryClass", config.getMoveIteratorFactoryCustomProperties(), "moveIteratorFactoryCustomProperties"); return new MoveIteratorFactoryToMoveSelectorBridge<>(moveIteratorFactory, randomSelection); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/factory/MoveListFactoryFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/factory/MoveListFactoryFactory.java index f22651d1c6..59140fe21e 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/factory/MoveListFactoryFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/factory/MoveListFactoryFactory.java @@ -17,12 +17,13 @@ public MoveListFactoryFactory(MoveListFactoryConfig moveSelectorConfig) { @Override public MoveSelector buildBaseMoveSelector(HeuristicConfigPolicy configPolicy, SelectionCacheType minimumCacheType, boolean randomSelection) { - if (config.getMoveListFactoryClass() == null) { - throw new IllegalArgumentException("The moveListFactoryConfig (" + config - + ") lacks a moveListFactoryClass (" + config.getMoveListFactoryClass() + ")."); + var moveListFactoryClass = config.getMoveListFactoryClass(); + if (moveListFactoryClass == null) { + throw new IllegalArgumentException("The moveListFactoryConfig (%s) lacks a moveListFactoryClass (%s)." + .formatted(config, moveListFactoryClass)); } MoveListFactory moveListFactory = - ConfigUtils.newInstance(config, "moveListFactoryClass", config.getMoveListFactoryClass()); + ConfigUtils.newInstance(config, "moveListFactoryClass", moveListFactoryClass); ConfigUtils.applyCustomProperties(moveListFactory, "moveListFactoryClass", config.getMoveListFactoryCustomProperties(), "moveListFactoryCustomProperties"); // MoveListFactoryToMoveSelectorBridge caches by design, so it uses the minimumCacheType diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMoveSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMoveSelectorFactory.java index 1d7482998d..f7d18cb3c2 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMoveSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/ChangeMoveSelectorFactory.java @@ -131,13 +131,33 @@ The changeMoveSelectorConfig ({}) is being used for a list variable. Please update your solver config to use {} now.""", config, ListChangeMoveSelectorConfig.class.getSimpleName()); ListChangeMoveSelectorConfig listChangeMoveSelectorConfig = ListChangeMoveSelectorFactory.buildChildMoveSelectorConfig( - variableDescriptor, config.getValueSelectorConfig(), - new DestinationSelectorConfig() - .withEntitySelectorConfig(config.getEntitySelectorConfig()) - .withValueSelectorConfig(config.getValueSelectorConfig())); + variableDescriptor, config.getValueSelectorConfig(), createDestinationSelectorConfig()); if (inheritFoldedConfig) { listChangeMoveSelectorConfig.inheritFolded(config); } return listChangeMoveSelectorConfig; } + + private DestinationSelectorConfig createDestinationSelectorConfig() { + var entitySelectorConfig = config.getEntitySelectorConfig(); + var valueSelectorConfig = config.getValueSelectorConfig(); + if (entitySelectorConfig == null) { + if (valueSelectorConfig == null) { + return new DestinationSelectorConfig(); + } else { + return new DestinationSelectorConfig() + .withValueSelectorConfig(valueSelectorConfig); + } + } else { + if (valueSelectorConfig == null) { + return new DestinationSelectorConfig() + .withEntitySelectorConfig(entitySelectorConfig); + } else { + return new DestinationSelectorConfig() + .withEntitySelectorConfig(entitySelectorConfig) + .withValueSelectorConfig(valueSelectorConfig); + } + } + } + } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarChangeMoveSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarChangeMoveSelectorFactory.java index 3010ea91a6..d3f5c3ced7 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarChangeMoveSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/PillarChangeMoveSelectorFactory.java @@ -2,7 +2,6 @@ import java.util.Collections; import java.util.Comparator; -import java.util.List; import java.util.Objects; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; @@ -11,11 +10,9 @@ import ai.timefold.solver.core.config.heuristic.selector.move.generic.PillarChangeMoveSelectorConfig; import ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig; import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy; -import ai.timefold.solver.core.impl.heuristic.selector.entity.pillar.PillarSelector; import ai.timefold.solver.core.impl.heuristic.selector.entity.pillar.PillarSelectorFactory; import ai.timefold.solver.core.impl.heuristic.selector.move.AbstractMoveSelectorFactory; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; -import ai.timefold.solver.core.impl.heuristic.selector.value.ValueSelector; import ai.timefold.solver.core.impl.heuristic.selector.value.ValueSelectorFactory; public class PillarChangeMoveSelectorFactory @@ -28,19 +25,18 @@ public PillarChangeMoveSelectorFactory(PillarChangeMoveSelectorConfig moveSelect @Override protected MoveSelector buildBaseMoveSelector(HeuristicConfigPolicy configPolicy, SelectionCacheType minimumCacheType, boolean randomSelection) { - PillarSelectorConfig pillarSelectorConfig = - Objects.requireNonNullElseGet(config.getPillarSelectorConfig(), PillarSelectorConfig::new); - ValueSelectorConfig valueSelectorConfig = - Objects.requireNonNullElseGet(config.getValueSelectorConfig(), ValueSelectorConfig::new); - List variableNameIncludeList = config.getValueSelectorConfig() == null - || config.getValueSelectorConfig().getVariableName() == null ? null - : Collections.singletonList(config.getValueSelectorConfig().getVariableName()); - SelectionOrder selectionOrder = SelectionOrder.fromRandomSelectionBoolean(randomSelection); - PillarSelector pillarSelector = PillarSelectorFactory. create(pillarSelectorConfig) + var pillarSelectorConfig = Objects.requireNonNullElseGet(config.getPillarSelectorConfig(), PillarSelectorConfig::new); + var valueSelectorConfig = config.getValueSelectorConfig(); + var variableNameIncludeList = valueSelectorConfig == null + || valueSelectorConfig.getVariableName() == null ? null + : Collections.singletonList(valueSelectorConfig.getVariableName()); + var selectionOrder = SelectionOrder.fromRandomSelectionBoolean(randomSelection); + var pillarSelector = PillarSelectorFactory. create(pillarSelectorConfig) .buildPillarSelector(configPolicy, config.getSubPillarType(), (Class>) config.getSubPillarSequenceComparatorClass(), minimumCacheType, selectionOrder, variableNameIncludeList); - ValueSelector valueSelector = ValueSelectorFactory. create(valueSelectorConfig) + var valueSelector = ValueSelectorFactory + . create(Objects.requireNonNullElseGet(valueSelectorConfig, ValueSelectorConfig::new)) .buildValueSelector(configPolicy, pillarSelector.getEntityDescriptor(), minimumCacheType, selectionOrder); return new PillarChangeMoveSelector<>(pillarSelector, valueSelector, randomSelection); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListChangeMoveSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListChangeMoveSelectorFactory.java index 1a942f21ce..9c915c3473 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListChangeMoveSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/ListChangeMoveSelectorFactory.java @@ -1,11 +1,8 @@ package ai.timefold.solver.core.impl.heuristic.selector.move.generic.list; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; -import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; import ai.timefold.solver.core.api.domain.variable.PlanningListVariable; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; @@ -15,18 +12,14 @@ import ai.timefold.solver.core.config.heuristic.selector.move.MoveSelectorConfig; import ai.timefold.solver.core.config.heuristic.selector.move.generic.list.ListChangeMoveSelectorConfig; import ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig; -import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor; -import ai.timefold.solver.core.impl.domain.variable.descriptor.GenuineVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.VariableDescriptor; import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy; import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelectorFactory; -import ai.timefold.solver.core.impl.heuristic.selector.list.DestinationSelector; import ai.timefold.solver.core.impl.heuristic.selector.list.DestinationSelectorFactory; import ai.timefold.solver.core.impl.heuristic.selector.move.AbstractMoveSelectorFactory; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; import ai.timefold.solver.core.impl.heuristic.selector.value.EntityIndependentValueSelector; -import ai.timefold.solver.core.impl.heuristic.selector.value.ValueSelector; import ai.timefold.solver.core.impl.heuristic.selector.value.ValueSelectorFactory; public class ListChangeMoveSelectorFactory @@ -39,88 +32,84 @@ public ListChangeMoveSelectorFactory(ListChangeMoveSelectorConfig moveSelectorCo @Override protected MoveSelector buildBaseMoveSelector(HeuristicConfigPolicy configPolicy, SelectionCacheType minimumCacheType, boolean randomSelection) { - checkUnfolded("valueSelectorConfig", config.getValueSelectorConfig()); - checkUnfolded("destinationSelectorConfig", config.getDestinationSelectorConfig()); - checkUnfolded("destinationEntitySelectorConfig", config.getDestinationSelectorConfig().getEntitySelectorConfig()); - checkUnfolded("destinationValueSelectorConfig", config.getDestinationSelectorConfig().getValueSelectorConfig()); + var valueSelectorConfig = checkUnfolded("valueSelectorConfig", config.getValueSelectorConfig()); + var destinationSelectorConfig = checkUnfolded("destinationSelectorConfig", config.getDestinationSelectorConfig()); + var destinationEntitySelectorConfig = + checkUnfolded("destinationEntitySelectorConfig", destinationSelectorConfig.getEntitySelectorConfig()); + checkUnfolded("destinationValueSelectorConfig", destinationSelectorConfig.getValueSelectorConfig()); - SelectionOrder selectionOrder = SelectionOrder.fromRandomSelectionBoolean(randomSelection); + var selectionOrder = SelectionOrder.fromRandomSelectionBoolean(randomSelection); - EntityDescriptor entityDescriptor = EntitySelectorFactory - . create(config.getDestinationSelectorConfig().getEntitySelectorConfig()) + var entityDescriptor = EntitySelectorFactory + . create(destinationEntitySelectorConfig) .extractEntityDescriptor(configPolicy); - ValueSelector sourceValueSelector = ValueSelectorFactory - . create(config.getValueSelectorConfig()) + var sourceValueSelector = ValueSelectorFactory + . create(valueSelectorConfig) .buildValueSelector(configPolicy, entityDescriptor, minimumCacheType, selectionOrder); - if (!(sourceValueSelector instanceof EntityIndependentValueSelector)) { - throw new IllegalArgumentException("The listChangeMoveSelector (" + config - + ") for a list variable needs to be based on an " - + EntityIndependentValueSelector.class.getSimpleName() + " (" + sourceValueSelector + ")." - + " Check your valueSelectorConfig."); + if (!(sourceValueSelector instanceof EntityIndependentValueSelector castSourceValueSelector)) { + throw new IllegalArgumentException(""" + The listChangeMoveSelector (%s) for a list variable needs to be based on an %s (%s). + Check your valueSelectorConfig.""" + .formatted(config, EntityIndependentValueSelector.class.getSimpleName(), sourceValueSelector)); } - DestinationSelector destinationSelector = DestinationSelectorFactory - . create(config.getDestinationSelectorConfig()) + var destinationSelector = DestinationSelectorFactory + . create(destinationSelectorConfig) .buildDestinationSelector(configPolicy, minimumCacheType, randomSelection); - return new ListChangeMoveSelector<>( - (EntityIndependentValueSelector) sourceValueSelector, - destinationSelector, - randomSelection); + return new ListChangeMoveSelector<>(castSourceValueSelector, destinationSelector, randomSelection); } @Override protected MoveSelectorConfig buildUnfoldedMoveSelectorConfig(HeuristicConfigPolicy configPolicy) { - Collection> entityDescriptors; - EntityDescriptor onlyEntityDescriptor = config.getDestinationSelectorConfig() == null ? null - : config.getDestinationSelectorConfig().getEntitySelectorConfig() == null ? null - : EntitySelectorFactory - . create(config.getDestinationSelectorConfig().getEntitySelectorConfig()) - .extractEntityDescriptor(configPolicy); - if (onlyEntityDescriptor != null) { - entityDescriptors = Collections.singletonList(onlyEntityDescriptor); - } else { - entityDescriptors = configPolicy.getSolutionDescriptor().getGenuineEntityDescriptors(); - } + var destinationSelectorConfig = config.getDestinationSelectorConfig(); + var destinationEntitySelectorConfig = destinationSelectorConfig == null ? null + : destinationSelectorConfig.getEntitySelectorConfig(); + var onlyEntityDescriptor = destinationEntitySelectorConfig == null ? null + : EntitySelectorFactory. create(destinationEntitySelectorConfig) + .extractEntityDescriptor(configPolicy); + var entityDescriptors = + onlyEntityDescriptor == null ? configPolicy.getSolutionDescriptor().getGenuineEntityDescriptors() + : Collections.singletonList(onlyEntityDescriptor); if (entityDescriptors.size() > 1) { - throw new IllegalArgumentException("The listChangeMoveSelector (" + config - + ") cannot unfold when there are multiple entities (" + entityDescriptors + ")." - + " Please use one listChangeMoveSelector per each planning list variable."); + throw new IllegalArgumentException(""" + The listChangeMoveSelector (%s) cannot unfold when there are multiple entities (%s). + Please use one listChangeMoveSelector per each planning list variable.""" + .formatted(config, entityDescriptors)); } - EntityDescriptor entityDescriptor = entityDescriptors.iterator().next(); + var entityDescriptor = entityDescriptors.iterator().next(); - List> variableDescriptorList = new ArrayList<>(); - GenuineVariableDescriptor onlyVariableDescriptor = config.getValueSelectorConfig() == null ? null - : ValueSelectorFactory. create(config.getValueSelectorConfig()) + var variableDescriptorList = new ArrayList>(); + var valueSelectorConfig = config.getValueSelectorConfig(); + var onlyVariableDescriptor = valueSelectorConfig == null ? null + : ValueSelectorFactory. create(valueSelectorConfig) .extractVariableDescriptor(configPolicy, entityDescriptor); - GenuineVariableDescriptor onlyDestinationVariableDescriptor = - config.getDestinationSelectorConfig() == null ? null - : config.getDestinationSelectorConfig().getValueSelectorConfig() == null ? null - : ValueSelectorFactory - . create(config.getDestinationSelectorConfig().getValueSelectorConfig()) - .extractVariableDescriptor(configPolicy, entityDescriptor); + var destinationValueSelectorConfig = destinationSelectorConfig == null ? null + : destinationSelectorConfig.getValueSelectorConfig(); + var onlyDestinationVariableDescriptor = + destinationValueSelectorConfig == null ? null + : ValueSelectorFactory. create(destinationValueSelectorConfig) + .extractVariableDescriptor(configPolicy, entityDescriptor); if (onlyVariableDescriptor != null && onlyDestinationVariableDescriptor != null) { if (!onlyVariableDescriptor.isListVariable()) { - throw new IllegalArgumentException("The listChangeMoveSelector (" + config - + ") is configured to use a planning variable (" + onlyVariableDescriptor - + "), which is not a planning list variable." - + " Either fix your annotations and use a @" + PlanningListVariable.class.getSimpleName() - + " on the variable to make it work with listChangeMoveSelector" - + " or use a changeMoveSelector instead."); + throw new IllegalArgumentException(""" + The listChangeMoveSelector (%s) is configured to use a planning variable (%s), \ + which is not a planning list variable. + Either fix your annotations and use a @%s on the variable to make it work with listChangeMoveSelector + or use a changeMoveSelector instead.""" + .formatted(config, onlyVariableDescriptor, PlanningListVariable.class.getSimpleName())); } if (!onlyDestinationVariableDescriptor.isListVariable()) { - throw new IllegalArgumentException("The destinationSelector (" + config.getDestinationSelectorConfig() - + ") is configured to use a planning variable (" + onlyDestinationVariableDescriptor - + "), which is not a planning list variable."); + throw new IllegalArgumentException( + "The destinationSelector (%s) is configured to use a planning variable (%s), which is not a planning list variable." + .formatted(destinationSelectorConfig, onlyDestinationVariableDescriptor)); } if (onlyVariableDescriptor != onlyDestinationVariableDescriptor) { - throw new IllegalArgumentException("The listChangeMoveSelector's valueSelector (" - + config.getValueSelectorConfig() - + ") and destinationSelector's valueSelector (" - + config.getDestinationSelectorConfig().getValueSelectorConfig() - + ") must be configured for the same planning variable."); + throw new IllegalArgumentException( + "The listChangeMoveSelector's valueSelector (%s) and destinationSelector's valueSelector (%s) must be configured for the same planning variable." + .formatted(valueSelectorConfig, destinationValueSelectorConfig)); } if (onlyEntityDescriptor != null) { // No need for unfolding or deducing @@ -132,20 +121,20 @@ protected MoveSelectorConfig buildUnfoldedMoveSelectorConfig(HeuristicConfigP entityDescriptor.getGenuineVariableDescriptorList().stream() .filter(VariableDescriptor::isListVariable) .map(variableDescriptor -> ((ListVariableDescriptor) variableDescriptor)) - .collect(Collectors.toList())); + .toList()); } if (variableDescriptorList.isEmpty()) { - throw new IllegalArgumentException("The listChangeMoveSelector (" + config - + ") cannot unfold because there are no planning list variables."); + throw new IllegalArgumentException( + "The listChangeMoveSelector (%s) cannot unfold because there are no planning list variables." + .formatted(config)); } if (variableDescriptorList.size() > 1) { - throw new IllegalArgumentException("The listChangeMoveSelector (" + config - + ") cannot unfold because there are multiple planning list variables."); + throw new IllegalArgumentException( + "The listChangeMoveSelector (%s) cannot unfold because there are multiple planning list variables." + .formatted(config)); } - ListChangeMoveSelectorConfig listChangeMoveSelectorConfig = buildChildMoveSelectorConfig( - variableDescriptorList.get(0), - config.getValueSelectorConfig(), - config.getDestinationSelectorConfig()); + var listChangeMoveSelectorConfig = + buildChildMoveSelectorConfig(variableDescriptorList.get(0), valueSelectorConfig, destinationSelectorConfig); listChangeMoveSelectorConfig.inheritFolded(config); return listChangeMoveSelectorConfig; } @@ -155,7 +144,7 @@ public static ListChangeMoveSelectorConfig buildChildMoveSelectorConfig( ValueSelectorConfig inheritedValueSelectorConfig, DestinationSelectorConfig inheritedDestinationSelectorConfig) { - ValueSelectorConfig childValueSelectorConfig = new ValueSelectorConfig(inheritedValueSelectorConfig); + var childValueSelectorConfig = new ValueSelectorConfig(inheritedValueSelectorConfig); if (childValueSelectorConfig.getMimicSelectorRef() == null) { childValueSelectorConfig.setVariableName(variableDescriptor.getVariableName()); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListChangeMoveSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListChangeMoveSelectorFactory.java index 823de38eaf..940832dcbd 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListChangeMoveSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SubListChangeMoveSelectorFactory.java @@ -3,10 +3,8 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.stream.Collectors; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType; import ai.timefold.solver.core.config.heuristic.selector.common.SelectionOrder; @@ -17,15 +15,11 @@ import ai.timefold.solver.core.config.heuristic.selector.move.generic.list.SubListChangeMoveSelectorConfig; import ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig; import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor; -import ai.timefold.solver.core.impl.domain.variable.descriptor.GenuineVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.VariableDescriptor; import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy; -import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelector; import ai.timefold.solver.core.impl.heuristic.selector.entity.EntitySelectorFactory; -import ai.timefold.solver.core.impl.heuristic.selector.list.DestinationSelector; import ai.timefold.solver.core.impl.heuristic.selector.list.DestinationSelectorFactory; -import ai.timefold.solver.core.impl.heuristic.selector.list.SubListSelector; import ai.timefold.solver.core.impl.heuristic.selector.list.SubListSelectorFactory; import ai.timefold.solver.core.impl.heuristic.selector.move.AbstractMoveSelectorFactory; import ai.timefold.solver.core.impl.heuristic.selector.move.MoveSelector; @@ -41,82 +35,75 @@ public SubListChangeMoveSelectorFactory(SubListChangeMoveSelectorConfig moveSele @Override protected MoveSelector buildBaseMoveSelector(HeuristicConfigPolicy configPolicy, SelectionCacheType minimumCacheType, boolean randomSelection) { - checkUnfolded("subListSelectorConfig", config.getSubListSelectorConfig()); - checkUnfolded("destinationSelectorConfig", config.getDestinationSelectorConfig()); + var subListSelectorConfig = checkUnfolded("subListSelectorConfig", config.getSubListSelectorConfig()); + var destinationSelectorConfig = checkUnfolded("destinationSelectorConfig", config.getDestinationSelectorConfig()); if (!randomSelection) { - throw new IllegalArgumentException("The subListChangeMoveSelector (" + config - + ") only supports random selection order."); + throw new IllegalArgumentException("The subListChangeMoveSelector (%s) only supports random selection order." + .formatted(config)); } - - SelectionOrder selectionOrder = SelectionOrder.fromRandomSelectionBoolean(randomSelection); - - EntitySelector entitySelector = EntitySelectorFactory - . create(config.getDestinationSelectorConfig().getEntitySelectorConfig()) + var selectionOrder = SelectionOrder.fromRandomSelectionBoolean(randomSelection); + var entitySelector = EntitySelectorFactory + . create(destinationSelectorConfig.getEntitySelectorConfig()) .buildEntitySelector(configPolicy, minimumCacheType, selectionOrder); - - SubListSelector subListSelector = SubListSelectorFactory - . create(config.getSubListSelectorConfig()) + var subListSelector = SubListSelectorFactory + . create(subListSelectorConfig) .buildSubListSelector(configPolicy, entitySelector, minimumCacheType, selectionOrder); - - DestinationSelector destinationSelector = DestinationSelectorFactory - . create(config.getDestinationSelectorConfig()) + var destinationSelector = DestinationSelectorFactory + . create(destinationSelectorConfig) .buildDestinationSelector(configPolicy, minimumCacheType, randomSelection); - - boolean selectReversingMoveToo = Objects.requireNonNullElse(config.getSelectReversingMoveToo(), true); - + var selectReversingMoveToo = Objects.requireNonNullElse(config.getSelectReversingMoveToo(), true); return new RandomSubListChangeMoveSelector<>(subListSelector, destinationSelector, selectReversingMoveToo); } @Override protected MoveSelectorConfig buildUnfoldedMoveSelectorConfig(HeuristicConfigPolicy configPolicy) { + var destinationSelectorConfig = config.getDestinationSelectorConfig(); + var destinationEntitySelectorConfig = destinationSelectorConfig == null ? null + : destinationSelectorConfig.getEntitySelectorConfig(); Collection> entityDescriptors; - EntityDescriptor onlyEntityDescriptor = config.getDestinationSelectorConfig() == null ? null - : config.getDestinationSelectorConfig().getEntitySelectorConfig() == null ? null - : EntitySelectorFactory - . create(config.getDestinationSelectorConfig().getEntitySelectorConfig()) - .extractEntityDescriptor(configPolicy); + var onlyEntityDescriptor = destinationEntitySelectorConfig == null ? null + : EntitySelectorFactory. create(destinationEntitySelectorConfig) + .extractEntityDescriptor(configPolicy); if (onlyEntityDescriptor != null) { entityDescriptors = Collections.singletonList(onlyEntityDescriptor); } else { entityDescriptors = configPolicy.getSolutionDescriptor().getGenuineEntityDescriptors(); } if (entityDescriptors.size() > 1) { - throw new IllegalArgumentException("The subListChangeMoveSelector (" + config - + ") cannot unfold when there are multiple entities (" + entityDescriptors + ")." - + " Please use one subListChangeMoveSelector per each planning list variable."); + throw new IllegalArgumentException(""" + The subListChangeMoveSelector (%s) cannot unfold when there are multiple entities (%s). + Please use one subListChangeMoveSelector per each planning list variable.""" + .formatted(config, entityDescriptors)); } - EntityDescriptor entityDescriptor = entityDescriptors.iterator().next(); - - List> variableDescriptorList = new ArrayList<>(); - GenuineVariableDescriptor onlySubListVariableDescriptor = - config.getSubListSelectorConfig() == null ? null - : config.getSubListSelectorConfig().getValueSelectorConfig() == null ? null - : ValueSelectorFactory - . create(config.getSubListSelectorConfig().getValueSelectorConfig()) - .extractVariableDescriptor(configPolicy, entityDescriptor); - GenuineVariableDescriptor onlyDestinationVariableDescriptor = - config.getDestinationSelectorConfig() == null ? null - : config.getDestinationSelectorConfig().getValueSelectorConfig() == null ? null - : ValueSelectorFactory - . create(config.getDestinationSelectorConfig().getValueSelectorConfig()) - .extractVariableDescriptor(configPolicy, entityDescriptor); + var entityDescriptor = entityDescriptors.iterator().next(); + + var subListSelectorConfig = config.getSubListSelectorConfig(); + var subListValueSelectorConfig = subListSelectorConfig == null ? null + : subListSelectorConfig.getValueSelectorConfig(); + var variableDescriptorList = new ArrayList>(); + var onlySubListVariableDescriptor = subListValueSelectorConfig == null ? null + : ValueSelectorFactory. create(subListValueSelectorConfig) + .extractVariableDescriptor(configPolicy, entityDescriptor); + var destinationValueSelectorConfig = destinationSelectorConfig == null ? null + : destinationSelectorConfig.getValueSelectorConfig(); + var onlyDestinationVariableDescriptor = destinationValueSelectorConfig == null ? null + : ValueSelectorFactory. create(destinationValueSelectorConfig) + .extractVariableDescriptor(configPolicy, entityDescriptor); if (onlySubListVariableDescriptor != null && onlyDestinationVariableDescriptor != null) { if (!onlySubListVariableDescriptor.isListVariable()) { - throw new IllegalArgumentException("The subListChangeMoveSelector (" + config - + ") is configured to use a planning variable (" + onlySubListVariableDescriptor - + "), which is not a planning list variable."); + throw new IllegalArgumentException( + "The subListChangeMoveSelector (%s) is configured to use a planning variable (%s), which is not a planning list variable." + .formatted(config, onlySubListVariableDescriptor)); } if (!onlyDestinationVariableDescriptor.isListVariable()) { - throw new IllegalArgumentException("The subListChangeMoveSelector (" + config - + ") is configured to use a planning variable (" + onlyDestinationVariableDescriptor - + "), which is not a planning list variable."); + throw new IllegalArgumentException( + "The subListChangeMoveSelector (%s) is configured to use a planning variable (%s), which is not a planning list variable." + .formatted(config, onlyDestinationVariableDescriptor)); } if (onlySubListVariableDescriptor != onlyDestinationVariableDescriptor) { - throw new IllegalArgumentException("The subListSelector's valueSelector (" - + config.getSubListSelectorConfig().getValueSelectorConfig() - + ") and destinationSelector's valueSelector (" - + config.getDestinationSelectorConfig().getValueSelectorConfig() - + ") must be configured for the same planning variable."); + throw new IllegalArgumentException( + "The subListSelector's valueSelector (%s) and destinationSelector's valueSelector (%s) must be configured for the same planning variable." + .formatted(subListValueSelectorConfig, destinationEntitySelectorConfig)); } if (onlyEntityDescriptor != null) { // No need for unfolding or deducing @@ -128,43 +115,47 @@ protected MoveSelectorConfig buildUnfoldedMoveSelectorConfig(HeuristicConfigP entityDescriptor.getGenuineVariableDescriptorList().stream() .filter(VariableDescriptor::isListVariable) .map(variableDescriptor -> ((ListVariableDescriptor) variableDescriptor)) - .collect(Collectors.toList())); + .toList()); } if (variableDescriptorList.isEmpty()) { - throw new IllegalArgumentException("The subListChangeMoveSelector (" + config - + ") cannot unfold because there are no planning list variables."); + throw new IllegalArgumentException( + "The subListChangeMoveSelector (%s) cannot unfold because there are no planning list variables." + .formatted(config)); } if (variableDescriptorList.size() > 1) { - throw new IllegalArgumentException("The subListChangeMoveSelector (" + config - + ") cannot unfold because there are multiple planning list variables."); + throw new IllegalArgumentException( + "The subListChangeMoveSelector (%s) cannot unfold because there are multiple planning list variables." + .formatted(config)); } return buildChildMoveSelectorConfig(variableDescriptorList.get(0)); } private SubListChangeMoveSelectorConfig buildChildMoveSelectorConfig(ListVariableDescriptor variableDescriptor) { - SubListChangeMoveSelectorConfig subListChangeMoveSelectorConfig = config.copyConfig() - .withSubListSelectorConfig(new SubListSelectorConfig(config.getSubListSelectorConfig()) - .withValueSelectorConfig(Optional.ofNullable(config.getSubListSelectorConfig()) + var subListSelectorConfig = config.getSubListSelectorConfig(); + var destinationSelectorConfig = config.getDestinationSelectorConfig(); + var subListChangeMoveSelectorConfig = config.copyConfig() + .withSubListSelectorConfig(new SubListSelectorConfig(subListSelectorConfig) + .withValueSelectorConfig(Optional.ofNullable(subListSelectorConfig) .map(SubListSelectorConfig::getValueSelectorConfig) .map(ValueSelectorConfig::new) // use copy constructor if inherited not null .orElseGet(ValueSelectorConfig::new))) - .withDestinationSelectorConfig(new DestinationSelectorConfig(config.getDestinationSelectorConfig()) + .withDestinationSelectorConfig(new DestinationSelectorConfig(destinationSelectorConfig) .withEntitySelectorConfig( - Optional.ofNullable(config.getDestinationSelectorConfig()) + Optional.ofNullable(destinationSelectorConfig) .map(DestinationSelectorConfig::getEntitySelectorConfig) .map(EntitySelectorConfig::new) // use copy constructor if inherited not null .orElseGet(EntitySelectorConfig::new) // otherwise create new instance // override entity class (destination entity selector is never replaying) .withEntityClass(variableDescriptor.getEntityDescriptor().getEntityClass())) .withValueSelectorConfig( - Optional.ofNullable(config.getDestinationSelectorConfig()) + Optional.ofNullable(destinationSelectorConfig) .map(DestinationSelectorConfig::getValueSelectorConfig) .map(ValueSelectorConfig::new) // use copy constructor if inherited not null .orElseGet(ValueSelectorConfig::new) // otherwise create new instance // override variable name (destination value selector is never replaying) .withVariableName(variableDescriptor.getVariableName()))); - SubListSelectorConfig subListSelectorConfig = subListChangeMoveSelectorConfig.getSubListSelectorConfig(); + subListSelectorConfig = Objects.requireNonNull(subListChangeMoveSelectorConfig.getSubListSelectorConfig()); SubListConfigUtil.transferDeprecatedMinimumSubListSize( subListChangeMoveSelectorConfig, SubListChangeMoveSelectorConfig::getMinimumSubListSize, @@ -175,11 +166,10 @@ private SubListChangeMoveSelectorConfig buildChildMoveSelectorConfig(ListVariabl SubListChangeMoveSelectorConfig::getMaximumSubListSize, "subListSelector", subListSelectorConfig); - if (subListSelectorConfig.getMimicSelectorRef() == null) { - subListSelectorConfig.getValueSelectorConfig().setVariableName(variableDescriptor.getVariableName()); + Objects.requireNonNull(subListSelectorConfig.getValueSelectorConfig()) + .setVariableName(variableDescriptor.getVariableName()); } - return subListChangeMoveSelectorConfig; } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java index 1115a447e9..ecc814b200 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/value/ValueSelectorFactory.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.function.Function; import ai.timefold.solver.core.api.domain.solution.PlanningSolution; import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider; @@ -12,9 +13,7 @@ import ai.timefold.solver.core.config.heuristic.selector.value.ValueSelectorConfig; import ai.timefold.solver.core.enterprise.TimefoldSolverEnterpriseService; import ai.timefold.solver.core.impl.domain.entity.descriptor.EntityDescriptor; -import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor; import ai.timefold.solver.core.impl.domain.valuerange.descriptor.EntityIndependentValueRangeDescriptor; -import ai.timefold.solver.core.impl.domain.valuerange.descriptor.ValueRangeDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.BasicVariableDescriptor; import ai.timefold.solver.core.impl.domain.variable.descriptor.GenuineVariableDescriptor; import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy; @@ -40,7 +39,6 @@ import ai.timefold.solver.core.impl.heuristic.selector.value.decorator.UnassignedListValueSelector; import ai.timefold.solver.core.impl.heuristic.selector.value.mimic.MimicRecordingValueSelector; import ai.timefold.solver.core.impl.heuristic.selector.value.mimic.MimicReplayingValueSelector; -import ai.timefold.solver.core.impl.heuristic.selector.value.mimic.ValueMimicRecorder; import ai.timefold.solver.core.impl.solver.ClassInstanceCache; public class ValueSelectorFactory @@ -56,11 +54,12 @@ public ValueSelectorFactory(ValueSelectorConfig valueSelectorConfig) { public GenuineVariableDescriptor extractVariableDescriptor(HeuristicConfigPolicy configPolicy, EntityDescriptor entityDescriptor) { - String variableName = config.getVariableName(); + var variableName = config.getVariableName(); + var mimicSelectorRef = config.getMimicSelectorRef(); if (variableName != null) { return getVariableDescriptorForName(downcastEntityDescriptor(configPolicy, entityDescriptor), variableName); - } else if (config.getMimicSelectorRef() != null) { - return configPolicy.getValueMimicRecorder(config.getMimicSelectorRef()).getVariableDescriptor(); + } else if (mimicSelectorRef != null) { + return configPolicy.getValueMimicRecorder(mimicSelectorRef).getVariableDescriptor(); } else { return null; } @@ -86,20 +85,21 @@ public ValueSelector buildValueSelector(HeuristicConfigPolicy entityDescriptor, SelectionCacheType minimumCacheType, SelectionOrder inheritedSelectionOrder, boolean applyReinitializeVariableFiltering, ListValueFilteringType listValueFilteringType) { - GenuineVariableDescriptor variableDescriptor = deduceGenuineVariableDescriptor( - downcastEntityDescriptor(configPolicy, entityDescriptor), config.getVariableName()); + var variableDescriptor = deduceGenuineVariableDescriptor(downcastEntityDescriptor(configPolicy, entityDescriptor), + config.getVariableName()); if (config.getMimicSelectorRef() != null) { - ValueSelector valueSelector = buildMimicReplaying(configPolicy); + var valueSelector = buildMimicReplaying(configPolicy); valueSelector = applyReinitializeVariableFiltering(applyReinitializeVariableFiltering, variableDescriptor, valueSelector); valueSelector = applyDowncasting(valueSelector); return valueSelector; } - SelectionCacheType resolvedCacheType = SelectionCacheType.resolve(config.getCacheType(), minimumCacheType); - SelectionOrder resolvedSelectionOrder = SelectionOrder.resolve(config.getSelectionOrder(), inheritedSelectionOrder); + var resolvedCacheType = SelectionCacheType.resolve(config.getCacheType(), minimumCacheType); + var resolvedSelectionOrder = SelectionOrder.resolve(config.getSelectionOrder(), inheritedSelectionOrder); - if (config.getNearbySelectionConfig() != null) { - config.getNearbySelectionConfig().validateNearby(resolvedCacheType, resolvedSelectionOrder); + var nearbySelectionConfig = config.getNearbySelectionConfig(); + if (nearbySelectionConfig != null) { + nearbySelectionConfig.validateNearby(resolvedCacheType, resolvedSelectionOrder); } validateCacheTypeVersusSelectionOrder(resolvedCacheType, resolvedSelectionOrder); validateSorting(resolvedSelectionOrder); @@ -107,16 +107,16 @@ public ValueSelector buildValueSelector(HeuristicConfigPolicy valueSelector = + var valueSelector = buildBaseValueSelector(variableDescriptor, SelectionCacheType.max(minimumCacheType, resolvedCacheType), determineBaseRandomSelection(variableDescriptor, resolvedCacheType, resolvedSelectionOrder)); - if (config.getNearbySelectionConfig() != null) { + if (nearbySelectionConfig != null) { // TODO Static filtering (such as movableEntitySelectionFilter) should affect nearbySelection too valueSelector = applyNearbySelection(configPolicy, entityDescriptor, minimumCacheType, resolvedSelectionOrder, valueSelector); } - ClassInstanceCache instanceCache = configPolicy.getClassInstanceCache(); + var instanceCache = configPolicy.getClassInstanceCache(); valueSelector = applyFiltering(valueSelector, instanceCache); valueSelector = applyInitializedChainedValueFilter(configPolicy, variableDescriptor, valueSelector); valueSelector = applySorting(resolvedCacheType, resolvedSelectionOrder, valueSelector, instanceCache); @@ -146,38 +146,38 @@ protected ValueSelector buildMimicReplaying(HeuristicConfigPolicy valueMimicRecorder = configPolicy.getValueMimicRecorder(config.getMimicSelectorRef()); + var valueMimicRecorder = configPolicy.getValueMimicRecorder(config.getMimicSelectorRef()); if (valueMimicRecorder == null) { - throw new IllegalArgumentException("The valueSelectorConfig (" + config - + ") has a mimicSelectorRef (" + config.getMimicSelectorRef() - + ") for which no valueSelector with that id exists (in its solver phase)."); + throw new IllegalArgumentException( + "The valueSelectorConfig (%s) has a mimicSelectorRef (%s) for which no valueSelector with that id exists (in its solver phase)." + .formatted(config, config.getMimicSelectorRef())); } return new MimicReplayingValueSelector<>(valueMimicRecorder); } protected EntityDescriptor downcastEntityDescriptor(HeuristicConfigPolicy configPolicy, EntityDescriptor entityDescriptor) { - if (config.getDowncastEntityClass() != null) { - Class parentEntityClass = entityDescriptor.getEntityClass(); - if (!parentEntityClass.isAssignableFrom(config.getDowncastEntityClass())) { - throw new IllegalStateException("The downcastEntityClass (" + config.getDowncastEntityClass() - + ") is not a subclass of the parentEntityClass (" + parentEntityClass - + ") configured by the " + EntitySelector.class.getSimpleName() + "."); + var downcastEntityClass = config.getDowncastEntityClass(); + if (downcastEntityClass != null) { + var parentEntityClass = entityDescriptor.getEntityClass(); + if (!parentEntityClass.isAssignableFrom(downcastEntityClass)) { + throw new IllegalStateException( + "The downcastEntityClass (%s) is not a subclass of the parentEntityClass (%s) configured by the %s." + .formatted(downcastEntityClass, parentEntityClass, EntitySelector.class.getSimpleName())); } - SolutionDescriptor solutionDescriptor = configPolicy.getSolutionDescriptor(); - entityDescriptor = solutionDescriptor.getEntityDescriptorStrict(config.getDowncastEntityClass()); + var solutionDescriptor = configPolicy.getSolutionDescriptor(); + entityDescriptor = solutionDescriptor.getEntityDescriptorStrict(downcastEntityClass); if (entityDescriptor == null) { - throw new IllegalArgumentException("The selectorConfig (" + config - + ") has an downcastEntityClass (" + config.getDowncastEntityClass() - + ") that is not a known planning entity.\n" - + "Check your solver configuration. If that class (" + config.getDowncastEntityClass().getSimpleName() - + ") is not in the entityClassSet (" + solutionDescriptor.getEntityClassSet() - + "), check your @" + PlanningSolution.class.getSimpleName() - + " implementation's annotated methods too."); + throw new IllegalArgumentException(""" + The selectorConfig (%s) has an downcastEntityClass (%s) that is not a known planning entity. + Check your solver configuration. If that class (%s) is not in the entityClassSet (%s), \ + check your @%s implementation's annotated methods too.""" + .formatted(config, downcastEntityClass, downcastEntityClass.getSimpleName(), + solutionDescriptor.getEntityClassSet(), PlanningSolution.class.getSimpleName())); } } return entityDescriptor; @@ -185,22 +185,17 @@ protected EntityDescriptor downcastEntityDescriptor(HeuristicConfigPo protected boolean determineBaseRandomSelection(GenuineVariableDescriptor variableDescriptor, SelectionCacheType resolvedCacheType, SelectionOrder resolvedSelectionOrder) { - switch (resolvedSelectionOrder) { - case ORIGINAL: - return false; - case SORTED: - case SHUFFLED: - case PROBABILISTIC: + return switch (resolvedSelectionOrder) { + case ORIGINAL, SORTED, SHUFFLED, PROBABILISTIC -> // baseValueSelector and lower should be ORIGINAL if they are going to get cached completely - return false; - case RANDOM: + false; + case RANDOM -> // Predict if caching will occur - return resolvedCacheType.isNotCached() + resolvedCacheType.isNotCached() || (isBaseInherentlyCached(variableDescriptor) && !hasFiltering(variableDescriptor)); - default: - throw new IllegalStateException("The selectionOrder (" + resolvedSelectionOrder - + ") is not implemented."); - } + default -> throw new IllegalStateException("The selectionOrder (" + resolvedSelectionOrder + + ") is not implemented."); + }; } protected boolean isBaseInherentlyCached(GenuineVariableDescriptor variableDescriptor) { @@ -209,7 +204,7 @@ protected boolean isBaseInherentlyCached(GenuineVariableDescriptor va private ValueSelector buildBaseValueSelector(GenuineVariableDescriptor variableDescriptor, SelectionCacheType minimumCacheType, boolean randomSelection) { - ValueRangeDescriptor valueRangeDescriptor = variableDescriptor.getValueRangeDescriptor(); + var valueRangeDescriptor = variableDescriptor.getValueRangeDescriptor(); // TODO minimumCacheType SOLVER is only a problem if the valueRange includes entities or custom weird cloning if (minimumCacheType == SelectionCacheType.SOLVER) { // TODO Solver cached entities are not compatible with ConstraintStreams and IncrementalScoreDirector @@ -235,7 +230,7 @@ private boolean hasFiltering(GenuineVariableDescriptor variableDescri protected ValueSelector applyFiltering(ValueSelector valueSelector, ClassInstanceCache instanceCache) { - GenuineVariableDescriptor variableDescriptor = valueSelector.getVariableDescriptor(); + var variableDescriptor = valueSelector.getVariableDescriptor(); if (hasFiltering(variableDescriptor)) { List> filterList = new ArrayList<>(config.getFilterClass() == null ? 1 : 2); if (config.getFilterClass() != null) { @@ -253,7 +248,7 @@ protected ValueSelector applyFiltering(ValueSelector value protected ValueSelector applyInitializedChainedValueFilter(HeuristicConfigPolicy configPolicy, GenuineVariableDescriptor variableDescriptor, ValueSelector valueSelector) { - boolean isChained = variableDescriptor instanceof BasicVariableDescriptor basicVariableDescriptor + var isChained = variableDescriptor instanceof BasicVariableDescriptor basicVariableDescriptor && basicVariableDescriptor.isChained(); if (configPolicy.isInitializedChainedValueFilterEnabled() && isChained) { valueSelector = InitializedValueSelector.create(valueSelector); @@ -262,58 +257,50 @@ protected ValueSelector applyInitializedChainedValueFilter(HeuristicC } protected void validateSorting(SelectionOrder resolvedSelectionOrder) { - if ((config.getSorterManner() != null || config.getSorterComparatorClass() != null + var sorterManner = config.getSorterManner(); + if ((sorterManner != null || config.getSorterComparatorClass() != null || config.getSorterWeightFactoryClass() != null || config.getSorterOrder() != null || config.getSorterClass() != null) && resolvedSelectionOrder != SelectionOrder.SORTED) { - throw new IllegalArgumentException("The valueSelectorConfig (" + config - + ") with sorterManner (" + config.getSorterManner() - + ") and sorterComparatorClass (" + config.getSorterComparatorClass() - + ") and sorterWeightFactoryClass (" + config.getSorterWeightFactoryClass() - + ") and sorterOrder (" + config.getSorterOrder() - + ") and sorterClass (" + config.getSorterClass() - + ") has a resolvedSelectionOrder (" + resolvedSelectionOrder - + ") that is not " + SelectionOrder.SORTED + "."); - } - if (config.getSorterManner() != null && config.getSorterComparatorClass() != null) { - throw new IllegalArgumentException("The valueSelectorConfig (" + config - + ") has both a sorterManner (" + config.getSorterManner() - + ") and a sorterComparatorClass (" + config.getSorterComparatorClass() + ")."); - } - if (config.getSorterManner() != null && config.getSorterWeightFactoryClass() != null) { - throw new IllegalArgumentException("The valueSelectorConfig (" + config - + ") has both a sorterManner (" + config.getSorterManner() - + ") and a sorterWeightFactoryClass (" + config.getSorterWeightFactoryClass() + ")."); - } - if (config.getSorterManner() != null && config.getSorterClass() != null) { - throw new IllegalArgumentException("The valueSelectorConfig (" + config - + ") has both a sorterManner (" + config.getSorterManner() - + ") and a sorterClass (" + config.getSorterClass() + ")."); - } - if (config.getSorterManner() != null && config.getSorterOrder() != null) { - throw new IllegalArgumentException("The valueSelectorConfig (" + config - + ") with sorterManner (" + config.getSorterManner() - + ") has a non-null sorterOrder (" + config.getSorterOrder() + ")."); - } + throw new IllegalArgumentException(""" + The valueSelectorConfig (%s) with sorterManner (%s) \ + and sorterComparatorClass (%s) and sorterWeightFactoryClass (%s) and sorterOrder (%s) and sorterClass (%s) \ + has a resolvedSelectionOrder (%s) that is not %s.""" + .formatted(config, sorterManner, config.getSorterComparatorClass(), config.getSorterWeightFactoryClass(), + config.getSorterOrder(), config.getSorterClass(), resolvedSelectionOrder, SelectionOrder.SORTED)); + } + assertNotSorterMannerAnd(config, "sorterComparatorClass", ValueSelectorConfig::getSorterComparatorClass); + assertNotSorterMannerAnd(config, "sorterWeightFactoryClass", ValueSelectorConfig::getSorterWeightFactoryClass); + assertNotSorterMannerAnd(config, "sorterClass", ValueSelectorConfig::getSorterClass); + assertNotSorterMannerAnd(config, "sorterOrder", ValueSelectorConfig::getSorterOrder); + assertNotSorterClassAnd(config, "sorterComparatorClass", ValueSelectorConfig::getSorterComparatorClass); + assertNotSorterClassAnd(config, "sorterWeightFactoryClass", ValueSelectorConfig::getSorterWeightFactoryClass); + assertNotSorterClassAnd(config, "sorterOrder", ValueSelectorConfig::getSorterOrder); if (config.getSorterComparatorClass() != null && config.getSorterWeightFactoryClass() != null) { - throw new IllegalArgumentException("The valueSelectorConfig (" + config - + ") has both a sorterComparatorClass (" + config.getSorterComparatorClass() - + ") and a sorterWeightFactoryClass (" + config.getSorterWeightFactoryClass() + ")."); - } - if (config.getSorterComparatorClass() != null && config.getSorterClass() != null) { - throw new IllegalArgumentException("The valueSelectorConfig (" + config - + ") has both a sorterComparatorClass (" + config.getSorterComparatorClass() - + ") and a sorterClass (" + config.getSorterClass() + ")."); + throw new IllegalArgumentException( + "The valueSelectorConfig (%s) has both a sorterComparatorClass (%s) and a sorterWeightFactoryClass (%s)." + .formatted(config, config.getSorterComparatorClass(), config.getSorterWeightFactoryClass())); } - if (config.getSorterWeightFactoryClass() != null && config.getSorterClass() != null) { - throw new IllegalArgumentException("The valueSelectorConfig (" + config - + ") has both a sorterWeightFactoryClass (" + config.getSorterWeightFactoryClass() - + ") and a sorterClass (" + config.getSorterClass() + ")."); + } + + private void assertNotSorterMannerAnd(ValueSelectorConfig config, String propertyName, + Function propertyAccessor) { + var sorterManner = config.getSorterManner(); + var property = propertyAccessor.apply(config); + if (sorterManner != null && property != null) { + throw new IllegalArgumentException("The entitySelectorConfig (%s) has both a sorterManner (%s) and a %s (%s)." + .formatted(config, sorterManner, propertyName, property)); } - if (config.getSorterClass() != null && config.getSorterOrder() != null) { - throw new IllegalArgumentException("The valueSelectorConfig (" + config - + ") with sorterClass (" + config.getSorterClass() - + ") has a non-null sorterOrder (" + config.getSorterOrder() + ")."); + } + + private void assertNotSorterClassAnd(ValueSelectorConfig config, String propertyName, + Function propertyAccessor) { + var sorterClass = config.getSorterClass(); + var property = propertyAccessor.apply(config); + if (sorterClass != null && property != null) { + throw new IllegalArgumentException( + "The entitySelectorConfig (%s) with sorterClass (%s) has a non-null %s (%s)." + .formatted(config, sorterClass, propertyName, property)); } } @@ -321,12 +308,13 @@ protected ValueSelector applySorting(SelectionCacheType resolvedCache ValueSelector valueSelector, ClassInstanceCache instanceCache) { if (resolvedSelectionOrder == SelectionOrder.SORTED) { SelectionSorter sorter; - if (config.getSorterManner() != null) { - GenuineVariableDescriptor variableDescriptor = valueSelector.getVariableDescriptor(); - if (!ValueSelectorConfig.hasSorter(config.getSorterManner(), variableDescriptor)) { + var sorterManner = config.getSorterManner(); + if (sorterManner != null) { + var variableDescriptor = valueSelector.getVariableDescriptor(); + if (!ValueSelectorConfig.hasSorter(sorterManner, variableDescriptor)) { return valueSelector; } - sorter = ValueSelectorConfig.determineSorter(config.getSorterManner(), variableDescriptor); + sorter = ValueSelectorConfig.determineSorter(sorterManner, variableDescriptor); } else if (config.getSorterComparatorClass() != null) { Comparator sorterComparator = instanceCache.newInstance(config, "sorterComparatorClass", config.getSorterComparatorClass()); @@ -342,7 +330,7 @@ protected ValueSelector applySorting(SelectionCacheType resolvedCache } else { throw new IllegalArgumentException("The valueSelectorConfig (" + config + ") with resolvedSelectionOrder (" + resolvedSelectionOrder - + ") needs a sorterManner (" + config.getSorterManner() + + ") needs a sorterManner (" + sorterManner + ") or a sorterComparatorClass (" + config.getSorterComparatorClass() + ") or a sorterWeightFactoryClass (" + config.getSorterWeightFactoryClass() + ") or a sorterClass (" + config.getSorterClass() + ")."); @@ -461,21 +449,21 @@ private ValueSelector applyNearbySelection(HeuristicConfigPolicy applyMimicRecording(HeuristicConfigPolicy configPolicy, ValueSelector valueSelector) { - if (config.getId() != null) { - if (config.getId().isEmpty()) { - throw new IllegalArgumentException("The valueSelectorConfig (" + config - + ") has an empty id (" + config.getId() + ")."); + var id = config.getId(); + if (id != null) { + if (id.isEmpty()) { + throw new IllegalArgumentException("The valueSelectorConfig (%s) has an empty id (%s).".formatted(config, id)); } if (!(valueSelector instanceof EntityIndependentValueSelector)) { - throw new IllegalArgumentException("The valueSelectorConfig (" + config - + ") with id (" + config.getId() - + ") needs to be based on an " - + EntityIndependentValueSelector.class.getSimpleName() + " (" + valueSelector + ")." - + " Check your @" + ValueRangeProvider.class.getSimpleName() + " annotations."); + throw new IllegalArgumentException(""" + The valueSelectorConfig (%s) with id (%s) needs to be based on an %s (%s). + Check your @%s annotations.""" + .formatted(config, id, EntityIndependentValueSelector.class.getSimpleName(), valueSelector, + ValueRangeProvider.class.getSimpleName())); } - MimicRecordingValueSelector mimicRecordingValueSelector = new MimicRecordingValueSelector<>( - (EntityIndependentValueSelector) valueSelector); - configPolicy.addValueMimicRecorder(config.getId(), mimicRecordingValueSelector); + var mimicRecordingValueSelector = + new MimicRecordingValueSelector<>((EntityIndependentValueSelector) valueSelector); + configPolicy.addValueMimicRecorder(id, mimicRecordingValueSelector); valueSelector = mimicRecordingValueSelector; } return valueSelector; diff --git a/core/src/main/java/ai/timefold/solver/core/impl/localsearch/DefaultLocalSearchPhaseFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/localsearch/DefaultLocalSearchPhaseFactory.java index 3f82dbf0e3..1c8c3e6dde 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/localsearch/DefaultLocalSearchPhaseFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/localsearch/DefaultLocalSearchPhaseFactory.java @@ -95,18 +95,18 @@ private LocalSearchDecider buildDecider(HeuristicConfigPolicy buildAcceptor(HeuristicConfigPolicy configPolicy) { - LocalSearchAcceptorConfig acceptorConfig_; - if (phaseConfig.getAcceptorConfig() != null) { - if (phaseConfig.getLocalSearchType() != null) { - throw new IllegalArgumentException("The localSearchType (" + phaseConfig.getLocalSearchType() - + ") must not be configured if the acceptorConfig (" + phaseConfig.getAcceptorConfig() - + ") is explicitly configured."); + var acceptorConfig = phaseConfig.getAcceptorConfig(); + var localSearchType = phaseConfig.getLocalSearchType(); + if (acceptorConfig != null) { + if (localSearchType != null) { + throw new IllegalArgumentException( + "The localSearchType (%s) must not be configured if the acceptorConfig (%s) is explicitly configured." + .formatted(localSearchType, acceptorConfig)); } - acceptorConfig_ = phaseConfig.getAcceptorConfig(); + return buildAcceptor(acceptorConfig, configPolicy); } else { - LocalSearchType localSearchType_ = - Objects.requireNonNullElse(phaseConfig.getLocalSearchType(), LocalSearchType.LATE_ACCEPTANCE); - acceptorConfig_ = new LocalSearchAcceptorConfig(); + var localSearchType_ = Objects.requireNonNullElse(localSearchType, LocalSearchType.LATE_ACCEPTANCE); + var acceptorConfig_ = new LocalSearchAcceptorConfig(); switch (localSearchType_) { case HILL_CLIMBING: case VARIABLE_NEIGHBORHOOD_DESCENT: @@ -128,8 +128,13 @@ protected Acceptor buildAcceptor(HeuristicConfigPolicy con throw new IllegalStateException("The localSearchType (" + localSearchType_ + ") is not implemented."); } + return buildAcceptor(acceptorConfig_, configPolicy); } - return AcceptorFactory. create(acceptorConfig_) + } + + private Acceptor buildAcceptor(LocalSearchAcceptorConfig acceptorConfig, + HeuristicConfigPolicy configPolicy) { + return AcceptorFactory. create(acceptorConfig) .buildAcceptor(configPolicy); } @@ -180,20 +185,19 @@ protected MoveSelector buildMoveSelector(HeuristicConfigPolicy(determineDefaultMoveSelectorConfig(configPolicy)) .buildMoveSelector(configPolicy, defaultCacheType, defaultSelectionOrder, true); } else { - AbstractMoveSelectorFactory moveSelectorFactory = - MoveSelectorFactory.create(phaseConfig.getMoveSelectorConfig()); + AbstractMoveSelectorFactory moveSelectorFactory = MoveSelectorFactory.create(moveSelectorConfig); if (configPolicy.getNearbyDistanceMeterClass() != null - && NearbyAutoConfigurationEnabled.class - .isAssignableFrom(phaseConfig.getMoveSelectorConfig().getClass()) - && !UnionMoveSelectorConfig.class.isAssignableFrom(phaseConfig.getMoveSelectorConfig().getClass())) { + && NearbyAutoConfigurationEnabled.class.isAssignableFrom(moveSelectorConfig.getClass()) + && !UnionMoveSelectorConfig.class.isAssignableFrom(moveSelectorConfig.getClass())) { // The move selector config is not a composite selector, but it accepts Nearby autoconfiguration. // We create a new UnionMoveSelectorConfig with the existing selector to enable Nearby autoconfiguration. - MoveSelectorConfig moveSelectorCopy = (MoveSelectorConfig) phaseConfig.getMoveSelectorConfig().copyConfig(); + MoveSelectorConfig moveSelectorCopy = (MoveSelectorConfig) moveSelectorConfig.copyConfig(); UnionMoveSelectorConfig updatedConfig = new UnionMoveSelectorConfig() .withMoveSelectors(moveSelectorCopy); moveSelectorFactory = MoveSelectorFactory.create(updatedConfig); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/localsearch/decider/acceptor/AcceptorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/localsearch/decider/acceptor/AcceptorFactory.java index ec8e224caf..964816af61 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/localsearch/decider/acceptor/AcceptorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/localsearch/decider/acceptor/AcceptorFactory.java @@ -64,24 +64,26 @@ public Acceptor buildAcceptor(HeuristicConfigPolicy config } private Optional> buildHillClimbingAcceptor() { - if (acceptorConfig.getAcceptorTypeList() != null - && acceptorConfig.getAcceptorTypeList().contains(AcceptorType.HILL_CLIMBING)) { - HillClimbingAcceptor acceptor = new HillClimbingAcceptor<>(); - return Optional.of(acceptor); + if (acceptorTypeListsContainsAcceptorType(AcceptorType.HILL_CLIMBING)) { + return Optional.of(new HillClimbingAcceptor<>()); } return Optional.empty(); } + private boolean acceptorTypeListsContainsAcceptorType(AcceptorType acceptorType) { + var acceptorTypeList = acceptorConfig.getAcceptorTypeList(); + return acceptorTypeList != null && acceptorTypeList.contains(acceptorType); + } + private Optional> buildStepCountingHillClimbingAcceptor() { - if ((acceptorConfig.getAcceptorTypeList() != null - && acceptorConfig.getAcceptorTypeList().contains(AcceptorType.STEP_COUNTING_HILL_CLIMBING)) + if (acceptorTypeListsContainsAcceptorType(AcceptorType.STEP_COUNTING_HILL_CLIMBING) || acceptorConfig.getStepCountingHillClimbingSize() != null) { int stepCountingHillClimbingSize_ = Objects.requireNonNullElse(acceptorConfig.getStepCountingHillClimbingSize(), 400); - StepCountingHillClimbingType stepCountingHillClimbingType_ = + var stepCountingHillClimbingType_ = Objects.requireNonNullElse(acceptorConfig.getStepCountingHillClimbingType(), StepCountingHillClimbingType.STEP); - StepCountingHillClimbingAcceptor acceptor = new StepCountingHillClimbingAcceptor<>( + var acceptor = new StepCountingHillClimbingAcceptor( stepCountingHillClimbingSize_, stepCountingHillClimbingType_); return Optional.of(acceptor); } @@ -89,11 +91,10 @@ private Optional> buildStepCountingH } private Optional> buildEntityTabuAcceptor(HeuristicConfigPolicy configPolicy) { - if ((acceptorConfig.getAcceptorTypeList() != null - && acceptorConfig.getAcceptorTypeList().contains(AcceptorType.ENTITY_TABU)) + if (acceptorTypeListsContainsAcceptorType(AcceptorType.ENTITY_TABU) || acceptorConfig.getEntityTabuSize() != null || acceptorConfig.getEntityTabuRatio() != null || acceptorConfig.getFadingEntityTabuSize() != null || acceptorConfig.getFadingEntityTabuRatio() != null) { - EntityTabuAcceptor acceptor = new EntityTabuAcceptor<>(configPolicy.getLogIndentation()); + var acceptor = new EntityTabuAcceptor(configPolicy.getLogIndentation()); if (acceptorConfig.getEntityTabuSize() != null) { if (acceptorConfig.getEntityTabuRatio() != null) { throw new IllegalArgumentException("The acceptor cannot have both acceptorConfig.getEntityTabuSize() (" @@ -128,11 +129,10 @@ private Optional> buildEntityTabuAcceptor(Heuristi } private Optional> buildValueTabuAcceptor(HeuristicConfigPolicy configPolicy) { - if ((acceptorConfig.getAcceptorTypeList() != null - && acceptorConfig.getAcceptorTypeList().contains(AcceptorType.VALUE_TABU)) + if (acceptorTypeListsContainsAcceptorType(AcceptorType.VALUE_TABU) || acceptorConfig.getValueTabuSize() != null || acceptorConfig.getValueTabuRatio() != null || acceptorConfig.getFadingValueTabuSize() != null || acceptorConfig.getFadingValueTabuRatio() != null) { - ValueTabuAcceptor acceptor = new ValueTabuAcceptor<>(configPolicy.getLogIndentation()); + var acceptor = new ValueTabuAcceptor(configPolicy.getLogIndentation()); if (acceptorConfig.getValueTabuSize() != null) { if (acceptorConfig.getValueTabuRatio() != null) { throw new IllegalArgumentException("The acceptor cannot have both acceptorConfig.getValueTabuSize() (" @@ -177,10 +177,9 @@ private Optional> buildValueTabuAcceptor(HeuristicC } private Optional> buildMoveTabuAcceptor(HeuristicConfigPolicy configPolicy) { - if ((acceptorConfig.getAcceptorTypeList() != null - && acceptorConfig.getAcceptorTypeList().contains(AcceptorType.MOVE_TABU)) + if (acceptorTypeListsContainsAcceptorType(AcceptorType.MOVE_TABU) || acceptorConfig.getMoveTabuSize() != null || acceptorConfig.getFadingMoveTabuSize() != null) { - MoveTabuAcceptor acceptor = new MoveTabuAcceptor<>(configPolicy.getLogIndentation()); + var acceptor = new MoveTabuAcceptor(configPolicy.getLogIndentation()); if (acceptorConfig.getMoveTabuSize() != null) { acceptor.setTabuSizeStrategy(new FixedTabuSizeStrategy<>(acceptorConfig.getMoveTabuSize())); } @@ -197,10 +196,9 @@ private Optional> buildMoveTabuAcceptor(HeuristicCon private Optional> buildSimulatedAnnealingAcceptor(HeuristicConfigPolicy configPolicy) { - if ((acceptorConfig.getAcceptorTypeList() != null - && acceptorConfig.getAcceptorTypeList().contains(AcceptorType.SIMULATED_ANNEALING)) + if (acceptorTypeListsContainsAcceptorType(AcceptorType.SIMULATED_ANNEALING) || acceptorConfig.getSimulatedAnnealingStartingTemperature() != null) { - SimulatedAnnealingAcceptor acceptor = new SimulatedAnnealingAcceptor<>(); + var acceptor = new SimulatedAnnealingAcceptor(); if (acceptorConfig.getSimulatedAnnealingStartingTemperature() == null) { // TODO Support SA without a parameter throw new IllegalArgumentException("The acceptorType (" + AcceptorType.SIMULATED_ANNEALING @@ -215,10 +213,9 @@ private Optional> buildMoveTabuAcceptor(HeuristicCon } private Optional> buildLateAcceptanceAcceptor() { - if ((acceptorConfig.getAcceptorTypeList() != null - && acceptorConfig.getAcceptorTypeList().contains(AcceptorType.LATE_ACCEPTANCE)) + if (acceptorTypeListsContainsAcceptorType(AcceptorType.LATE_ACCEPTANCE) || acceptorConfig.getLateAcceptanceSize() != null) { - LateAcceptanceAcceptor acceptor = new LateAcceptanceAcceptor<>(); + var acceptor = new LateAcceptanceAcceptor(); acceptor.setLateAcceptanceSize(Objects.requireNonNullElse(acceptorConfig.getLateAcceptanceSize(), 400)); return Optional.of(acceptor); } @@ -226,11 +223,10 @@ private Optional> buildLateAcceptanceAcceptor( } private Optional> buildGreatDelugeAcceptor(HeuristicConfigPolicy configPolicy) { - if ((acceptorConfig.getAcceptorTypeList() != null - && acceptorConfig.getAcceptorTypeList().contains(AcceptorType.GREAT_DELUGE)) + if (acceptorTypeListsContainsAcceptorType(AcceptorType.GREAT_DELUGE) || acceptorConfig.getGreatDelugeWaterLevelIncrementScore() != null || acceptorConfig.getGreatDelugeWaterLevelIncrementRatio() != null) { - GreatDelugeAcceptor acceptor = new GreatDelugeAcceptor<>(); + var acceptor = new GreatDelugeAcceptor(); if (acceptorConfig.getGreatDelugeWaterLevelIncrementScore() != null) { if (acceptorConfig.getGreatDelugeWaterLevelIncrementRatio() != null) { throw new IllegalArgumentException("The acceptor cannot have both a " diff --git a/core/src/main/java/ai/timefold/solver/core/impl/localsearch/decider/forager/LocalSearchForagerFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/localsearch/decider/forager/LocalSearchForagerFactory.java index d1018fd87d..3b9081433d 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/localsearch/decider/forager/LocalSearchForagerFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/localsearch/decider/forager/LocalSearchForagerFactory.java @@ -19,10 +19,9 @@ public LocalSearchForagerFactory(LocalSearchForagerConfig foragerConfig) { } public LocalSearchForager buildForager() { - LocalSearchPickEarlyType pickEarlyType_ = - Objects.requireNonNullElse(foragerConfig.getPickEarlyType(), LocalSearchPickEarlyType.NEVER); - int acceptedCountLimit_ = Objects.requireNonNullElse(foragerConfig.getAcceptedCountLimit(), Integer.MAX_VALUE); - FinalistPodiumType finalistPodiumType_ = + var pickEarlyType_ = Objects.requireNonNullElse(foragerConfig.getPickEarlyType(), LocalSearchPickEarlyType.NEVER); + var acceptedCountLimit_ = Objects.requireNonNullElse(foragerConfig.getAcceptedCountLimit(), Integer.MAX_VALUE); + var finalistPodiumType_ = Objects.requireNonNullElse(foragerConfig.getFinalistPodiumType(), FinalistPodiumType.HIGHEST_SCORE); // Breaking ties randomly leads to better results statistically boolean breakTieRandomly_ = Objects.requireNonNullElse(foragerConfig.getBreakTieRandomly(), true); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/phase/custom/DefaultCustomPhaseFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/phase/custom/DefaultCustomPhaseFactory.java index bc503280d4..a164532ee9 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/phase/custom/DefaultCustomPhaseFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/phase/custom/DefaultCustomPhaseFactory.java @@ -2,10 +2,8 @@ import java.util.ArrayList; import java.util.Collection; -import java.util.List; import ai.timefold.solver.core.config.phase.custom.CustomPhaseConfig; -import ai.timefold.solver.core.config.solver.EnvironmentMode; import ai.timefold.solver.core.config.util.ConfigUtils; import ai.timefold.solver.core.impl.heuristic.HeuristicConfigPolicy; import ai.timefold.solver.core.impl.phase.AbstractPhaseFactory; @@ -22,32 +20,34 @@ public DefaultCustomPhaseFactory(CustomPhaseConfig phaseConfig) { public CustomPhase buildPhase(int phaseIndex, boolean triggerFirstInitializedSolutionEvent, HeuristicConfigPolicy solverConfigPolicy, BestSolutionRecaller bestSolutionRecaller, Termination solverTermination) { - HeuristicConfigPolicy phaseConfigPolicy = solverConfigPolicy.createPhaseConfigPolicy(); - if (ConfigUtils.isEmptyCollection(phaseConfig.getCustomPhaseCommandClassList()) - && ConfigUtils.isEmptyCollection(phaseConfig.getCustomPhaseCommandList())) { + var phaseConfigPolicy = solverConfigPolicy.createPhaseConfigPolicy(); + var customPhaseCommandClassList = phaseConfig.getCustomPhaseCommandClassList(); + var customPhaseCommandList = phaseConfig.getCustomPhaseCommandList(); + if (ConfigUtils.isEmptyCollection(customPhaseCommandClassList) + && ConfigUtils.isEmptyCollection(customPhaseCommandList)) { throw new IllegalArgumentException( "Configure at least 1 in the configuration."); } - List> customPhaseCommandList_ = new ArrayList<>(getCustomPhaseCommandListSize()); - if (phaseConfig.getCustomPhaseCommandClassList() != null) { - for (Class customPhaseCommandClass : phaseConfig.getCustomPhaseCommandClassList()) { + var customPhaseCommandList_ = new ArrayList>(getCustomPhaseCommandListSize()); + if (customPhaseCommandClassList != null) { + for (var customPhaseCommandClass : customPhaseCommandClassList) { if (customPhaseCommandClass == null) { - throw new IllegalArgumentException("The customPhaseCommandClass (" + customPhaseCommandClass - + ") cannot be null in the customPhase (" + phaseConfig + ").\n" + - "Maybe there was a typo in the class name provided in the solver config XML?"); + throw new IllegalArgumentException(""" + The customPhaseCommandClass (%s) cannot be null in the customPhase (%s). + Maybe there was a typo in the class name provided in the solver config XML?""" + .formatted(customPhaseCommandClass, phaseConfig)); } customPhaseCommandList_.add(createCustomPhaseCommand(customPhaseCommandClass)); } } - if (phaseConfig.getCustomPhaseCommandList() != null) { - customPhaseCommandList_.addAll((Collection) phaseConfig.getCustomPhaseCommandList()); + if (customPhaseCommandList != null) { + customPhaseCommandList_.addAll((Collection) customPhaseCommandList); } - DefaultCustomPhase.Builder builder = - new DefaultCustomPhase.Builder<>(phaseIndex, triggerFirstInitializedSolutionEvent, - solverConfigPolicy.getLogIndentation(), - buildPhaseTermination(phaseConfigPolicy, solverTermination), customPhaseCommandList_); - EnvironmentMode environmentMode = phaseConfigPolicy.getEnvironmentMode(); + var builder = new DefaultCustomPhase.Builder<>(phaseIndex, triggerFirstInitializedSolutionEvent, + solverConfigPolicy.getLogIndentation(), buildPhaseTermination(phaseConfigPolicy, solverTermination), + customPhaseCommandList_); + var environmentMode = phaseConfigPolicy.getEnvironmentMode(); if (environmentMode.isNonIntrusiveFullAsserted()) { builder.setAssertStepScoreFromScratch(true); } @@ -64,7 +64,9 @@ public CustomPhase buildPhase(int phaseIndex, boolean triggerFirstIni } private int getCustomPhaseCommandListSize() { - return (phaseConfig.getCustomPhaseCommandClassList() == null ? 0 : phaseConfig.getCustomPhaseCommandClassList().size()) - + (phaseConfig.getCustomPhaseCommandList() == null ? 0 : phaseConfig.getCustomPhaseCommandList().size()); + var customPhaseCommandClassList = phaseConfig.getCustomPhaseCommandClassList(); + var customPhaseCommandList = phaseConfig.getCustomPhaseCommandList(); + return (customPhaseCommandClassList == null ? 0 : customPhaseCommandClassList.size()) + + (customPhaseCommandList == null ? 0 : customPhaseCommandList.size()); } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/ScoreDirectorFactoryFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/ScoreDirectorFactoryFactory.java index eeff393036..b3423d75d4 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/ScoreDirectorFactoryFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/ScoreDirectorFactoryFactory.java @@ -24,19 +24,21 @@ public ScoreDirectorFactoryFactory(ScoreDirectorFactoryConfig config) { public InnerScoreDirectorFactory buildScoreDirectorFactory(EnvironmentMode environmentMode, SolutionDescriptor solutionDescriptor) { var scoreDirectorFactory = decideMultipleScoreDirectorFactories(solutionDescriptor, environmentMode); - if (config.getAssertionScoreDirectorFactory() != null) { - if (config.getAssertionScoreDirectorFactory().getAssertionScoreDirectorFactory() != null) { - throw new IllegalArgumentException("A assertionScoreDirectorFactory (" - + config.getAssertionScoreDirectorFactory() + ") cannot have a non-null assertionScoreDirectorFactory (" - + config.getAssertionScoreDirectorFactory().getAssertionScoreDirectorFactory() + ")."); + var assertionScoreDirectorFactory = config.getAssertionScoreDirectorFactory(); + if (assertionScoreDirectorFactory != null) { + if (assertionScoreDirectorFactory.getAssertionScoreDirectorFactory() != null) { + throw new IllegalArgumentException( + "A assertionScoreDirectorFactory (%s) cannot have a non-null assertionScoreDirectorFactory (%s)." + .formatted(assertionScoreDirectorFactory, + assertionScoreDirectorFactory.getAssertionScoreDirectorFactory())); } if (environmentMode.compareTo(EnvironmentMode.FAST_ASSERT) > 0) { - throw new IllegalArgumentException("A non-null assertionScoreDirectorFactory (" - + config.getAssertionScoreDirectorFactory() + ") requires an environmentMode (" - + environmentMode + ") of " + EnvironmentMode.FAST_ASSERT + " or lower."); + throw new IllegalArgumentException( + "A non-null assertionScoreDirectorFactory (%s) requires an environmentMode (%s) of %s or lower." + .formatted(assertionScoreDirectorFactory, environmentMode, EnvironmentMode.FAST_ASSERT)); } var assertionScoreDirectorFactoryFactory = - new ScoreDirectorFactoryFactory(config.getAssertionScoreDirectorFactory()); + new ScoreDirectorFactoryFactory(assertionScoreDirectorFactory); scoreDirectorFactory.setAssertionScoreDirectorFactory(assertionScoreDirectorFactoryFactory .buildScoreDirectorFactory(EnvironmentMode.NON_REPRODUCIBLE, solutionDescriptor)); } @@ -80,40 +82,42 @@ DRL constraints requested via scoreDrlList (%s), but this is no longer supported } private static void assertCorrectDirectorFactory(ScoreDirectorFactoryConfig config) { - var hasEasyScoreCalculator = config.getEasyScoreCalculatorClass() != null; + var easyScoreCalculatorClass = config.getEasyScoreCalculatorClass(); + var hasEasyScoreCalculator = easyScoreCalculatorClass != null; if (!hasEasyScoreCalculator && config.getEasyScoreCalculatorCustomProperties() != null) { throw new IllegalStateException( "If there is no easyScoreCalculatorClass (%s), then there can be no easyScoreCalculatorCustomProperties (%s) either." - .formatted(config.getEasyScoreCalculatorClass(), config.getEasyScoreCalculatorCustomProperties())); + .formatted(easyScoreCalculatorClass, config.getEasyScoreCalculatorCustomProperties())); } - var hasIncrementalScoreCalculator = config.getIncrementalScoreCalculatorClass() != null; + var incrementalScoreCalculatorClass = config.getIncrementalScoreCalculatorClass(); + var hasIncrementalScoreCalculator = incrementalScoreCalculatorClass != null; if (!hasIncrementalScoreCalculator && config.getIncrementalScoreCalculatorCustomProperties() != null) { throw new IllegalStateException( "If there is no incrementalScoreCalculatorClass (%s), then there can be no incrementalScoreCalculatorCustomProperties (%s) either." - .formatted(config.getIncrementalScoreCalculatorClass(), + .formatted(incrementalScoreCalculatorClass, config.getIncrementalScoreCalculatorCustomProperties())); } - var hasConstraintProvider = config.getConstraintProviderClass() != null; + var constraintProviderClass = config.getConstraintProviderClass(); + var hasConstraintProvider = constraintProviderClass != null; if (!hasConstraintProvider && config.getConstraintProviderCustomProperties() != null) { throw new IllegalStateException( "If there is no constraintProviderClass (%s), then there can be no constraintProviderCustomProperties (%s) either." - .formatted(config.getConstraintProviderClass(), - config.getConstraintProviderCustomProperties())); + .formatted(constraintProviderClass, config.getConstraintProviderCustomProperties())); } if (hasEasyScoreCalculator && (hasIncrementalScoreCalculator || hasConstraintProvider) || (hasIncrementalScoreCalculator && hasConstraintProvider)) { var scoreDirectorFactoryPropertyList = new ArrayList(3); if (hasEasyScoreCalculator) { scoreDirectorFactoryPropertyList - .add("an easyScoreCalculatorClass (%s)".formatted(config.getEasyScoreCalculatorClass().getName())); + .add("an easyScoreCalculatorClass (%s)".formatted(easyScoreCalculatorClass.getName())); } if (hasConstraintProvider) { scoreDirectorFactoryPropertyList - .add("an constraintProviderClass (%s)".formatted(config.getConstraintProviderClass().getName())); + .add("an constraintProviderClass (%s)".formatted(constraintProviderClass.getName())); } if (hasIncrementalScoreCalculator) { scoreDirectorFactoryPropertyList.add("an incrementalScoreCalculatorClass (%s)" - .formatted(config.getIncrementalScoreCalculatorClass().getName())); + .formatted(incrementalScoreCalculatorClass.getName())); } var joined = String.join(" and ", scoreDirectorFactoryPropertyList); throw new IllegalArgumentException("The scoreDirectorFactory cannot have %s together." diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirector.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirector.java index 071f4d24ea..c34c6abd8a 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirector.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirector.java @@ -47,14 +47,12 @@ public EasyScoreCalculator getEasyScoreCalculator() { public Score_ calculateScore() { variableListenerSupport.assertNotificationQueuesAreEmpty(); Score_ score = easyScoreCalculator.calculateScore(workingSolution); - if (score == null) { - throw new IllegalStateException("The easyScoreCalculator (" + easyScoreCalculator.getClass() - + ") must return a non-null score (" + score + ") in the method calculateScore()."); - } else if (!score.isSolutionInitialized()) { - throw new IllegalStateException("The score (" + this + ")'s initScore (" + score.initScore() - + ") should be 0.\n" - + "Maybe the score calculator (" + easyScoreCalculator.getClass() + ") is calculating " - + "the initScore too, although it's the score director's responsibility."); + if (!score.isSolutionInitialized()) { + throw new IllegalStateException(""" + The score (%s)'s initScore (%d) should be 0. + Maybe the score calculator (%s) is calculating the initScore too, \ + although it's the score director's responsibility.""" + .formatted(this, score.initScore(), easyScoreCalculator.getClass())); } int workingInitScore = getWorkingInitScore(); if (workingInitScore != 0) { @@ -72,8 +70,8 @@ public Score_ calculateScore() { */ @Override public Map> getConstraintMatchTotalMap() { - throw new IllegalStateException(ConstraintMatch.class.getSimpleName() - + " is not supported by " + EasyScoreDirector.class.getSimpleName() + "."); + throw new IllegalStateException("%s is not supported by %s." + .formatted(ConstraintMatch.class.getSimpleName(), EasyScoreDirector.class.getSimpleName())); } /** @@ -84,8 +82,8 @@ public Map> getConstraintMatchTotalMap() { */ @Override public Map> getIndictmentMap() { - throw new IllegalStateException(ConstraintMatch.class.getSimpleName() - + " is not supported by " + EasyScoreDirector.class.getSimpleName() + "."); + throw new IllegalStateException("%s is not supported by %s." + .formatted(ConstraintMatch.class.getSimpleName(), EasyScoreDirector.class.getSimpleName())); } @Override diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirectorFactory.java index f08b423af5..0ad39fd873 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/easy/EasyScoreDirectorFactory.java @@ -23,13 +23,14 @@ public final class EasyScoreDirectorFactory> EasyScoreDirectorFactory buildScoreDirectorFactory(SolutionDescriptor solutionDescriptor, ScoreDirectorFactoryConfig config) { - if (!EasyScoreCalculator.class.isAssignableFrom(config.getEasyScoreCalculatorClass())) { + var easyScoreCalculatorClass = config.getEasyScoreCalculatorClass(); + if (easyScoreCalculatorClass == null || !EasyScoreCalculator.class.isAssignableFrom(easyScoreCalculatorClass)) { throw new IllegalArgumentException( "The easyScoreCalculatorClass (%s) does not implement %s." .formatted(config.getEasyScoreCalculatorClass(), EasyScoreCalculator.class.getSimpleName())); } - EasyScoreCalculator easyScoreCalculator = ConfigUtils.newInstance(config, - "easyScoreCalculatorClass", config.getEasyScoreCalculatorClass()); + EasyScoreCalculator easyScoreCalculator = + ConfigUtils.newInstance(config, "easyScoreCalculatorClass", easyScoreCalculatorClass); ConfigUtils.applyCustomProperties(easyScoreCalculator, "easyScoreCalculatorClass", config.getEasyScoreCalculatorCustomProperties(), "easyScoreCalculatorCustomProperties"); return new EasyScoreDirectorFactory<>(solutionDescriptor, easyScoreCalculator); diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/director/stream/BavetConstraintStreamScoreDirectorFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/score/director/stream/BavetConstraintStreamScoreDirectorFactory.java index 08c97bdf69..dee49f3a09 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/director/stream/BavetConstraintStreamScoreDirectorFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/director/stream/BavetConstraintStreamScoreDirectorFactory.java @@ -24,25 +24,28 @@ public final class BavetConstraintStreamScoreDirectorFactory> BavetConstraintStreamScoreDirectorFactory buildScoreDirectorFactory(SolutionDescriptor solutionDescriptor, ScoreDirectorFactoryConfig config, EnvironmentMode environmentMode) { - if (!ConstraintProvider.class.isAssignableFrom(config.getConstraintProviderClass())) { + var providedConstraintProviderClass = config.getConstraintProviderClass(); + if (providedConstraintProviderClass == null + || !ConstraintProvider.class.isAssignableFrom(providedConstraintProviderClass)) { throw new IllegalArgumentException( "The constraintProviderClass (%s) does not implement %s." - .formatted(config.getConstraintProviderClass(), ConstraintProvider.class.getSimpleName())); + .formatted(providedConstraintProviderClass, ConstraintProvider.class.getSimpleName())); } - var constraintProviderClass = getConstraintProviderClass(config); + var constraintProviderClass = getConstraintProviderClass(config, providedConstraintProviderClass); var constraintProvider = ConfigUtils.newInstance(config, "constraintProviderClass", constraintProviderClass); ConfigUtils.applyCustomProperties(constraintProvider, "constraintProviderClass", config.getConstraintProviderCustomProperties(), "constraintProviderCustomProperties"); return new BavetConstraintStreamScoreDirectorFactory<>(solutionDescriptor, constraintProvider, environmentMode); } - private static Class getConstraintProviderClass(ScoreDirectorFactoryConfig config) { + private static Class getConstraintProviderClass(ScoreDirectorFactoryConfig config, + Class providedConstraintProviderClass) { if (Boolean.TRUE.equals(config.getConstraintStreamAutomaticNodeSharing())) { var enterpriseService = TimefoldSolverEnterpriseService.loadOrFail(TimefoldSolverEnterpriseService.Feature.AUTOMATIC_NODE_SHARING); return enterpriseService.buildLambdaSharedConstraintProvider(config.getConstraintProviderClass()); } else { - return config.getConstraintProviderClass(); + return providedConstraintProviderClass; } } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/InnerConstraintFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/InnerConstraintFactory.java index 4a5f6137f1..f7308a8086 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/InnerConstraintFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/InnerConstraintFactory.java @@ -2,7 +2,6 @@ import static ai.timefold.solver.core.api.score.stream.Joiners.lessThan; import static java.util.stream.Collectors.groupingBy; -import static java.util.stream.Collectors.toList; import java.util.Arrays; import java.util.List; @@ -42,9 +41,9 @@ private DefaultBiJoiner buildLessThanId(Class sourceClass) { SolutionDescriptor solutionDescriptor = getSolutionDescriptor(); MemberAccessor planningIdMemberAccessor = solutionDescriptor.getPlanningIdAccessor(sourceClass); if (planningIdMemberAccessor == null) { - throw new IllegalArgumentException("The fromClass (" + sourceClass + ") has no member with a @" - + PlanningId.class.getSimpleName() + " annotation," - + " so the pairs cannot be made unique ([A,B] vs [B,A])."); + throw new IllegalArgumentException( + "The fromClass (%s) has no member with a @%s annotation, so the pairs cannot be made unique ([A,B] vs [B,A])." + .formatted(sourceClass, PlanningId.class.getSimpleName())); } Function planningIdGetter = planningIdMemberAccessor.getGetterFunction(); return (DefaultBiJoiner) lessThan(planningIdGetter); @@ -74,35 +73,38 @@ public void assertValidFromType(Class fromType) { List canonicalClassNameList = problemFactOrEntityClassSet.stream() .map(Class::getCanonicalName) .sorted() - .collect(toList()); - throw new IllegalArgumentException("Cannot use class (" + fromType.getCanonicalName() - + ") in a constraint stream as it is neither the same as, nor a superclass or superinterface of " - + "one of planning entities or problem facts.\n" - + "Ensure that all from(), join(), ifExists() and ifNotExists() building blocks only reference " - + "classes assignable from planning entities or problem facts (" + canonicalClassNameList + ") " - + "annotated on the planning solution (" + solutionDescriptor.getSolutionClass().getCanonicalName() - + ")."); + .toList(); + throw new IllegalArgumentException(""" + Cannot use class (%s) in a constraint stream as it is neither the same as, \ + nor a superclass or superinterface of one of planning entities or problem facts. + Ensure that all from(), join(), ifExists() and ifNotExists() building blocks \ + only reference classes assignable from planning entities \ + or problem facts (%s) annotated on the planning solution (%s).""" + .formatted(fromType.getCanonicalName(), canonicalClassNameList, + solutionDescriptor.getSolutionClass().getCanonicalName())); } } + @SuppressWarnings("unchecked") public List buildConstraints(ConstraintProvider constraintProvider) { - Constraint[] constraints = constraintProvider.defineConstraints(this); - if (constraints == null) { - throw new IllegalStateException("The constraintProvider class (" + constraintProvider.getClass() - + ")'s defineConstraints() must not return null.\n" - + "Maybe return an empty array instead if there are no constraints."); - } + Constraint[] constraints = Objects.requireNonNull(constraintProvider.defineConstraints(this), + () -> """ + The constraintProvider class (%s)'s defineConstraints() must not return null." + Maybe return an empty array instead if there are no constraints.""" + .formatted(constraintProvider.getClass())); if (Arrays.stream(constraints).anyMatch(Objects::isNull)) { - throw new IllegalStateException("The constraintProvider class (" + constraintProvider.getClass() - + ")'s defineConstraints() must not contain an element that is null.\n" - + "Maybe don't include any null elements in the " + Constraint.class.getSimpleName() + " array."); + throw new IllegalStateException(""" + The constraintProvider class (%s)'s defineConstraints() must not contain an element that is null. + Maybe don't include any null elements in the %s array.""" + .formatted(constraintProvider.getClass(), Constraint.class.getSimpleName())); } // Fail fast on duplicate constraint IDs. Map> constraintsPerIdMap = Arrays.stream(constraints).collect(groupingBy(Constraint::getConstraintRef)); constraintsPerIdMap.forEach((constraintRef, duplicateConstraintList) -> { if (duplicateConstraintList.size() > 1) { - throw new IllegalStateException("There are multiple constraints with the same ID (" + constraintRef + ")."); + throw new IllegalStateException("There are multiple constraints with the same ID (%s)." + .formatted(constraintRef)); } }); return Arrays.stream(constraints) diff --git a/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverFactory.java index ed8fc87578..77f1e76d20 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverFactory.java @@ -86,9 +86,10 @@ public > InnerScoreDirectorFactory(); var monitoringConfig = solverConfig.determineMetricConfig(); solverScope.setMonitoringTags(Tags.empty()); + var solverMetricList = Objects.requireNonNull(monitoringConfig.getSolverMetricList()); var metricsRequiringConstraintMatchSet = Collections. emptyList(); - if (!monitoringConfig.getSolverMetricList().isEmpty()) { - solverScope.setSolverMetricSet(EnumSet.copyOf(monitoringConfig.getSolverMetricList())); + if (!solverMetricList.isEmpty()) { + solverScope.setSolverMetricSet(EnumSet.copyOf(solverMetricList)); metricsRequiringConstraintMatchSet = solverScope.getSolverMetricSet().stream() .filter(SolverMetric::isMetricConstraintMatchBased) .filter(solverScope::isMetricEnabled) @@ -191,24 +192,24 @@ private SolutionDescriptor buildSolutionDescriptor() { } public RandomFactory buildRandomFactory(EnvironmentMode environmentMode_) { - RandomFactory randomFactory; - if (solverConfig.getRandomFactoryClass() != null) { - if (solverConfig.getRandomType() != null || solverConfig.getRandomSeed() != null) { + var randomFactoryClass = solverConfig.getRandomFactoryClass(); + if (randomFactoryClass != null) { + var randomType = solverConfig.getRandomType(); + var randomSeed = solverConfig.getRandomSeed(); + if (randomType != null || randomSeed != null) { throw new IllegalArgumentException( - "The solverConfig with randomFactoryClass (" + solverConfig.getRandomFactoryClass() - + ") has a non-null randomType (" + solverConfig.getRandomType() - + ") or a non-null randomSeed (" + solverConfig.getRandomSeed() + ")."); + "The solverConfig with randomFactoryClass (%s) has a non-null randomType (%s) or a non-null randomSeed (%s)." + .formatted(randomFactoryClass, randomType, randomSeed)); } - randomFactory = ConfigUtils.newInstance(solverConfig, "randomFactoryClass", solverConfig.getRandomFactoryClass()); + return ConfigUtils.newInstance(solverConfig, "randomFactoryClass", randomFactoryClass); } else { - RandomType randomType_ = Objects.requireNonNullElse(solverConfig.getRandomType(), RandomType.JDK); - Long randomSeed_ = solverConfig.getRandomSeed(); + var randomType_ = Objects.requireNonNullElse(solverConfig.getRandomType(), RandomType.JDK); + var randomSeed_ = solverConfig.getRandomSeed(); if (solverConfig.getRandomSeed() == null && environmentMode_ != EnvironmentMode.NON_REPRODUCIBLE) { randomSeed_ = DEFAULT_RANDOM_SEED; } - randomFactory = new DefaultRandomFactory(randomType_, randomSeed_); + return new DefaultRandomFactory(randomType_, randomSeed_); } - return randomFactory; } public List> buildPhaseList(HeuristicConfigPolicy configPolicy, 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 d8f0567ba1..d8b7bdaf4b 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 @@ -42,18 +42,15 @@ public final class DefaultSolverManager implements Solver private final ExecutorService solverThreadPool; private final ConcurrentMap> problemIdToSolverJobMap; - public DefaultSolverManager(SolverFactory solverFactory, - SolverManagerConfig solverManagerConfig) { - defaultExceptionHandler = (problemId, throwable) -> LOGGER.error( - "Solving failed for problemId ({}).", problemId, throwable); + public DefaultSolverManager(SolverFactory solverFactory, SolverManagerConfig solverManagerConfig) { + this.defaultExceptionHandler = + (problemId, throwable) -> LOGGER.error("Solving failed for problemId ({}).", problemId, throwable); this.solverFactory = solverFactory; validateSolverFactory(); - int parallelSolverCount = solverManagerConfig.resolveParallelSolverCount(); - var threadFactory = Executors.defaultThreadFactory(); - if (solverManagerConfig.getThreadFactoryClass() != null) { - threadFactory = ConfigUtils.newInstance(solverManagerConfig, "threadFactoryClass", - solverManagerConfig.getThreadFactoryClass()); - } + var parallelSolverCount = solverManagerConfig.resolveParallelSolverCount(); + var threadFactoryClass = solverManagerConfig.getThreadFactoryClass(); + var threadFactory = threadFactoryClass == null ? Executors.defaultThreadFactory() + : ConfigUtils.newInstance(solverManagerConfig, "threadFactoryClass", threadFactoryClass); solverThreadPool = Executors.newFixedThreadPool(parallelSolverCount, threadFactory); problemIdToSolverJobMap = new ConcurrentHashMap<>(parallelSolverCount * 10); } diff --git a/core/src/main/java/ai/timefold/solver/core/impl/solver/termination/TerminationFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/solver/termination/TerminationFactory.java index d38f46bb69..02922575fb 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/solver/termination/TerminationFactory.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/solver/termination/TerminationFactory.java @@ -57,18 +57,20 @@ public > Termination buildTermination( Arrays.fill(timeGradientWeightNumbers, 0.50); // Number pulled out of thin air terminationList.add(new BestScoreTermination<>(scoreDefinition, bestScoreLimit_, timeGradientWeightNumbers)); } - if (terminationConfig.getBestScoreFeasible() != null) { + var bestScoreFeasible = terminationConfig.getBestScoreFeasible(); + if (bestScoreFeasible != null) { ScoreDefinition scoreDefinition = configPolicy.getScoreDefinition(); - if (!terminationConfig.getBestScoreFeasible()) { - throw new IllegalArgumentException("The termination bestScoreFeasible (" - + terminationConfig.getBestScoreFeasible() + ") cannot be false."); + if (!bestScoreFeasible) { + throw new IllegalArgumentException("The termination bestScoreFeasible (%s) cannot be false." + .formatted(bestScoreFeasible)); } int feasibleLevelsSize = scoreDefinition.getFeasibleLevelsSize(); if (feasibleLevelsSize < 1) { - throw new IllegalStateException("The termination with bestScoreFeasible (" - + terminationConfig.getBestScoreFeasible() - + ") can only be used with a score type that has at least 1 feasible level but the scoreDefinition (" - + scoreDefinition + ") has feasibleLevelsSize (" + feasibleLevelsSize + "), which is less than 1."); + throw new IllegalStateException(""" + The termination with bestScoreFeasible (%s) can only be used with a score type \ + that has at least 1 feasible level but the scoreDefinition (%s) has feasibleLevelsSize (%s), \ + which is less than 1.""" + .formatted(bestScoreFeasible, scoreDefinition, feasibleLevelsSize)); } double[] timeGradientWeightFeasibleNumbers = new double[feasibleLevelsSize - 1]; Arrays.fill(timeGradientWeightFeasibleNumbers, 0.50); // Number pulled out of thin air @@ -124,11 +126,12 @@ public > Termination buildTermination( } protected List> buildInnerTermination(HeuristicConfigPolicy configPolicy) { - if (ConfigUtils.isEmptyCollection(terminationConfig.getTerminationConfigList())) { + var terminationConfigList = terminationConfig.getTerminationConfigList(); + if (ConfigUtils.isEmptyCollection(terminationConfigList)) { return Collections.emptyList(); } - return terminationConfig.getTerminationConfigList().stream() + return terminationConfigList.stream() .map(config -> TerminationFactory. create(config) .buildTermination(configPolicy)) .filter(Objects::nonNull) diff --git a/migration/src/main/java/ai/timefold/solver/migration/v8/AsConstraintRecipe.java b/migration/src/main/java/ai/timefold/solver/migration/v8/AsConstraintRecipe.java index e2dec93291..a0bff622f8 100644 --- a/migration/src/main/java/ai/timefold/solver/migration/v8/AsConstraintRecipe.java +++ b/migration/src/main/java/ai/timefold/solver/migration/v8/AsConstraintRecipe.java @@ -1,6 +1,7 @@ package ai.timefold.solver.migration.v8; import java.util.Arrays; +import java.util.Objects; import java.util.regex.Pattern; import ai.timefold.solver.migration.AbstractRecipe; @@ -225,17 +226,18 @@ public J.MethodInvocation visitMethodInvocation(J.MethodInvocation originalMetho if (matcherMeta == null) { return method; } - var select = method.getSelect(); + var select = Objects.requireNonNull(method.getSelect()); var arguments = method.getArguments(); String templateCode; - if (select.getType().isAssignableFrom(uniConstraintStreamPattern)) { + var selectType = Objects.requireNonNull(select.getType()); + if (selectType.isAssignableFrom(uniConstraintStreamPattern)) { templateCode = "#{any(ai.timefold.solver.core.api.score.stream.uni.UniConstraintStream)}\n"; - } else if (select.getType().isAssignableFrom(biConstraintStreamPattern)) { + } else if (selectType.isAssignableFrom(biConstraintStreamPattern)) { templateCode = "#{any(ai.timefold.solver.core.api.score.stream.bi.BiConstraintStream)}\n"; - } else if (select.getType().isAssignableFrom(triConstraintStreamPattern)) { + } else if (selectType.isAssignableFrom(triConstraintStreamPattern)) { templateCode = "#{any(ai.timefold.solver.core.api.score.stream.tri.TriConstraintStream)}\n"; - } else if (select.getType().isAssignableFrom(quadConstraintStreamPattern)) { + } else if (selectType.isAssignableFrom(quadConstraintStreamPattern)) { templateCode = "#{any(ai.timefold.solver.core.api.score.stream.quad.QuadConstraintStream)}\n"; } else { LOGGER.warn("Cannot refactor to asConstraint() method for deprecated method ({}).", method); diff --git a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/domain/solution/ConstraintWeightOverridesSerializer.java b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/domain/solution/ConstraintWeightOverridesSerializer.java index d43eab4e42..4b79d452ed 100644 --- a/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/domain/solution/ConstraintWeightOverridesSerializer.java +++ b/persistence/jackson/src/main/java/ai/timefold/solver/jackson/api/domain/solution/ConstraintWeightOverridesSerializer.java @@ -1,6 +1,7 @@ package ai.timefold.solver.jackson.api.domain.solution; import java.io.IOException; +import java.util.Objects; import ai.timefold.solver.core.api.domain.solution.ConstraintWeightOverrides; import ai.timefold.solver.core.api.score.Score; @@ -17,7 +18,7 @@ public void serialize(ConstraintWeightOverrides constraintWeightOverride SerializerProvider serializerProvider) throws IOException { generator.writeStartObject(); for (var constraintName : constraintWeightOverrides.getKnownConstraintNames()) { - var weight = constraintWeightOverrides.getConstraintWeight(constraintName); + var weight = Objects.requireNonNull(constraintWeightOverrides.getConstraintWeight(constraintName)); generator.writeStringField(constraintName, weight.toString()); } generator.writeEndObject(); diff --git a/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonLikeType.java b/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonLikeType.java index 5e13521a08..55433c51f7 100644 --- a/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonLikeType.java +++ b/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonLikeType.java @@ -610,18 +610,16 @@ public String toString() { @Override public boolean equals(Object o) { - if (this == o) { + if (this == o) return true; - } - if (o == null || !PythonLikeType.class.isAssignableFrom(o.getClass())) { + if (!(o instanceof PythonLikeType that)) return false; - } - PythonLikeType that = (PythonLikeType) o; - return JAVA_TYPE_INTERNAL_NAME.equals(that.JAVA_TYPE_INTERNAL_NAME); + return Objects.equals(JAVA_TYPE_INTERNAL_NAME, that.JAVA_TYPE_INTERNAL_NAME); } @Override public int hashCode() { - return Objects.hash(JAVA_TYPE_INTERNAL_NAME); + return JAVA_TYPE_INTERNAL_NAME.hashCode(); } + } diff --git a/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonString.java b/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonString.java index eecf634f7c..8cb0c4c573 100644 --- a/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonString.java +++ b/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/PythonString.java @@ -1504,10 +1504,10 @@ public String toString() { @Override public boolean equals(Object o) { - if (o instanceof String) { - return value.equals(o); - } else if (o instanceof PythonString) { - return ((PythonString) o).value.equals(value); + if (o instanceof String s) { + return s.equals(value); + } else if (o instanceof PythonString s) { + return s.value.equals(value); } else { return false; } diff --git a/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeDict.java b/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeDict.java index 5377f0e23d..d0eca7631e 100644 --- a/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeDict.java +++ b/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeDict.java @@ -5,7 +5,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; import java.util.stream.Stream; @@ -387,8 +386,7 @@ public Iterator iterator() { @Override public boolean equals(Object o) { - if (o instanceof Map) { - Map other = (Map) o; + if (o instanceof Map other) { if (other.size() != this.size()) { return false; } @@ -399,7 +397,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(delegate); + return delegate.hashCode(); } @Override diff --git a/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeFrozenSet.java b/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeFrozenSet.java index fee99d676d..d0ffe87d15 100644 --- a/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeFrozenSet.java +++ b/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeFrozenSet.java @@ -4,7 +4,6 @@ import java.util.Collections; import java.util.HashSet; import java.util.Iterator; -import java.util.Objects; import java.util.Set; import java.util.function.Predicate; @@ -329,8 +328,7 @@ public void clear() { @Override public boolean equals(Object o) { - if (o instanceof Set) { - Set other = (Set) o; + if (o instanceof Set other) { if (other.size() != this.size()) { return false; } @@ -341,7 +339,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(delegate); + return delegate.hashCode(); } @Override diff --git a/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeList.java b/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeList.java index a5b4c321e9..9ea4ab0808 100644 --- a/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeList.java +++ b/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeList.java @@ -581,8 +581,7 @@ public String toString() { @Override public boolean equals(Object o) { - if (o instanceof List) { - List other = (List) o; + if (o instanceof List other) { if (other.size() != delegate.size()) { return false; } @@ -599,7 +598,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(delegate); + return delegate.hashCode(); } public List getDelegate() { diff --git a/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeSet.java b/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeSet.java index 6eb168b92c..eca5a85869 100644 --- a/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeSet.java +++ b/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeSet.java @@ -460,8 +460,7 @@ public void clear() { @Override public boolean equals(Object o) { - if (o instanceof Set) { - Set other = (Set) o; + if (o instanceof Set other) { if (other.size() != this.size()) { return false; } diff --git a/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeTuple.java b/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeTuple.java index 23b5b17f23..6af38368aa 100644 --- a/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeTuple.java +++ b/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/PythonLikeTuple.java @@ -392,8 +392,7 @@ public List subList(int i, int i1) { @Override public boolean equals(Object o) { - if (o instanceof List) { - List other = (List) o; + if (o instanceof List other) { if (other.size() != this.size()) { return false; } @@ -437,7 +436,7 @@ public int compareTo(PythonLikeTuple other) { @Override public int hashCode() { - return Objects.hash(delegate); + return delegate.hashCode(); } @Override diff --git a/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/view/DictItemView.java b/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/view/DictItemView.java index d628f6ef85..f32ee97bc7 100644 --- a/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/view/DictItemView.java +++ b/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/collections/view/DictItemView.java @@ -171,8 +171,7 @@ public PythonLikeSet symmetricDifference(DictItemView other) { @Override public boolean equals(Object o) { - if (o instanceof DictItemView) { - DictItemView other = (DictItemView) o; + if (o instanceof DictItemView other) { return entrySet.equals(other.entrySet); } return false; diff --git a/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/JavaObjectWrapper.java b/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/JavaObjectWrapper.java index 3ed68b7085..12d63efcc5 100644 --- a/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/JavaObjectWrapper.java +++ b/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/JavaObjectWrapper.java @@ -304,11 +304,11 @@ public String toString() { } @Override - public boolean equals(Object other) { - if (other instanceof JavaObjectWrapper) { - return wrappedObject.equals(((JavaObjectWrapper) other).wrappedObject); + public boolean equals(Object o) { + if (o instanceof JavaObjectWrapper other) { + return wrappedObject.equals(other.wrappedObject); } - return wrappedObject.equals(other); + return wrappedObject.equals(o); } @Override diff --git a/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/PythonObjectWrapper.java b/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/PythonObjectWrapper.java index ca47d96d8d..2d1b922974 100644 --- a/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/PythonObjectWrapper.java +++ b/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/types/wrappers/PythonObjectWrapper.java @@ -88,15 +88,13 @@ public boolean equals(Object o) { if (this == o) { return true; } - if (!(o instanceof PythonObjectWrapper)) { + if (!(o instanceof PythonObjectWrapper other)) { return false; } - PythonObjectWrapper other = (PythonObjectWrapper) o; Object maybeEquals = $getType().$getAttributeOrNull("__eq__"); - if (!(maybeEquals instanceof PythonLikeFunction)) { + if (!(maybeEquals instanceof PythonLikeFunction equals)) { return super.equals(o); } - PythonLikeFunction equals = (PythonLikeFunction) maybeEquals; PythonLikeObject result = equals.$call(List.of(this, other), Map.of(), null); if (result instanceof PythonBoolean) { return ((PythonBoolean) result).getBooleanValue(); @@ -107,10 +105,9 @@ public boolean equals(Object o) { @Override public int hashCode() { Object maybeHash = $getType().$getAttributeOrNull("__hash__"); - if (!(maybeHash instanceof PythonLikeFunction)) { + if (!(maybeHash instanceof PythonLikeFunction hash)) { return super.hashCode(); } - PythonLikeFunction hash = (PythonLikeFunction) maybeHash; PythonLikeObject result = hash.$call(List.of(this), Map.of(), null); if (result instanceof PythonInteger) { return ((PythonInteger) result).value.hashCode(); diff --git a/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/ConcurrentWeakIdentityHashMap.java b/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/ConcurrentWeakIdentityHashMap.java index f07ff65c1d..c9642f862c 100644 --- a/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/ConcurrentWeakIdentityHashMap.java +++ b/python/jpyinterpreter/src/main/java/ai/timefold/jpyinterpreter/util/ConcurrentWeakIdentityHashMap.java @@ -123,8 +123,8 @@ private static class Key extends WeakReference { } @Override - public boolean equals(Object obj) { - return this == obj || obj instanceof Key && ((Key) obj).get() == get(); + public boolean equals(Object o) { + return this == o || o instanceof Key other && other.get() == get(); } @Override diff --git a/quarkus-integration/quarkus-benchmark/runtime/src/main/java/ai/timefold/solver/benchmark/quarkus/TimefoldBenchmarkRecorder.java b/quarkus-integration/quarkus-benchmark/runtime/src/main/java/ai/timefold/solver/benchmark/quarkus/TimefoldBenchmarkRecorder.java index d474914013..fb304a5520 100644 --- a/quarkus-integration/quarkus-benchmark/runtime/src/main/java/ai/timefold/solver/benchmark/quarkus/TimefoldBenchmarkRecorder.java +++ b/quarkus-integration/quarkus-benchmark/runtime/src/main/java/ai/timefold/solver/benchmark/quarkus/TimefoldBenchmarkRecorder.java @@ -2,7 +2,7 @@ import java.io.File; import java.util.Collections; -import java.util.List; +import java.util.Objects; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.Supplier; @@ -22,7 +22,7 @@ public class TimefoldBenchmarkRecorder { public Supplier benchmarkConfigSupplier(PlannerBenchmarkConfig benchmarkConfig, TimefoldBenchmarkRuntimeConfig timefoldRuntimeConfig) { return () -> { - SolverConfig solverConfig = + var solverConfig = Arc.container().instance(SolverConfig.class).get(); // If the termination configuration is set and the created benchmark configuration has no configuration item, // we need to add at least one configuration; otherwise, we will fail to recognize the runtime termination setting. @@ -45,7 +45,7 @@ private PlannerBenchmarkConfig updateBenchmarkConfigWithRuntimeProperties(Planne if (benchmarkRuntimeConfig != null && benchmarkRuntimeConfig.resultDirectory() != null) { plannerBenchmarkConfig.setBenchmarkDirectory(new File(benchmarkRuntimeConfig.resultDirectory())); } - SolverBenchmarkConfig inheritedBenchmarkConfig = plannerBenchmarkConfig.getInheritedSolverBenchmarkConfig(); + var inheritedBenchmarkConfig = plannerBenchmarkConfig.getInheritedSolverBenchmarkConfig(); if (plannerBenchmarkConfig.getSolverBenchmarkBluePrintConfigList() != null) { if (inheritedBenchmarkConfig == null) { @@ -75,7 +75,7 @@ private PlannerBenchmarkConfig updateBenchmarkConfigWithRuntimeProperties(Planne } if (inheritedTerminationConfig == null || !inheritedTerminationConfig.isConfigured()) { - List solverBenchmarkConfigList = plannerBenchmarkConfig.getSolverBenchmarkConfigList(); + var solverBenchmarkConfigList = plannerBenchmarkConfig.getSolverBenchmarkConfigList(); if (solverBenchmarkConfigList == null) { throw new IllegalStateException("At least one of the properties " + "quarkus.timefold.benchmark.solver.termination.spent-limit, " + @@ -84,13 +84,10 @@ private PlannerBenchmarkConfig updateBenchmarkConfigWithRuntimeProperties(Planne "is required if termination is not configured in the " + "inherited solver benchmark config and solverBenchmarkBluePrint is used."); } - for (int i = 0; i < solverBenchmarkConfigList.size(); i++) { - SolverBenchmarkConfig solverBenchmarkConfig = solverBenchmarkConfigList.get(i); - if (solverBenchmarkConfig.getSolverConfig() == null) { - solverBenchmarkConfig.setSolverConfig(new SolverConfig()); - } - SolverConfig solverConfig_ = solverBenchmarkConfig.getSolverConfig(); - TerminationConfig terminationConfig = solverConfig_.getTerminationConfig(); + for (var solverBenchmarkConfig : solverBenchmarkConfigList) { + var solverConfig_ = Objects.requireNonNullElseGet(solverBenchmarkConfig.getSolverConfig(), SolverConfig::new); + solverBenchmarkConfig.setSolverConfig(solverConfig_); // In case it was null before. + var terminationConfig = solverConfig_.getTerminationConfig(); if (terminationConfig == null) { terminationConfig = new TerminationConfig(); solverConfig_.setTerminationConfig(terminationConfig); @@ -117,7 +114,7 @@ private PlannerBenchmarkConfig updateBenchmarkConfigWithRuntimeProperties(Planne } if (plannerBenchmarkConfig.getSolverBenchmarkConfigList() != null) { - for (SolverBenchmarkConfig childBenchmarkConfig : plannerBenchmarkConfig.getSolverBenchmarkConfigList()) { + for (var childBenchmarkConfig : plannerBenchmarkConfig.getSolverBenchmarkConfigList()) { if (childBenchmarkConfig.getSolverConfig() == null) { childBenchmarkConfig.setSolverConfig(new SolverConfig()); } @@ -168,12 +165,13 @@ private void inheritScoreCalculation(SolverBenchmarkConfig childBenchmarkConfig, isScoreCalculationDefined(inheritedBenchmarkConfig.getSolverConfig())) { return; } - ScoreDirectorFactoryConfig childScoreDirectorFactoryConfig = childBenchmarkConfig.getSolverConfig() + var childScoreDirectorFactoryConfig = Objects.requireNonNull(childBenchmarkConfig.getSolverConfig()) .getScoreDirectorFactoryConfig(); - ScoreDirectorFactoryConfig inheritedScoreDirectorFactoryConfig = solverConfig.getScoreDirectorFactoryConfig(); + var inheritedScoreDirectorFactoryConfig = Objects.requireNonNull(solverConfig.getScoreDirectorFactoryConfig()); if (childScoreDirectorFactoryConfig == null) { childScoreDirectorFactoryConfig = new ScoreDirectorFactoryConfig(); - childBenchmarkConfig.getSolverConfig().setScoreDirectorFactoryConfig(childScoreDirectorFactoryConfig); + Objects.requireNonNull(childBenchmarkConfig.getSolverConfig()) + .setScoreDirectorFactoryConfig(childScoreDirectorFactoryConfig); } childScoreDirectorFactoryConfig.inherit(inheritedScoreDirectorFactoryConfig); } @@ -182,7 +180,7 @@ private boolean isScoreCalculationDefined(SolverConfig solverConfig) { if (solverConfig == null) { return false; } - ScoreDirectorFactoryConfig scoreDirectorFactoryConfig = solverConfig.getScoreDirectorFactoryConfig(); + var scoreDirectorFactoryConfig = solverConfig.getScoreDirectorFactoryConfig(); if (scoreDirectorFactoryConfig == null) { return false; } 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 5ffc2c95f7..e861731f04 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 @@ -13,6 +13,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -336,9 +337,9 @@ Some solver configs (%s) don't specify a %s class, yet there are multiple availa private void assertNodeSharingDisabled(Map solverConfigMap) { for (var entry : solverConfigMap.entrySet()) { var solverConfig = entry.getValue(); - if (solverConfig.getScoreDirectorFactoryConfig() != null && - Boolean.TRUE - .equals(solverConfig.getScoreDirectorFactoryConfig().getConstraintStreamAutomaticNodeSharing())) { + var scoreDirectorFactoryConfig = solverConfig.getScoreDirectorFactoryConfig(); + if (scoreDirectorFactoryConfig != null && + Boolean.TRUE.equals(scoreDirectorFactoryConfig.getConstraintStreamAutomaticNodeSharing())) { throw new IllegalStateException(""" SolverConfig %s enabled automatic node sharing via SolverConfig, which is not allowed. Enable automatic node sharing with the property %s instead.""" @@ -532,9 +533,10 @@ private SolverConfig loadSolverConfig(IndexView indexView, // Configure planning problem models and score director per solver applySolverProperties(indexView, solverName, solverConfig); - if (solverConfig.getSolutionClass() != null) { + var solutionClass = solverConfig.getSolutionClass(); + if (solutionClass != null) { // Need to register even when using GIZMO so annotations are preserved - Type jandexType = Type.create(DotName.createSimple(solverConfig.getSolutionClass().getName()), Type.Kind.CLASS); + Type jandexType = Type.create(DotName.createSimple(solutionClass.getName()), Type.Kind.CLASS); reflectiveHierarchyClass.produce(new ReflectiveHierarchyBuildItem.Builder() .type(jandexType) // Ignore only the packages from timefold-solver-core @@ -639,11 +641,11 @@ public void recordAndRegisterDevUIBean( private void generateConstraintVerifier(SolverConfig solverConfig, BuildProducer syntheticBeanBuildItemBuildProducer) { String constraintVerifierClassName = DotNames.CONSTRAINT_VERIFIER.toString(); - if (solverConfig.getScoreDirectorFactoryConfig().getConstraintProviderClass() != null && - isClassDefined(constraintVerifierClassName)) { - final Class constraintProviderClass = solverConfig.getScoreDirectorFactoryConfig().getConstraintProviderClass(); - final Class planningSolutionClass = solverConfig.getSolutionClass(); - final List> planningEntityClassList = solverConfig.getEntityClassList(); + var scoreDirectorFactoryConfig = Objects.requireNonNull(solverConfig.getScoreDirectorFactoryConfig()); + var constraintProviderClass = scoreDirectorFactoryConfig.getConstraintProviderClass(); + if (constraintProviderClass != null && isClassDefined(constraintVerifierClassName)) { + final Class planningSolutionClass = Objects.requireNonNull(solverConfig.getSolutionClass()); + final List> planningEntityClassList = Objects.requireNonNull(solverConfig.getEntityClassList()); // TODO Don't duplicate defaults by using ConstraintVerifier.create(solverConfig) instead SyntheticBeanBuildItem.ExtendedBeanConfigurator constraintDescriptor = SyntheticBeanBuildItem.configure(DotNames.CONSTRAINT_VERIFIER) diff --git a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldBenchmarkAutoConfiguration.java b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldBenchmarkAutoConfiguration.java index ea118c9f90..961c666d3a 100644 --- a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldBenchmarkAutoConfiguration.java +++ b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldBenchmarkAutoConfiguration.java @@ -5,6 +5,7 @@ import java.io.File; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; import ai.timefold.solver.benchmark.api.PlannerBenchmarkFactory; @@ -51,9 +52,6 @@ public void setApplicationContext(ApplicationContext context) throws BeansExcept public PlannerBenchmarkConfig plannerBenchmarkConfig() { assertSingleSolver(); SolverConfig solverConfig = context.getBean(SolverConfig.class); - if (solverConfig == null) { - return null; - } PlannerBenchmarkConfig benchmarkConfig; if (timefoldProperties.getBenchmark() != null && timefoldProperties.getBenchmark().getSolverBenchmarkConfigXml() != null) { @@ -81,20 +79,21 @@ public PlannerBenchmarkConfig plannerBenchmarkConfig() { benchmarkConfig.setBenchmarkDirectory(new File(BenchmarkProperties.DEFAULT_BENCHMARK_RESULT_DIRECTORY)); } - if (benchmarkConfig.getInheritedSolverBenchmarkConfig() == null) { - SolverBenchmarkConfig inheritedBenchmarkConfig = new SolverBenchmarkConfig(); + var inheritedBenchmarkConfig = benchmarkConfig.getInheritedSolverBenchmarkConfig(); + if (inheritedBenchmarkConfig == null) { + inheritedBenchmarkConfig = new SolverBenchmarkConfig(); benchmarkConfig.setInheritedSolverBenchmarkConfig(inheritedBenchmarkConfig); inheritedBenchmarkConfig.setSolverConfig(solverConfig.copyConfig()); } + var inheritedBenchmarkSolverConfig = Objects.requireNonNull(inheritedBenchmarkConfig.getSolverConfig()); if (timefoldProperties.getBenchmark() != null && timefoldProperties.getBenchmark().getSolver() != null) { - TimefoldSolverAutoConfiguration - .applyTerminationProperties(benchmarkConfig.getInheritedSolverBenchmarkConfig().getSolverConfig(), - timefoldProperties.getBenchmark().getSolver().getTermination()); + TimefoldSolverAutoConfiguration.applyTerminationProperties(inheritedBenchmarkSolverConfig, + timefoldProperties.getBenchmark().getSolver().getTermination()); } - if (benchmarkConfig.getInheritedSolverBenchmarkConfig().getSolverConfig().getTerminationConfig() == null || - !benchmarkConfig.getInheritedSolverBenchmarkConfig().getSolverConfig().getTerminationConfig().isConfigured()) { + var inheritedTerminationConfig = inheritedBenchmarkSolverConfig.getTerminationConfig(); + if (inheritedTerminationConfig == null || !inheritedTerminationConfig.isConfigured()) { List solverBenchmarkConfigList = benchmarkConfig.getSolverBenchmarkConfigList(); List unconfiguredTerminationSolverBenchmarkList = new ArrayList<>(); if (solverBenchmarkConfigList == null) { @@ -107,10 +106,9 @@ public PlannerBenchmarkConfig plannerBenchmarkConfig() { } if (solverBenchmarkConfigList.size() == 1 && solverBenchmarkConfigList.get(0).getSolverConfig() == null) { // Benchmark config was created from solver config, which means only the inherited solver config exists. - SolverBenchmarkConfig solverBenchmarkConfig = benchmarkConfig.getInheritedSolverBenchmarkConfig(); - if (!solverBenchmarkConfig.getSolverConfig().canTerminate()) { + if (!inheritedBenchmarkSolverConfig.canTerminate()) { String benchmarkConfigName = - requireNonNullElse(solverBenchmarkConfig.getName(), "InheritedSolverBenchmarkConfig"); + requireNonNullElse(inheritedBenchmarkConfig.getName(), "InheritedSolverBenchmarkConfig"); unconfiguredTerminationSolverBenchmarkList.add(benchmarkConfigName); } } else { diff --git a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAutoConfiguration.java b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAutoConfiguration.java index c2b1c96a2c..a07ed10136 100644 --- a/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAutoConfiguration.java +++ b/spring-integration/spring-boot-autoconfigure/src/main/java/ai/timefold/solver/spring/boot/autoconfigure/TimefoldSolverAutoConfiguration.java @@ -238,10 +238,11 @@ private void loadSolverConfig(IncludeAbstractClassesEntityScanner entityScanner, if (solverConfig.getSolutionClass() == null) { solverConfig.setSolutionClass(entityScanner.findFirstSolutionClass()); } - if (solverConfig.getEntityClassList() == null) { + var solverEntityClassList = solverConfig.getEntityClassList(); + if (solverEntityClassList == null) { solverConfig.setEntityClassList(entityScanner.findEntityClassList()); } else { - long entityClassCount = solverConfig.getEntityClassList().stream() + var entityClassCount = solverEntityClassList.stream() .filter(Objects::nonNull) .count(); if (entityClassCount == 0L) { @@ -249,7 +250,7 @@ private void loadSolverConfig(IncludeAbstractClassesEntityScanner entityScanner, """ The solverConfig's entityClassList (%s) does not contain any non-null entries. Maybe the classes listed there do not actually exist and therefore deserialization turned them to null?""" - .formatted(solverConfig.getEntityClassList().stream().map(Class::getSimpleName) + .formatted(solverEntityClassList.stream().map(Class::getSimpleName) .collect(joining(", ")))); } } @@ -268,7 +269,8 @@ private void applyScoreDirectorFactoryProperties(IncludeAbstractClassesEntitySca throw new UnsupportedOperationException( "Constraint stream automatic node sharing is unsupported in a Spring native image."); } - solverConfig.getScoreDirectorFactoryConfig().setConstraintStreamAutomaticNodeSharing(true); + Objects.requireNonNull(solverConfig.getScoreDirectorFactoryConfig()) + .setConstraintStreamAutomaticNodeSharing(true); } if (solverProperties.getEnvironmentMode() != null) { solverConfig.setEnvironmentMode(solverProperties.getEnvironmentMode());