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..72d21c4b26 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 @@ -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,17 +43,17 @@ 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(); + 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) { @@ -78,12 +76,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 +90,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,7 +111,7 @@ 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(), @@ -123,7 +122,7 @@ protected EntitySelector buildMimicReplaying(HeuristicConfigPolicy 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() @@ -134,22 +133,18 @@ protected EntitySelector buildMimicReplaying(HeuristicConfigPolicy entityDescriptor, SelectionCacheType resolvedCacheType, SelectionOrder resolvedSelectionOrder) { - switch (resolvedSelectionOrder) { - case ORIGINAL: - return false; - case SORTED: - case SHUFFLED: - case PROBABILISTIC: + return switch (resolvedSelectionOrder) { + case ORIGINAL -> false; + case 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() { @@ -182,7 +177,7 @@ 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) { @@ -263,7 +258,7 @@ protected EntitySelector applySorting(SelectionCacheType resolvedCach if (resolvedSelectionOrder == SelectionOrder.SORTED) { SelectionSorter sorter; if (config.getSorterManner() != null) { - EntityDescriptor entityDescriptor = entitySelector.getEntityDescriptor(); + var entityDescriptor = entitySelector.getEntityDescriptor(); if (!EntitySelectorConfig.hasSorter(config.getSorterManner(), entityDescriptor)) { return entitySelector; } @@ -357,14 +352,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..927e52b671 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,21 +108,17 @@ 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 -> false; + case 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() && !hasFiltering()); + default -> throw new IllegalStateException("The selectionOrder (" + resolvedSelectionOrder + + ") is not implemented."); + }; } protected boolean isBaseInherentlyCached() { 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..9401c21e4e 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,21 +115,23 @@ 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() + var subListChangeMoveSelectorConfig = config.copyConfig() .withSubListSelectorConfig(new SubListSelectorConfig(config.getSubListSelectorConfig()) .withValueSelectorConfig(Optional.ofNullable(config.getSubListSelectorConfig()) .map(SubListSelectorConfig::getValueSelectorConfig) @@ -164,7 +153,7 @@ private SubListChangeMoveSelectorConfig buildChildMoveSelectorConfig(ListVariabl // override variable name (destination value selector is never replaying) .withVariableName(variableDescriptor.getVariableName()))); - SubListSelectorConfig subListSelectorConfig = subListChangeMoveSelectorConfig.getSubListSelectorConfig(); + var subListSelectorConfig = Objects.requireNonNull(subListChangeMoveSelectorConfig.getSubListSelectorConfig()); SubListConfigUtil.transferDeprecatedMinimumSubListSize( subListChangeMoveSelectorConfig, SubListChangeMoveSelectorConfig::getMinimumSubListSize, @@ -175,11 +164,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..dd5ef87a43 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 @@ -12,9 +12,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 +38,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 +53,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 +84,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 +106,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 +145,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 +184,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 +203,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 +229,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 +247,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); @@ -322,7 +316,7 @@ protected ValueSelector applySorting(SelectionCacheType resolvedCache if (resolvedSelectionOrder == SelectionOrder.SORTED) { SelectionSorter sorter; if (config.getSorterManner() != null) { - GenuineVariableDescriptor variableDescriptor = valueSelector.getVariableDescriptor(); + var variableDescriptor = valueSelector.getVariableDescriptor(); if (!ValueSelectorConfig.hasSorter(config.getSorterManner(), variableDescriptor)) { return valueSelector; } @@ -461,21 +455,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..1b573b0ca5 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); } 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..480aca1233 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); + var pickEarlyType_ = Objects.requireNonNullElse(foragerConfig.getPickEarlyType(), LocalSearchPickEarlyType.NEVER); int acceptedCountLimit_ = Objects.requireNonNullElse(foragerConfig.getAcceptedCountLimit(), Integer.MAX_VALUE); - FinalistPodiumType finalistPodiumType_ = + 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..cc4e01eb24 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); } 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/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..db8bce7426 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) 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/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());