+ * Implementations are expected to be stateless.
+ * The solver may choose to reuse instances.
+ *
+ * @param the solution type, the class with the {@link PlanningSolution} annotation
+ * @param the selection type
+ *
+ * @see ValueSelectorConfig
+ * @see EntitySelectorConfig
+ * @see ConstructionHeuristicPhaseConfig
+ */
+@NullMarked
+@FunctionalInterface
+public interface ComparatorFactory {
+
+ /**
+ * @param solution never null, the {@link PlanningSolution} to which the selection belongs or applies to
+ * @return never null
+ */
+ Comparator createComparator(Solution_ solution);
+
+}
diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java b/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java
index ab0e2f9ea2..f041b0b872 100644
--- a/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java
+++ b/core/src/main/java/ai/timefold/solver/core/api/domain/entity/PlanningEntity.java
@@ -7,6 +7,7 @@
import java.lang.annotation.Target;
import java.util.Comparator;
+import ai.timefold.solver.core.api.domain.common.ComparatorFactory;
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
import ai.timefold.solver.core.api.domain.variable.PlanningVariable;
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory;
@@ -34,6 +35,40 @@
@Retention(RUNTIME)
public @interface PlanningEntity {
+ /**
+ * Allows sorting a collection of planning entities for this variable.
+ * Some algorithms perform better when the entities are sorted based on specific metrics.
+ *
+ * The {@link Comparator} should sort the data in ascending order.
+ * For example, prioritize three vehicles by sorting them based on their capacity:
+ * Vehicle C (4 people), Vehicle A (6 people), Vehicle B (32 people)
+ *
+ * Do not use together with {@link #comparatorFactoryClass()}.
+ *
+ * @return {@link PlanningVariable.NullComparator} when it is null (workaround for annotation limitation)
+ * @see #comparatorFactoryClass()
+ */
+ Class extends Comparator> comparatorClass() default NullComparator.class;
+
+ interface NullComparator extends Comparator {
+ }
+
+ /**
+ * The {@link ComparatorFactory} alternative for {@link #comparatorClass()}.
+ *
+ * Differs from {@link #comparatorClass()}
+ * because it allows accessing the current solution when creating the comparator.
+ *
+ * Do not use together with {@link #comparatorClass()}.
+ *
+ * @return {@link NullComparatorFactory} when it is null (workaround for annotation limitation)
+ * @see #comparatorClass()
+ */
+ Class extends ComparatorFactory> comparatorFactoryClass() default NullComparatorFactory.class;
+
+ interface NullComparatorFactory extends ComparatorFactory {
+ }
+
/**
* A pinned planning entity is never changed during planning,
* this is useful in repeated planning use cases (such as continuous planning and real-time planning).
@@ -50,7 +85,7 @@
/**
* Workaround for annotation limitation in {@link #pinningFilter()}.
- *
+ *
* @deprecated Prefer using {@link PlanningPin}.
*/
@Deprecated(forRemoval = true, since = "1.23.0")
@@ -69,13 +104,21 @@ interface NullPinningFilter extends PinningFilter {
*
* Do not use together with {@link #difficultyWeightFactoryClass()}.
*
+ * @deprecated Deprecated in favor of {@link #comparatorClass()}.
+ *
* @return {@link NullDifficultyComparator} when it is null (workaround for annotation limitation)
* @see #difficultyWeightFactoryClass()
*/
+ @Deprecated(forRemoval = true, since = "1.28.0")
Class extends Comparator> difficultyComparatorClass() default NullDifficultyComparator.class;
- /** Workaround for annotation limitation in {@link #difficultyComparatorClass()}. */
- interface NullDifficultyComparator extends Comparator {
+ /**
+ * Workaround for annotation limitation in {@link #difficultyComparatorClass()}.
+ *
+ * @deprecated Deprecated in favor of {@link NullComparator}.
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
+ interface NullDifficultyComparator extends NullComparator {
}
/**
@@ -83,13 +126,23 @@ interface NullDifficultyComparator extends Comparator {
*
* Do not use together with {@link #difficultyComparatorClass()}.
*
+ * @deprecated Deprecated in favor of {@link #comparatorFactoryClass()}.
+ *
* @return {@link NullDifficultyWeightFactory} when it is null (workaround for annotation limitation)
* @see #difficultyComparatorClass()
*/
+ @Deprecated(forRemoval = true, since = "1.28.0")
Class extends SelectionSorterWeightFactory> difficultyWeightFactoryClass() default NullDifficultyWeightFactory.class;
- /** Workaround for annotation limitation in {@link #difficultyWeightFactoryClass()}. */
- interface NullDifficultyWeightFactory extends SelectionSorterWeightFactory {
+ /**
+ * Workaround for annotation limitation in {@link #difficultyWeightFactoryClass()}.
+ *
+ * @deprecated Deprecated in favor of {@link NullComparatorFactory}.
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
+ interface NullDifficultyWeightFactory
+ extends SelectionSorterWeightFactory,
+ NullComparatorFactory {
}
}
diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningListVariable.java b/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningListVariable.java
index c6d28fc223..3ada275a51 100644
--- a/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningListVariable.java
+++ b/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningListVariable.java
@@ -6,11 +6,15 @@
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
+import java.util.Comparator;
import java.util.List;
+import ai.timefold.solver.core.api.domain.common.ComparatorFactory;
import ai.timefold.solver.core.api.domain.entity.PlanningEntity;
import ai.timefold.solver.core.api.domain.entity.PlanningPin;
import ai.timefold.solver.core.api.domain.entity.PlanningPinToIndex;
+import ai.timefold.solver.core.api.domain.variable.PlanningVariable.NullComparator;
+import ai.timefold.solver.core.api.domain.variable.PlanningVariable.NullComparatorFactory;
/**
* Specifies that a bean property (or a field) can be changed and should be optimized by the optimization algorithms.
@@ -55,5 +59,29 @@
String[] valueRangeProviderRefs() default {};
- // TODO value comparison: https://issues.redhat.com/browse/PLANNER-2542
+ /**
+ * Allows sorting a collection of planning values for this variable.
+ * Some algorithms perform better when the values are sorted based on specific metrics.
+ *
+ * The {@link Comparator} should sort the data in ascending order.
+ * For example, prioritize three visits by sorting them based on their importance:
+ * Visit C (SMALL_PRIORITY), Visit A (MEDIUM_PRIORITY), Visit B (HIGH_PRIORITY)
+ *
+ * Do not use together with {@link #comparatorFactoryClass()}.
+ *
+ * @return {@link NullComparator} when it is null (workaround for annotation limitation)
+ * @see #comparatorFactoryClass()
+ */
+ Class extends Comparator> comparatorClass() default NullComparator.class;
+
+ /**
+ * The {@link ComparatorFactory} alternative for {@link #comparatorClass()}.
+ *
+ * Do not use together with {@link #comparatorClass()}.
+ *
+ * @return {@link NullComparatorFactory} when it is null (workaround for annotation limitation)
+ * @see #comparatorClass()
+ */
+ Class extends ComparatorFactory> comparatorFactoryClass() default NullComparatorFactory.class;
+
}
diff --git a/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java b/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java
index 501ab44064..7f9967fd22 100644
--- a/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java
+++ b/core/src/main/java/ai/timefold/solver/core/api/domain/variable/PlanningVariable.java
@@ -8,6 +8,7 @@
import java.lang.annotation.Target;
import java.util.Comparator;
+import ai.timefold.solver.core.api.domain.common.ComparatorFactory;
import ai.timefold.solver.core.api.domain.entity.PlanningEntity;
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider;
@@ -50,6 +51,48 @@
*/
boolean allowsUnassigned() default false;
+ /**
+ * In some use cases, such as Vehicle Routing, planning entities form a specific graph type,
+ * as specified by {@link PlanningVariableGraphType}.
+ *
+ * @return never null, defaults to {@link PlanningVariableGraphType#NONE}
+ */
+ PlanningVariableGraphType graphType() default PlanningVariableGraphType.NONE;
+
+ /**
+ * Allows sorting a collection of planning values for this variable.
+ * Some algorithms perform better when the values are sorted based on specific metrics.
+ *
+ * The {@link Comparator} should sort the data in ascending order.
+ * For example, prioritize three visits by sorting them based on their importance:
+ * Visit C (SMALL_PRIORITY), Visit A (MEDIUM_PRIORITY), Visit B (HIGH_PRIORITY)
+ *
+ * Do not use together with {@link #comparatorFactoryClass()}.
+ *
+ * @return {@link NullComparator} when it is null (workaround for annotation limitation)
+ * @see #comparatorFactoryClass()
+ */
+ Class extends Comparator> comparatorClass() default NullComparator.class;
+
+ interface NullComparator extends Comparator {
+ }
+
+ /**
+ * The {@link ComparatorFactory} alternative for {@link #comparatorClass()}.
+ *
+ * Differs from {@link #comparatorClass()}
+ * because it allows accessing the current solution when creating the comparator.
+ *
+ * Do not use together with {@link #comparatorClass()}.
+ *
+ * @return {@link NullComparatorFactory} when it is null (workaround for annotation limitation)
+ * @see #comparatorClass()
+ */
+ Class extends ComparatorFactory> comparatorFactoryClass() default NullComparatorFactory.class;
+
+ interface NullComparatorFactory extends ComparatorFactory {
+ }
+
/**
* As defined by {@link #allowsUnassigned()}.
*
@@ -59,14 +102,6 @@
@Deprecated(forRemoval = true, since = "1.8.0")
boolean nullable() default false;
- /**
- * In some use cases, such as Vehicle Routing, planning entities form a specific graph type,
- * as specified by {@link PlanningVariableGraphType}.
- *
- * @return never null, defaults to {@link PlanningVariableGraphType#NONE}
- */
- PlanningVariableGraphType graphType() default PlanningVariableGraphType.NONE;
-
/**
* Allows a collection of planning values for this variable to be sorted by strength.
* A strengthWeight estimates how strong a planning value is.
@@ -78,13 +113,21 @@
*
* Do not use together with {@link #strengthWeightFactoryClass()}.
*
+ * @deprecated Deprecated in favor of {@link #comparatorClass()}.
+ *
* @return {@link NullStrengthComparator} when it is null (workaround for annotation limitation)
* @see #strengthWeightFactoryClass()
*/
+ @Deprecated(forRemoval = true, since = "1.28.0")
Class extends Comparator> strengthComparatorClass() default NullStrengthComparator.class;
- /** Workaround for annotation limitation in {@link #strengthComparatorClass()}. */
- interface NullStrengthComparator extends Comparator {
+ /**
+ * Workaround for annotation limitation in {@link #strengthComparatorClass()}.
+ *
+ * @deprecated Deprecated in favor of {@link NullComparator}.
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
+ interface NullStrengthComparator extends NullComparator {
}
/**
@@ -92,13 +135,21 @@ interface NullStrengthComparator extends Comparator {
*
* Do not use together with {@link #strengthComparatorClass()}.
*
+ * @deprecated Deprecated in favor of {@link #comparatorFactoryClass()}.
+ *
* @return {@link NullStrengthWeightFactory} when it is null (workaround for annotation limitation)
* @see #strengthComparatorClass()
*/
+ @Deprecated(forRemoval = true, since = "1.28.0")
Class extends SelectionSorterWeightFactory> strengthWeightFactoryClass() default NullStrengthWeightFactory.class;
- /** Workaround for annotation limitation in {@link #strengthWeightFactoryClass()}. */
- interface NullStrengthWeightFactory extends SelectionSorterWeightFactory {
+ /**
+ * Workaround for annotation limitation in {@link #strengthWeightFactoryClass()}.
+ *
+ * @deprecated Deprecated in favor of {@link NullComparatorFactory}.
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
+ interface NullStrengthWeightFactory
+ extends SelectionSorterWeightFactory, NullComparatorFactory {
}
-
}
diff --git a/core/src/main/java/ai/timefold/solver/core/config/constructionheuristic/ConstructionHeuristicType.java b/core/src/main/java/ai/timefold/solver/core/config/constructionheuristic/ConstructionHeuristicType.java
index e1af4741ac..cda2ed6781 100644
--- a/core/src/main/java/ai/timefold/solver/core/config/constructionheuristic/ConstructionHeuristicType.java
+++ b/core/src/main/java/ai/timefold/solver/core/config/constructionheuristic/ConstructionHeuristicType.java
@@ -5,8 +5,9 @@
import ai.timefold.solver.core.config.heuristic.selector.entity.EntitySorterManner;
import ai.timefold.solver.core.config.heuristic.selector.value.ValueSorterManner;
-import org.jspecify.annotations.NonNull;
+import org.jspecify.annotations.NullMarked;
+@NullMarked
@XmlEnum
public enum ConstructionHeuristicType {
/**
@@ -56,52 +57,30 @@ public enum ConstructionHeuristicType {
*/
ALLOCATE_FROM_POOL;
- public @NonNull EntitySorterManner getDefaultEntitySorterManner() {
- switch (this) {
- case FIRST_FIT:
- case WEAKEST_FIT:
- case STRONGEST_FIT:
- return EntitySorterManner.NONE;
- case FIRST_FIT_DECREASING:
- case WEAKEST_FIT_DECREASING:
- case STRONGEST_FIT_DECREASING:
- return EntitySorterManner.DECREASING_DIFFICULTY;
- case ALLOCATE_ENTITY_FROM_QUEUE:
- case ALLOCATE_TO_VALUE_FROM_QUEUE:
- case CHEAPEST_INSERTION:
- case ALLOCATE_FROM_POOL:
- return EntitySorterManner.DECREASING_DIFFICULTY_IF_AVAILABLE;
- default:
- throw new IllegalStateException("The constructionHeuristicType (" + this + ") is not implemented.");
- }
+ public EntitySorterManner getDefaultEntitySorterManner() {
+ return switch (this) {
+ case FIRST_FIT, WEAKEST_FIT, STRONGEST_FIT -> EntitySorterManner.NONE;
+ case FIRST_FIT_DECREASING, WEAKEST_FIT_DECREASING, STRONGEST_FIT_DECREASING -> EntitySorterManner.DESCENDING;
+ case ALLOCATE_ENTITY_FROM_QUEUE, ALLOCATE_TO_VALUE_FROM_QUEUE, CHEAPEST_INSERTION, ALLOCATE_FROM_POOL ->
+ EntitySorterManner.DESCENDING_IF_AVAILABLE;
+ };
}
- public @NonNull ValueSorterManner getDefaultValueSorterManner() {
- switch (this) {
- case FIRST_FIT:
- case FIRST_FIT_DECREASING:
- return ValueSorterManner.NONE;
- case WEAKEST_FIT:
- case WEAKEST_FIT_DECREASING:
- return ValueSorterManner.INCREASING_STRENGTH;
- case STRONGEST_FIT:
- case STRONGEST_FIT_DECREASING:
- return ValueSorterManner.DECREASING_STRENGTH;
- case ALLOCATE_ENTITY_FROM_QUEUE:
- case ALLOCATE_TO_VALUE_FROM_QUEUE:
- case CHEAPEST_INSERTION:
- case ALLOCATE_FROM_POOL:
- return ValueSorterManner.INCREASING_STRENGTH_IF_AVAILABLE;
- default:
- throw new IllegalStateException("The constructionHeuristicType (" + this + ") is not implemented.");
- }
+ public ValueSorterManner getDefaultValueSorterManner() {
+ return switch (this) {
+ case FIRST_FIT, FIRST_FIT_DECREASING -> ValueSorterManner.NONE;
+ case WEAKEST_FIT, WEAKEST_FIT_DECREASING -> ValueSorterManner.ASCENDING;
+ case STRONGEST_FIT, STRONGEST_FIT_DECREASING -> ValueSorterManner.DESCENDING;
+ case ALLOCATE_ENTITY_FROM_QUEUE, ALLOCATE_TO_VALUE_FROM_QUEUE, CHEAPEST_INSERTION, ALLOCATE_FROM_POOL ->
+ ValueSorterManner.ASCENDING_IF_AVAILABLE;
+ };
}
/**
* @return {@link ConstructionHeuristicType#values()} without duplicates (abstract types that end up behaving as one of the
* other types).
*/
- public static @NonNull ConstructionHeuristicType @NonNull [] getBluePrintTypes() {
+ public static ConstructionHeuristicType[] getBluePrintTypes() {
return new ConstructionHeuristicType[] {
FIRST_FIT,
FIRST_FIT_DECREASING,
diff --git a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySelectorConfig.java b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySelectorConfig.java
index 38a265b95f..5e8c52e316 100644
--- a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySelectorConfig.java
+++ b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySelectorConfig.java
@@ -7,6 +7,7 @@
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlType;
+import ai.timefold.solver.core.api.domain.common.ComparatorFactory;
import ai.timefold.solver.core.api.domain.entity.PlanningEntity;
import ai.timefold.solver.core.config.heuristic.selector.SelectorConfig;
import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType;
@@ -20,7 +21,7 @@
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter;
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory;
-import org.jspecify.annotations.NonNull;
+import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@XmlType(propOrder = {
@@ -33,12 +34,15 @@
"filterClass",
"sorterManner",
"sorterComparatorClass",
+ "comparatorClass",
"sorterWeightFactoryClass",
+ "comparatorFactoryClass",
"sorterOrder",
"sorterClass",
"probabilityWeightFactoryClass",
"selectedCountLimit"
})
+@NullMarked
public class EntitySelectorConfig extends SelectorConfig {
public static EntitySelectorConfig newMimicSelectorConfig(String mimicSelectorRef) {
@@ -46,29 +50,54 @@ public static EntitySelectorConfig newMimicSelectorConfig(String mimicSelectorRe
.withMimicSelectorRef(mimicSelectorRef);
}
+ @Nullable
@XmlAttribute
protected String id = null;
@XmlAttribute
+ @Nullable
protected String mimicSelectorRef = null;
+ @Nullable
protected Class> entityClass = null;
-
+ @Nullable
protected SelectionCacheType cacheType = null;
+ @Nullable
protected SelectionOrder selectionOrder = null;
+ @Nullable
@XmlElement(name = "nearbySelection")
protected NearbySelectionConfig nearbySelectionConfig = null;
+ @Nullable
protected Class extends SelectionFilter> filterClass = null;
+ @Nullable
protected EntitySorterManner sorterManner = null;
+ /**
+ * @deprecated Deprecated in favor of {@link #comparatorClass}.
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
+ @Nullable
protected Class extends Comparator> sorterComparatorClass = null;
+ @Nullable
+ protected Class extends Comparator> comparatorClass = null;
+ /**
+ * @deprecated Deprecated in favor of {@link #comparatorFactoryClass}.
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
+ @Nullable
protected Class extends SelectionSorterWeightFactory> sorterWeightFactoryClass = null;
+ @Nullable
+ protected Class extends ComparatorFactory> comparatorFactoryClass = null;
+ @Nullable
protected SelectionSorterOrder sorterOrder = null;
+ @Nullable
protected Class extends SelectionSorter> sorterClass = null;
+ @Nullable
protected Class extends SelectionProbabilityWeightFactory> probabilityWeightFactoryClass = null;
+ @Nullable
protected Long selectedCountLimit = null;
public EntitySelectorConfig() {
@@ -148,22 +177,55 @@ public void setSorterManner(@Nullable EntitySorterManner sorterManner) {
this.sorterManner = sorterManner;
}
+ /**
+ * @deprecated Deprecated in favor of {@link #getComparatorClass()}
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
public @Nullable Class extends Comparator> getSorterComparatorClass() {
return sorterComparatorClass;
}
+ /**
+ * @deprecated Deprecated in favor of {@link #setComparatorClass(Class)}
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
public void setSorterComparatorClass(@Nullable Class extends Comparator> sorterComparatorClass) {
this.sorterComparatorClass = sorterComparatorClass;
}
+ public @Nullable Class extends Comparator> getComparatorClass() {
+ return comparatorClass;
+ }
+
+ public void setComparatorClass(@Nullable Class extends Comparator> comparatorClass) {
+ this.comparatorClass = comparatorClass;
+ }
+
+ /**
+ * @deprecated Deprecated in favor of {@link #getComparatorFactoryClass()}
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
public @Nullable Class extends SelectionSorterWeightFactory> getSorterWeightFactoryClass() {
return sorterWeightFactoryClass;
}
+ /**
+ * @deprecated Deprecated in favor of {@link #setComparatorFactoryClass(Class)}
+ * @param sorterWeightFactoryClass the class
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
public void setSorterWeightFactoryClass(@Nullable Class extends SelectionSorterWeightFactory> sorterWeightFactoryClass) {
this.sorterWeightFactoryClass = sorterWeightFactoryClass;
}
+ public @Nullable Class extends ComparatorFactory> getComparatorFactoryClass() {
+ return comparatorFactoryClass;
+ }
+
+ public void setComparatorFactoryClass(@Nullable Class extends ComparatorFactory> comparatorFactoryClass) {
+ this.comparatorFactoryClass = comparatorFactoryClass;
+ }
+
public @Nullable SelectionSorterOrder getSorterOrder() {
return sorterOrder;
}
@@ -201,74 +263,94 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) {
// With methods
// ************************************************************************
- public @NonNull EntitySelectorConfig withId(@NonNull String id) {
+ public EntitySelectorConfig withId(String id) {
this.setId(id);
return this;
}
- public @NonNull EntitySelectorConfig withMimicSelectorRef(@NonNull String mimicSelectorRef) {
+ public EntitySelectorConfig withMimicSelectorRef(String mimicSelectorRef) {
this.setMimicSelectorRef(mimicSelectorRef);
return this;
}
- public @NonNull EntitySelectorConfig withEntityClass(@NonNull Class> entityClass) {
+ public EntitySelectorConfig withEntityClass(Class> entityClass) {
this.setEntityClass(entityClass);
return this;
}
- public @NonNull EntitySelectorConfig withCacheType(@NonNull SelectionCacheType cacheType) {
+ public EntitySelectorConfig withCacheType(SelectionCacheType cacheType) {
this.setCacheType(cacheType);
return this;
}
- public @NonNull EntitySelectorConfig withSelectionOrder(@NonNull SelectionOrder selectionOrder) {
+ public EntitySelectorConfig withSelectionOrder(SelectionOrder selectionOrder) {
this.setSelectionOrder(selectionOrder);
return this;
}
- public @NonNull EntitySelectorConfig withNearbySelectionConfig(@NonNull NearbySelectionConfig nearbySelectionConfig) {
+ public EntitySelectorConfig withNearbySelectionConfig(NearbySelectionConfig nearbySelectionConfig) {
this.setNearbySelectionConfig(nearbySelectionConfig);
return this;
}
- public @NonNull EntitySelectorConfig withFilterClass(@NonNull Class extends SelectionFilter> filterClass) {
+ public EntitySelectorConfig withFilterClass(Class extends SelectionFilter> filterClass) {
this.setFilterClass(filterClass);
return this;
}
- public @NonNull EntitySelectorConfig withSorterManner(@NonNull EntitySorterManner sorterManner) {
+ public EntitySelectorConfig withSorterManner(EntitySorterManner sorterManner) {
this.setSorterManner(sorterManner);
return this;
}
- public @NonNull EntitySelectorConfig withSorterComparatorClass(@NonNull Class extends Comparator> comparatorClass) {
+ /**
+ * @deprecated Deprecated in favor of {@link #withComparatorClass(Class)}
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
+ public EntitySelectorConfig withSorterComparatorClass(Class extends Comparator> comparatorClass) {
this.setSorterComparatorClass(comparatorClass);
return this;
}
- public @NonNull EntitySelectorConfig
- withSorterWeightFactoryClass(@NonNull Class extends SelectionSorterWeightFactory> weightFactoryClass) {
+ public EntitySelectorConfig withComparatorClass(Class extends Comparator> comparatorClass) {
+ this.setComparatorClass(comparatorClass);
+ return this;
+ }
+
+ /**
+ * @deprecated Deprecated in favor of {@link #withComparatorFactoryClass(Class)}
+ * @param weightFactoryClass the factory class
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
+ public EntitySelectorConfig
+ withSorterWeightFactoryClass(Class extends SelectionSorterWeightFactory> weightFactoryClass) {
this.setSorterWeightFactoryClass(weightFactoryClass);
return this;
}
- public @NonNull EntitySelectorConfig withSorterOrder(@NonNull SelectionSorterOrder sorterOrder) {
+ public EntitySelectorConfig
+ withComparatorFactoryClass(Class extends ComparatorFactory> comparatorFactoryClass) {
+ this.setComparatorFactoryClass(comparatorFactoryClass);
+ return this;
+ }
+
+ public EntitySelectorConfig withSorterOrder(SelectionSorterOrder sorterOrder) {
this.setSorterOrder(sorterOrder);
return this;
}
- public @NonNull EntitySelectorConfig withSorterClass(@NonNull Class extends SelectionSorter> sorterClass) {
+ public EntitySelectorConfig withSorterClass(Class extends SelectionSorter> sorterClass) {
this.setSorterClass(sorterClass);
return this;
}
- public @NonNull EntitySelectorConfig
- withProbabilityWeightFactoryClass(@NonNull Class extends SelectionProbabilityWeightFactory> factoryClass) {
+ public EntitySelectorConfig
+ withProbabilityWeightFactoryClass(Class extends SelectionProbabilityWeightFactory> factoryClass) {
this.setProbabilityWeightFactoryClass(factoryClass);
return this;
}
- public @NonNull EntitySelectorConfig withSelectedCountLimit(long selectedCountLimit) {
+ public EntitySelectorConfig withSelectedCountLimit(long selectedCountLimit) {
this.setSelectedCountLimit(selectedCountLimit);
return this;
}
@@ -278,7 +360,7 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) {
// ************************************************************************
@Override
- public @NonNull EntitySelectorConfig inherit(@NonNull EntitySelectorConfig inheritedConfig) {
+ public EntitySelectorConfig inherit(EntitySelectorConfig inheritedConfig) {
id = ConfigUtils.inheritOverwritableProperty(id, inheritedConfig.getId());
mimicSelectorRef = ConfigUtils.inheritOverwritableProperty(mimicSelectorRef,
inheritedConfig.getMimicSelectorRef());
@@ -293,8 +375,12 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) {
sorterManner, inheritedConfig.getSorterManner());
sorterComparatorClass = ConfigUtils.inheritOverwritableProperty(
sorterComparatorClass, inheritedConfig.getSorterComparatorClass());
+ comparatorClass = ConfigUtils.inheritOverwritableProperty(
+ comparatorClass, inheritedConfig.getComparatorClass());
sorterWeightFactoryClass = ConfigUtils.inheritOverwritableProperty(
sorterWeightFactoryClass, inheritedConfig.getSorterWeightFactoryClass());
+ comparatorFactoryClass = ConfigUtils.inheritOverwritableProperty(
+ comparatorFactoryClass, inheritedConfig.getComparatorFactoryClass());
sorterOrder = ConfigUtils.inheritOverwritableProperty(
sorterOrder, inheritedConfig.getSorterOrder());
sorterClass = ConfigUtils.inheritOverwritableProperty(
@@ -307,19 +393,21 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) {
}
@Override
- public @NonNull EntitySelectorConfig copyConfig() {
+ public EntitySelectorConfig copyConfig() {
return new EntitySelectorConfig().inherit(this);
}
@Override
- public void visitReferencedClasses(@NonNull Consumer> classVisitor) {
+ public void visitReferencedClasses(Consumer> classVisitor) {
classVisitor.accept(entityClass);
if (nearbySelectionConfig != null) {
nearbySelectionConfig.visitReferencedClasses(classVisitor);
}
classVisitor.accept(filterClass);
classVisitor.accept(sorterComparatorClass);
+ classVisitor.accept(comparatorClass);
classVisitor.accept(sorterWeightFactoryClass);
+ classVisitor.accept(comparatorFactoryClass);
classVisitor.accept(sorterClass);
classVisitor.accept(probabilityWeightFactoryClass);
}
@@ -329,41 +417,31 @@ public String toString() {
return getClass().getSimpleName() + "(" + entityClass + ")";
}
- public static boolean hasSorter(@NonNull EntitySorterManner entitySorterManner,
- @NonNull EntityDescriptor entityDescriptor) {
- switch (entitySorterManner) {
- case NONE:
- return false;
- case DECREASING_DIFFICULTY:
- return true;
- case DECREASING_DIFFICULTY_IF_AVAILABLE:
- return entityDescriptor.getDecreasingDifficultySorter() != null;
- default:
- throw new IllegalStateException("The sorterManner ("
- + entitySorterManner + ") is not implemented.");
- }
+ public static boolean hasSorter(EntitySorterManner entitySorterManner,
+ EntityDescriptor entityDescriptor) {
+ return switch (entitySorterManner) {
+ case NONE -> false;
+ case DECREASING_DIFFICULTY, DESCENDING -> true;
+ case DECREASING_DIFFICULTY_IF_AVAILABLE, DESCENDING_IF_AVAILABLE ->
+ entityDescriptor.getDescendingSorter() != null;
+ };
}
- public static @NonNull SelectionSorter determineSorter(
- @NonNull EntitySorterManner entitySorterManner, @NonNull EntityDescriptor entityDescriptor) {
- SelectionSorter sorter;
- switch (entitySorterManner) {
+ public static SelectionSorter determineSorter(
+ EntitySorterManner entitySorterManner, EntityDescriptor entityDescriptor) {
+ return switch (entitySorterManner) {
case NONE:
throw new IllegalStateException("Impossible state: hasSorter() should have returned null.");
- case DECREASING_DIFFICULTY:
- case DECREASING_DIFFICULTY_IF_AVAILABLE:
- sorter = (SelectionSorter) entityDescriptor.getDecreasingDifficultySorter();
+ case DECREASING_DIFFICULTY, DECREASING_DIFFICULTY_IF_AVAILABLE, DESCENDING, DESCENDING_IF_AVAILABLE:
+ var sorter = (SelectionSorter) entityDescriptor.getDescendingSorter();
if (sorter == null) {
- throw new IllegalArgumentException("The sorterManner (" + entitySorterManner
- + ") on entity class (" + entityDescriptor.getEntityClass()
- + ") fails because that entity class's @" + PlanningEntity.class.getSimpleName()
- + " annotation does not declare any difficulty comparison.");
+ throw new IllegalArgumentException(
+ "The sorterManner (%s) on entity class (%s) fails because that entity class's @%s annotation does not declare any difficulty comparison."
+ .formatted(entitySorterManner, entityDescriptor.getEntityClass(),
+ PlanningEntity.class.getSimpleName()));
}
- return sorter;
- default:
- throw new IllegalStateException("The sorterManner ("
- + entitySorterManner + ") is not implemented.");
- }
+ yield sorter;
+ };
}
@Override
diff --git a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySorterManner.java b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySorterManner.java
index 02762b65ee..7a54571e9c 100644
--- a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySorterManner.java
+++ b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/entity/EntitySorterManner.java
@@ -11,6 +11,16 @@
@XmlEnum
public enum EntitySorterManner {
NONE,
+ /**
+ * @deprecated use {@link #DESCENDING} instead
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
DECREASING_DIFFICULTY,
- DECREASING_DIFFICULTY_IF_AVAILABLE;
+ /**
+ * @deprecated use {@link #DESCENDING_IF_AVAILABLE} instead
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
+ DECREASING_DIFFICULTY_IF_AVAILABLE,
+ DESCENDING,
+ DESCENDING_IF_AVAILABLE
}
diff --git a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java
index 6d9f7df4f9..291bca6620 100644
--- a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java
+++ b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/move/MoveSelectorConfig.java
@@ -7,6 +7,7 @@
import jakarta.xml.bind.annotation.XmlSeeAlso;
import jakarta.xml.bind.annotation.XmlType;
+import ai.timefold.solver.core.api.domain.common.ComparatorFactory;
import ai.timefold.solver.core.config.heuristic.selector.SelectorConfig;
import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType;
import ai.timefold.solver.core.config.heuristic.selector.common.SelectionOrder;
@@ -35,7 +36,7 @@
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter;
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory;
-import org.jspecify.annotations.NonNull;
+import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
/**
@@ -67,29 +68,54 @@
"selectionOrder",
"filterClass",
"sorterComparatorClass",
+ "comparatorClass",
"sorterWeightFactoryClass",
+ "comparatorFactoryClass",
"sorterOrder",
"sorterClass",
"probabilityWeightFactoryClass",
"selectedCountLimit",
"fixedProbabilityWeight"
})
+@NullMarked
public abstract class MoveSelectorConfig> extends SelectorConfig {
+ @Nullable
protected SelectionCacheType cacheType = null;
+ @Nullable
protected SelectionOrder selectionOrder = null;
+ @Nullable
protected Class extends SelectionFilter> filterClass = null;
+ /**
+ * @deprecated Deprecated in favor of {@link #comparatorClass}.
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
+ @Nullable
protected Class extends Comparator> sorterComparatorClass = null;
+ @Nullable
+ protected Class extends Comparator> comparatorClass = null;
+ /**
+ * @deprecated Deprecated in favor of {@link #comparatorFactoryClass}.
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
+ @Nullable
protected Class extends SelectionSorterWeightFactory> sorterWeightFactoryClass = null;
+ @Nullable
+ protected Class extends ComparatorFactory> comparatorFactoryClass = null;
+ @Nullable
protected SelectionSorterOrder sorterOrder = null;
+ @Nullable
protected Class extends SelectionSorter> sorterClass = null;
+ @Nullable
protected Class extends SelectionProbabilityWeightFactory> probabilityWeightFactoryClass = null;
+ @Nullable
protected Long selectedCountLimit = null;
+ @Nullable
private Double fixedProbabilityWeight = null;
// ************************************************************************
@@ -120,22 +146,55 @@ public void setFilterClass(@Nullable Class extends SelectionFilter> filterClas
this.filterClass = filterClass;
}
+ /**
+ * @deprecated Deprecated in favor of {@link #getComparatorClass()}
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
public @Nullable Class extends Comparator> getSorterComparatorClass() {
return sorterComparatorClass;
}
+ /**
+ * @deprecated Deprecated in favor of {@link #setComparatorClass(Class)}
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
public void setSorterComparatorClass(@Nullable Class extends Comparator> sorterComparatorClass) {
this.sorterComparatorClass = sorterComparatorClass;
}
+ public @Nullable Class extends Comparator> getComparatorClass() {
+ return comparatorClass;
+ }
+
+ public void setComparatorClass(@Nullable Class extends Comparator> comparatorClass) {
+ this.comparatorClass = comparatorClass;
+ }
+
+ /**
+ * @deprecated Deprecated in favor of {@link #getComparatorFactoryClass()}
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
public @Nullable Class extends SelectionSorterWeightFactory> getSorterWeightFactoryClass() {
return sorterWeightFactoryClass;
}
+ /**
+ * @deprecated Deprecated in favor of {@link #setComparatorFactoryClass(Class)}
+ * @param sorterWeightFactoryClass the class
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
public void setSorterWeightFactoryClass(@Nullable Class extends SelectionSorterWeightFactory> sorterWeightFactoryClass) {
this.sorterWeightFactoryClass = sorterWeightFactoryClass;
}
+ public @Nullable Class extends ComparatorFactory> getComparatorFactoryClass() {
+ return comparatorFactoryClass;
+ }
+
+ public void setComparatorFactoryClass(@Nullable Class extends ComparatorFactory> comparatorFactoryClass) {
+ this.comparatorFactoryClass = comparatorFactoryClass;
+ }
+
public @Nullable SelectionSorterOrder getSorterOrder() {
return sorterOrder;
}
@@ -181,54 +240,74 @@ public void setFixedProbabilityWeight(@Nullable Double fixedProbabilityWeight) {
// With methods
// ************************************************************************
- public @NonNull Config_ withCacheType(@NonNull SelectionCacheType cacheType) {
+ public Config_ withCacheType(SelectionCacheType cacheType) {
this.cacheType = cacheType;
return (Config_) this;
}
- public @NonNull Config_ withSelectionOrder(@NonNull SelectionOrder selectionOrder) {
+ public Config_ withSelectionOrder(SelectionOrder selectionOrder) {
this.selectionOrder = selectionOrder;
return (Config_) this;
}
- public @NonNull Config_ withFilterClass(@NonNull Class extends SelectionFilter> filterClass) {
+ public Config_ withFilterClass(Class extends SelectionFilter> filterClass) {
this.filterClass = filterClass;
return (Config_) this;
}
- public @NonNull Config_ withSorterComparatorClass(@NonNull Class extends Comparator> sorterComparatorClass) {
+ /**
+ * @deprecated Deprecated in favor of {@link #withComparatorClass(Class)}
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
+ public Config_ withSorterComparatorClass(Class extends Comparator> sorterComparatorClass) {
this.sorterComparatorClass = sorterComparatorClass;
return (Config_) this;
}
- public @NonNull Config_ withSorterWeightFactoryClass(
- @NonNull Class extends SelectionSorterWeightFactory> sorterWeightFactoryClass) {
+ public Config_ withComparatorClass(Class extends Comparator> comparatorClass) {
+ this.setComparatorClass(comparatorClass);
+ return (Config_) this;
+ }
+
+ /**
+ * @deprecated Deprecated in favor of {@link #withComparatorFactoryClass(Class)}
+ * @param sorterWeightFactoryClass the factory class
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
+ public Config_ withSorterWeightFactoryClass(
+ Class extends SelectionSorterWeightFactory> sorterWeightFactoryClass) {
this.sorterWeightFactoryClass = sorterWeightFactoryClass;
return (Config_) this;
}
- public @NonNull Config_ withSorterOrder(@NonNull SelectionSorterOrder sorterOrder) {
+ public Config_
+ withComparatorFactoryClass(Class extends ComparatorFactory> comparatorFactoryClass) {
+ this.setComparatorFactoryClass(comparatorFactoryClass);
+ return (Config_) this;
+ }
+
+ public Config_ withSorterOrder(SelectionSorterOrder sorterOrder) {
this.sorterOrder = sorterOrder;
return (Config_) this;
}
- public @NonNull Config_ withSorterClass(@NonNull Class extends SelectionSorter> sorterClass) {
+ public Config_ withSorterClass(Class extends SelectionSorter> sorterClass) {
this.sorterClass = sorterClass;
return (Config_) this;
}
- public @NonNull Config_ withProbabilityWeightFactoryClass(
- @NonNull Class extends SelectionProbabilityWeightFactory> probabilityWeightFactoryClass) {
+ public Config_ withProbabilityWeightFactoryClass(
+ Class extends SelectionProbabilityWeightFactory> probabilityWeightFactoryClass) {
this.probabilityWeightFactoryClass = probabilityWeightFactoryClass;
return (Config_) this;
}
- public @NonNull Config_ withSelectedCountLimit(@NonNull Long selectedCountLimit) {
+ public Config_ withSelectedCountLimit(Long selectedCountLimit) {
this.selectedCountLimit = selectedCountLimit;
return (Config_) this;
}
- public @NonNull Config_ withFixedProbabilityWeight(@NonNull Double fixedProbabilityWeight) {
+ public Config_ withFixedProbabilityWeight(Double fixedProbabilityWeight) {
this.fixedProbabilityWeight = fixedProbabilityWeight;
return (Config_) this;
}
@@ -238,12 +317,12 @@ public void setFixedProbabilityWeight(@Nullable Double fixedProbabilityWeight) {
* except for {@link UnionMoveSelectorConfig} and {@link CartesianProductMoveSelectorConfig}.
*
*/
- public void extractLeafMoveSelectorConfigsIntoList(@NonNull List<@NonNull MoveSelectorConfig> leafMoveSelectorConfigList) {
+ public void extractLeafMoveSelectorConfigsIntoList(List leafMoveSelectorConfigList) {
leafMoveSelectorConfigList.add(this);
}
@Override
- public @NonNull Config_ inherit(@NonNull Config_ inheritedConfig) {
+ public Config_ inherit(Config_ inheritedConfig) {
inheritCommon(inheritedConfig);
return (Config_) this;
}
@@ -251,14 +330,16 @@ public void extractLeafMoveSelectorConfigsIntoList(@NonNull List<@NonNull MoveSe
/**
* Does not inherit subclass properties because this class and {@code foldedConfig} can be of a different type.
*/
- public void inheritFolded(@NonNull MoveSelectorConfig> foldedConfig) {
+ public void inheritFolded(MoveSelectorConfig> foldedConfig) {
inheritCommon(foldedConfig);
}
- protected void visitCommonReferencedClasses(@NonNull Consumer> classVisitor) {
+ protected void visitCommonReferencedClasses(Consumer> classVisitor) {
classVisitor.accept(filterClass);
classVisitor.accept(sorterComparatorClass);
+ classVisitor.accept(comparatorClass);
classVisitor.accept(sorterWeightFactoryClass);
+ classVisitor.accept(comparatorFactoryClass);
classVisitor.accept(sorterClass);
classVisitor.accept(probabilityWeightFactoryClass);
}
@@ -269,8 +350,12 @@ private void inheritCommon(MoveSelectorConfig> inheritedConfig) {
filterClass = ConfigUtils.inheritOverwritableProperty(filterClass, inheritedConfig.getFilterClass());
sorterComparatorClass = ConfigUtils.inheritOverwritableProperty(
sorterComparatorClass, inheritedConfig.getSorterComparatorClass());
+ comparatorClass = ConfigUtils.inheritOverwritableProperty(
+ comparatorClass, inheritedConfig.getComparatorClass());
sorterWeightFactoryClass = ConfigUtils.inheritOverwritableProperty(
sorterWeightFactoryClass, inheritedConfig.getSorterWeightFactoryClass());
+ comparatorFactoryClass = ConfigUtils.inheritOverwritableProperty(
+ comparatorFactoryClass, inheritedConfig.getComparatorFactoryClass());
sorterOrder = ConfigUtils.inheritOverwritableProperty(
sorterOrder, inheritedConfig.getSorterOrder());
sorterClass = ConfigUtils.inheritOverwritableProperty(
diff --git a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java
index a7b41b3538..0477d37d57 100644
--- a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java
+++ b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSelectorConfig.java
@@ -7,6 +7,7 @@
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlType;
+import ai.timefold.solver.core.api.domain.common.ComparatorFactory;
import ai.timefold.solver.core.api.domain.variable.PlanningVariable;
import ai.timefold.solver.core.config.heuristic.selector.SelectorConfig;
import ai.timefold.solver.core.config.heuristic.selector.common.SelectionCacheType;
@@ -20,7 +21,7 @@
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter;
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory;
-import org.jspecify.annotations.NonNull;
+import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@XmlType(propOrder = {
@@ -34,45 +35,75 @@
"filterClass",
"sorterManner",
"sorterComparatorClass",
+ "comparatorClass",
"sorterWeightFactoryClass",
+ "comparatorFactoryClass",
"sorterOrder",
"sorterClass",
"probabilityWeightFactoryClass",
"selectedCountLimit"
})
+@NullMarked
public class ValueSelectorConfig extends SelectorConfig {
@XmlAttribute
+ @Nullable
protected String id = null;
@XmlAttribute
+ @Nullable
protected String mimicSelectorRef = null;
+ @Nullable
protected Class> downcastEntityClass = null;
@XmlAttribute
+ @Nullable
protected String variableName = null;
+ @Nullable
protected SelectionCacheType cacheType = null;
+ @Nullable
protected SelectionOrder selectionOrder = null;
@XmlElement(name = "nearbySelection")
+ @Nullable
protected NearbySelectionConfig nearbySelectionConfig = null;
+ @Nullable
protected Class extends SelectionFilter> filterClass = null;
+ @Nullable
protected ValueSorterManner sorterManner = null;
+ /**
+ * @deprecated Deprecated in favor of {@link #comparatorClass}.
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
+ @Nullable
protected Class extends Comparator> sorterComparatorClass = null;
+ @Nullable
+ protected Class extends Comparator> comparatorClass = null;
+ /**
+ * @deprecated Deprecated in favor of {@link #comparatorFactoryClass}.
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
+ @Nullable
protected Class extends SelectionSorterWeightFactory> sorterWeightFactoryClass = null;
+ @Nullable
+ protected Class extends ComparatorFactory> comparatorFactoryClass = null;
+ @Nullable
protected SelectionSorterOrder sorterOrder = null;
+ @Nullable
protected Class extends SelectionSorter> sorterClass = null;
+ @Nullable
protected Class extends SelectionProbabilityWeightFactory> probabilityWeightFactoryClass = null;
+ @Nullable
protected Long selectedCountLimit = null;
public ValueSelectorConfig() {
}
- public ValueSelectorConfig(@NonNull String variableName) {
+ public ValueSelectorConfig(String variableName) {
this.variableName = variableName;
}
@@ -154,22 +185,55 @@ public void setSorterManner(@Nullable ValueSorterManner sorterManner) {
this.sorterManner = sorterManner;
}
+ /**
+ * @deprecated Deprecated in favor of {@link #getComparatorClass()}
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
public @Nullable Class extends Comparator> getSorterComparatorClass() {
return sorterComparatorClass;
}
+ /**
+ * @deprecated Deprecated in favor of {@link #setComparatorClass(Class)}
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
public void setSorterComparatorClass(@Nullable Class extends Comparator> sorterComparatorClass) {
this.sorterComparatorClass = sorterComparatorClass;
}
+ public @Nullable Class extends Comparator> getComparatorClass() {
+ return comparatorClass;
+ }
+
+ public void setComparatorClass(@Nullable Class extends Comparator> comparatorClass) {
+ this.comparatorClass = comparatorClass;
+ }
+
+ /**
+ * @deprecated Deprecated in favor of {@link #getComparatorFactoryClass()}
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
public @Nullable Class extends SelectionSorterWeightFactory> getSorterWeightFactoryClass() {
return sorterWeightFactoryClass;
}
+ /**
+ * @deprecated Deprecated in favor of {@link #setComparatorFactoryClass(Class)}
+ * @param sorterWeightFactoryClass the class
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
public void setSorterWeightFactoryClass(@Nullable Class extends SelectionSorterWeightFactory> sorterWeightFactoryClass) {
this.sorterWeightFactoryClass = sorterWeightFactoryClass;
}
+ public @Nullable Class extends ComparatorFactory> getComparatorFactoryClass() {
+ return comparatorFactoryClass;
+ }
+
+ public void setComparatorFactoryClass(@Nullable Class extends ComparatorFactory> comparatorFactoryClass) {
+ this.comparatorFactoryClass = comparatorFactoryClass;
+ }
+
public @Nullable SelectionSorterOrder getSorterOrder() {
return sorterOrder;
}
@@ -207,79 +271,99 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) {
// With methods
// ************************************************************************
- public @NonNull ValueSelectorConfig withId(@NonNull String id) {
+ public ValueSelectorConfig withId(String id) {
this.setId(id);
return this;
}
- public @NonNull ValueSelectorConfig withMimicSelectorRef(@NonNull String mimicSelectorRef) {
+ public ValueSelectorConfig withMimicSelectorRef(String mimicSelectorRef) {
this.setMimicSelectorRef(mimicSelectorRef);
return this;
}
- public @NonNull ValueSelectorConfig withDowncastEntityClass(@NonNull Class> entityClass) {
+ public ValueSelectorConfig withDowncastEntityClass(Class> entityClass) {
this.setDowncastEntityClass(entityClass);
return this;
}
- public @NonNull ValueSelectorConfig withVariableName(@NonNull String variableName) {
+ public ValueSelectorConfig withVariableName(String variableName) {
this.setVariableName(variableName);
return this;
}
- public @NonNull ValueSelectorConfig withCacheType(@NonNull SelectionCacheType cacheType) {
+ public ValueSelectorConfig withCacheType(SelectionCacheType cacheType) {
this.setCacheType(cacheType);
return this;
}
- public @NonNull ValueSelectorConfig withSelectionOrder(@NonNull SelectionOrder selectionOrder) {
+ public ValueSelectorConfig withSelectionOrder(SelectionOrder selectionOrder) {
this.setSelectionOrder(selectionOrder);
return this;
}
- public @NonNull ValueSelectorConfig withNearbySelectionConfig(@NonNull NearbySelectionConfig nearbySelectionConfig) {
+ public ValueSelectorConfig withNearbySelectionConfig(NearbySelectionConfig nearbySelectionConfig) {
this.setNearbySelectionConfig(nearbySelectionConfig);
return this;
}
- public @NonNull ValueSelectorConfig withFilterClass(@NonNull Class extends SelectionFilter> filterClass) {
+ public ValueSelectorConfig withFilterClass(Class extends SelectionFilter> filterClass) {
this.setFilterClass(filterClass);
return this;
}
- public @NonNull ValueSelectorConfig withSorterManner(@NonNull ValueSorterManner sorterManner) {
+ public ValueSelectorConfig withSorterManner(ValueSorterManner sorterManner) {
this.setSorterManner(sorterManner);
return this;
}
- public @NonNull ValueSelectorConfig withSorterComparatorClass(@NonNull Class extends Comparator> comparatorClass) {
+ /**
+ * @deprecated Deprecated in favor of {@link #withComparatorClass(Class)}
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
+ public ValueSelectorConfig withSorterComparatorClass(Class extends Comparator> comparatorClass) {
this.setSorterComparatorClass(comparatorClass);
return this;
}
- public @NonNull ValueSelectorConfig
- withSorterWeightFactoryClass(@NonNull Class extends SelectionSorterWeightFactory> weightFactoryClass) {
+ public ValueSelectorConfig withComparatorClass(Class extends Comparator> comparatorClass) {
+ this.setComparatorClass(comparatorClass);
+ return this;
+ }
+
+ /**
+ * @deprecated Deprecated in favor of {@link #withComparatorFactoryClass(Class)}
+ * @param weightFactoryClass the factory class
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
+ public ValueSelectorConfig
+ withSorterWeightFactoryClass(Class extends SelectionSorterWeightFactory> weightFactoryClass) {
this.setSorterWeightFactoryClass(weightFactoryClass);
return this;
}
- public @NonNull ValueSelectorConfig withSorterOrder(@NonNull SelectionSorterOrder sorterOrder) {
+ public ValueSelectorConfig
+ withComparatorFactoryClass(Class extends ComparatorFactory> comparatorFactoryClass) {
+ this.setComparatorFactoryClass(comparatorFactoryClass);
+ return this;
+ }
+
+ public ValueSelectorConfig withSorterOrder(SelectionSorterOrder sorterOrder) {
this.setSorterOrder(sorterOrder);
return this;
}
- public @NonNull ValueSelectorConfig withSorterClass(@NonNull Class extends SelectionSorter> sorterClass) {
+ public ValueSelectorConfig withSorterClass(Class extends SelectionSorter> sorterClass) {
this.setSorterClass(sorterClass);
return this;
}
- public @NonNull ValueSelectorConfig
- withProbabilityWeightFactoryClass(@NonNull Class extends SelectionProbabilityWeightFactory> factoryClass) {
+ public ValueSelectorConfig
+ withProbabilityWeightFactoryClass(Class extends SelectionProbabilityWeightFactory> factoryClass) {
this.setProbabilityWeightFactoryClass(factoryClass);
return this;
}
- public @NonNull ValueSelectorConfig withSelectedCountLimit(long selectedCountLimit) {
+ public ValueSelectorConfig withSelectedCountLimit(long selectedCountLimit) {
this.setSelectedCountLimit(selectedCountLimit);
return this;
}
@@ -289,7 +373,7 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) {
// ************************************************************************
@Override
- public @NonNull ValueSelectorConfig inherit(@NonNull ValueSelectorConfig inheritedConfig) {
+ public ValueSelectorConfig inherit(ValueSelectorConfig inheritedConfig) {
id = ConfigUtils.inheritOverwritableProperty(id, inheritedConfig.getId());
mimicSelectorRef = ConfigUtils.inheritOverwritableProperty(mimicSelectorRef,
inheritedConfig.getMimicSelectorRef());
@@ -304,8 +388,12 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) {
sorterManner, inheritedConfig.getSorterManner());
sorterComparatorClass = ConfigUtils.inheritOverwritableProperty(
sorterComparatorClass, inheritedConfig.getSorterComparatorClass());
+ comparatorClass = ConfigUtils.inheritOverwritableProperty(
+ comparatorClass, inheritedConfig.getComparatorClass());
sorterWeightFactoryClass = ConfigUtils.inheritOverwritableProperty(
sorterWeightFactoryClass, inheritedConfig.getSorterWeightFactoryClass());
+ comparatorFactoryClass = ConfigUtils.inheritOverwritableProperty(
+ comparatorFactoryClass, inheritedConfig.getComparatorFactoryClass());
sorterOrder = ConfigUtils.inheritOverwritableProperty(
sorterOrder, inheritedConfig.getSorterOrder());
sorterClass = ConfigUtils.inheritOverwritableProperty(
@@ -318,19 +406,21 @@ public void setSelectedCountLimit(@Nullable Long selectedCountLimit) {
}
@Override
- public @NonNull ValueSelectorConfig copyConfig() {
+ public ValueSelectorConfig copyConfig() {
return new ValueSelectorConfig().inherit(this);
}
@Override
- public void visitReferencedClasses(@NonNull Consumer> classVisitor) {
+ public void visitReferencedClasses(Consumer> classVisitor) {
classVisitor.accept(downcastEntityClass);
if (nearbySelectionConfig != null) {
nearbySelectionConfig.visitReferencedClasses(classVisitor);
}
classVisitor.accept(filterClass);
classVisitor.accept(sorterComparatorClass);
+ classVisitor.accept(comparatorClass);
classVisitor.accept(sorterWeightFactoryClass);
+ classVisitor.accept(comparatorFactoryClass);
classVisitor.accept(sorterClass);
classVisitor.accept(probabilityWeightFactoryClass);
}
@@ -340,48 +430,32 @@ public String toString() {
return getClass().getSimpleName() + "(" + variableName + ")";
}
- public static boolean hasSorter(@NonNull ValueSorterManner valueSorterManner,
- @NonNull GenuineVariableDescriptor variableDescriptor) {
- switch (valueSorterManner) {
- case NONE:
- return false;
- case INCREASING_STRENGTH:
- case DECREASING_STRENGTH:
- return true;
- case INCREASING_STRENGTH_IF_AVAILABLE:
- return variableDescriptor.getIncreasingStrengthSorter() != null;
- case DECREASING_STRENGTH_IF_AVAILABLE:
- return variableDescriptor.getDecreasingStrengthSorter() != null;
- default:
- throw new IllegalStateException("The sorterManner ("
- + valueSorterManner + ") is not implemented.");
- }
- }
-
- public static @NonNull SelectionSorter determineSorter(
- @NonNull ValueSorterManner valueSorterManner, @NonNull GenuineVariableDescriptor variableDescriptor) {
- SelectionSorter sorter;
- switch (valueSorterManner) {
- case NONE:
- throw new IllegalStateException("Impossible state: hasSorter() should have returned null.");
- case INCREASING_STRENGTH:
- case INCREASING_STRENGTH_IF_AVAILABLE:
- sorter = variableDescriptor.getIncreasingStrengthSorter();
- break;
- case DECREASING_STRENGTH:
- case DECREASING_STRENGTH_IF_AVAILABLE:
- sorter = variableDescriptor.getDecreasingStrengthSorter();
- break;
- default:
- throw new IllegalStateException("The sorterManner ("
- + valueSorterManner + ") is not implemented.");
- }
+ public static boolean hasSorter(ValueSorterManner valueSorterManner,
+ GenuineVariableDescriptor variableDescriptor) {
+ return switch (valueSorterManner) {
+ case NONE -> false;
+ case INCREASING_STRENGTH, DECREASING_STRENGTH, ASCENDING, DESCENDING -> true;
+ case INCREASING_STRENGTH_IF_AVAILABLE, ASCENDING_IF_AVAILABLE ->
+ variableDescriptor.getAscendingSorter() != null;
+ case DECREASING_STRENGTH_IF_AVAILABLE, DESCENDING_IF_AVAILABLE ->
+ variableDescriptor.getDescendingSorter() != null;
+ };
+ }
+
+ public static SelectionSorter determineSorter(
+ ValueSorterManner valueSorterManner, GenuineVariableDescriptor variableDescriptor) {
+ SelectionSorter sorter = switch (valueSorterManner) {
+ case NONE -> throw new IllegalStateException("Impossible state: hasSorter() should have returned null.");
+ case INCREASING_STRENGTH, INCREASING_STRENGTH_IF_AVAILABLE, ASCENDING, ASCENDING_IF_AVAILABLE ->
+ variableDescriptor.getAscendingSorter();
+ case DECREASING_STRENGTH, DECREASING_STRENGTH_IF_AVAILABLE, DESCENDING, DESCENDING_IF_AVAILABLE ->
+ variableDescriptor.getDescendingSorter();
+ };
if (sorter == null) {
- throw new IllegalArgumentException("The sorterManner (" + valueSorterManner
- + ") on entity class (" + variableDescriptor.getEntityDescriptor().getEntityClass()
- + ")'s variable (" + variableDescriptor.getVariableName()
- + ") fails because that variable getter's @" + PlanningVariable.class.getSimpleName()
- + " annotation does not declare any strength comparison.");
+ throw new IllegalArgumentException(
+ "The sorterManner (%s) on entity class (%s)'s variable (%s) fails because that variable getter's @%s annotation does not declare any strength comparison."
+ .formatted(valueSorterManner, variableDescriptor.getEntityDescriptor().getEntityClass(),
+ variableDescriptor.getVariableName(), PlanningVariable.class.getSimpleName()));
}
return sorter;
}
diff --git a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSorterManner.java b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSorterManner.java
index dfe5d79918..7702d36b74 100644
--- a/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSorterManner.java
+++ b/core/src/main/java/ai/timefold/solver/core/config/heuristic/selector/value/ValueSorterManner.java
@@ -11,10 +11,30 @@
@XmlEnum
public enum ValueSorterManner {
NONE(true),
+ /**
+ * @deprecated use {@link #ASCENDING} instead
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
INCREASING_STRENGTH(false),
+ /**
+ * @deprecated use {@link #ASCENDING_IF_AVAILABLE} instead
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
INCREASING_STRENGTH_IF_AVAILABLE(true),
+ /**
+ * @deprecated use {@link #DESCENDING} instead
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
DECREASING_STRENGTH(false),
- DECREASING_STRENGTH_IF_AVAILABLE(true);
+ /**
+ * @deprecated use {@link #DESCENDING_IF_AVAILABLE} instead
+ */
+ @Deprecated(forRemoval = true, since = "1.28.0")
+ DECREASING_STRENGTH_IF_AVAILABLE(true),
+ ASCENDING(false),
+ ASCENDING_IF_AVAILABLE(true),
+ DESCENDING(false),
+ DESCENDING_IF_AVAILABLE(true);
private final boolean nonePossible;
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 f14e513706..c125cb8214 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
@@ -116,8 +116,6 @@ private EntityPlacerConfig> buildDefaultEntityPlacerConfig(HeuristicConfigPoli
if (listVariableDescriptor == null) {
return Optional.empty();
}
- failIfConfigured(phaseConfig.getConstructionHeuristicType(), "constructionHeuristicType");
- failIfConfigured(phaseConfig.getMoveSelectorConfigList(), "moveSelectorConfigList");
// When an entity has both list and basic variables,
// the CH configuration will require two separate placers to initialize each variable,
// which cannot be deduced automatically by default, since a single placer would be returned
@@ -130,13 +128,6 @@ The entity (%s) has both basic and list variables and cannot be deduced automati
return Optional.of(listVariableDescriptor);
}
- private static void failIfConfigured(Object configValue, String configName) {
- if (configValue != null) {
- throw new IllegalArgumentException("Construction Heuristic phase with a list variable does not support "
- + configName + " configuration. Remove the " + configName + " (" + configValue + ") from the config.");
- }
- }
-
@SuppressWarnings("rawtypes")
public static EntityPlacerConfig buildListVariableQueuedValuePlacerConfig(HeuristicConfigPolicy> configPolicy,
ListVariableDescriptor> variableDescriptor) {
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/entity/descriptor/EntityDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/entity/descriptor/EntityDescriptor.java
index b49e018d32..48df189078 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/entity/descriptor/EntityDescriptor.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/entity/descriptor/EntityDescriptor.java
@@ -63,9 +63,9 @@
import ai.timefold.solver.core.impl.domain.variable.inverserelation.InverseRelationShadowVariableDescriptor;
import ai.timefold.solver.core.impl.domain.variable.nextprev.NextElementShadowVariableDescriptor;
import ai.timefold.solver.core.impl.domain.variable.nextprev.PreviousElementShadowVariableDescriptor;
+import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorFactorySelectionSorter;
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorSelectionSorter;
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter;
-import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.WeightFactorySelectionSorter;
import ai.timefold.solver.core.impl.move.director.MoveDirector;
import ai.timefold.solver.core.impl.neighborhood.maybeapi.stream.enumerating.function.UniEnumeratingFilter;
import ai.timefold.solver.core.impl.util.CollectionUtils;
@@ -111,7 +111,7 @@ public class EntityDescriptor {
// Only declared movable filter, excludes inherited and descending movable filters
private MovableFilter declaredMovableEntityFilter;
- private SelectionSorter decreasingDifficultySorter;
+ private SelectionSorter descendingSorter;
// Only declared variable descriptors, excludes inherited variable descriptors
private Map> declaredGenuineVariableDescriptorMap;
@@ -243,7 +243,7 @@ private void processEntityAnnotations() {
() -> new IllegalStateException("Impossible state as the previous if block would fail first."));
}
processMovable(entityAnnotation);
- processDifficulty(entityAnnotation);
+ processSorting(entityAnnotation);
}
/**
@@ -263,35 +263,67 @@ private void processMovable(PlanningEntity entityAnnotation) {
}
}
- private void processDifficulty(PlanningEntity entityAnnotation) {
+ private void processSorting(PlanningEntity entityAnnotation) {
if (entityAnnotation == null) {
return;
}
var difficultyComparatorClass = entityAnnotation.difficultyComparatorClass();
- if (difficultyComparatorClass == PlanningEntity.NullDifficultyComparator.class) {
+ if (difficultyComparatorClass != null
+ && PlanningEntity.NullComparator.class.isAssignableFrom(difficultyComparatorClass)) {
difficultyComparatorClass = null;
}
+ var comparatorClass = entityAnnotation.comparatorClass();
+ if (comparatorClass != null
+ && PlanningEntity.NullComparator.class.isAssignableFrom(comparatorClass)) {
+ comparatorClass = null;
+ }
+ if (difficultyComparatorClass != null && comparatorClass != null) {
+ throw new IllegalStateException(
+ "The entityClass (%s) cannot have a %s (%s) and a %s (%s) at the same time.".formatted(getEntityClass(),
+ "difficultyComparatorClass", difficultyComparatorClass.getName(), "comparatorClass",
+ comparatorClass.getName()));
+ }
var difficultyWeightFactoryClass = entityAnnotation.difficultyWeightFactoryClass();
- if (difficultyWeightFactoryClass == PlanningEntity.NullDifficultyWeightFactory.class) {
+ if (difficultyWeightFactoryClass != null
+ && PlanningEntity.NullComparatorFactory.class.isAssignableFrom(difficultyWeightFactoryClass)) {
difficultyWeightFactoryClass = null;
}
- if (difficultyComparatorClass != null && difficultyWeightFactoryClass != null) {
- throw new IllegalStateException(
- "The entityClass (%s) cannot have a difficultyComparatorClass (%s) and a difficultyWeightFactoryClass (%s) at the same time."
- .formatted(entityClass, difficultyComparatorClass.getName(),
- difficultyWeightFactoryClass.getName()));
+ var comparatorFactoryClass = entityAnnotation.comparatorFactoryClass();
+ if (comparatorFactoryClass != null
+ && PlanningEntity.NullComparatorFactory.class.isAssignableFrom(comparatorFactoryClass)) {
+ comparatorFactoryClass = null;
}
+ if (difficultyWeightFactoryClass != null && comparatorFactoryClass != null) {
+ throw new IllegalStateException(
+ "The entityClass (%s) cannot have a %s (%s) and a %s (%s) at the same time.".formatted(getEntityClass(),
+ "difficultyWeightFactoryClass", difficultyWeightFactoryClass.getName(), "comparatorFactoryClass",
+ comparatorFactoryClass.getName()));
+ }
+ // Selected settings
+ var selectedComparatorPropertyName = "comparatorClass";
+ var selectedComparatorClass = comparatorClass;
+ var selectedComparatorFactoryPropertyName = "comparatorFactoryClass";
+ var selectedComparatorFactoryClass = comparatorFactoryClass;
if (difficultyComparatorClass != null) {
- var difficultyComparator = ConfigUtils.newInstance(this::toString,
- "difficultyComparatorClass", difficultyComparatorClass);
- decreasingDifficultySorter = new ComparatorSelectionSorter<>(
- difficultyComparator, SelectionSorterOrder.DESCENDING);
+ selectedComparatorPropertyName = "difficultyComparatorClass";
+ selectedComparatorClass = difficultyComparatorClass;
}
if (difficultyWeightFactoryClass != null) {
- var difficultyWeightFactory = ConfigUtils.newInstance(this::toString,
- "difficultyWeightFactoryClass", difficultyWeightFactoryClass);
- decreasingDifficultySorter = new WeightFactorySelectionSorter<>(
- difficultyWeightFactory, SelectionSorterOrder.DESCENDING);
+ selectedComparatorFactoryPropertyName = "difficultyWeightFactoryClass";
+ selectedComparatorFactoryClass = difficultyWeightFactoryClass;
+ }
+ if (selectedComparatorClass != null && selectedComparatorFactoryClass != null) {
+ throw new IllegalStateException("The entityClass (%s) cannot have a %s (%s) and a %s (%s) at the same time."
+ .formatted(entityClass, selectedComparatorPropertyName, selectedComparatorClass.getName(),
+ selectedComparatorFactoryPropertyName, selectedComparatorFactoryClass.getName()));
+ }
+ if (selectedComparatorClass != null) {
+ var comparator = ConfigUtils.newInstance(this::toString, selectedComparatorPropertyName, selectedComparatorClass);
+ descendingSorter = new ComparatorSelectionSorter<>(comparator, SelectionSorterOrder.DESCENDING);
+ } else if (selectedComparatorFactoryClass != null) {
+ var comparator = ConfigUtils.newInstance(this::toString, selectedComparatorFactoryPropertyName,
+ selectedComparatorFactoryClass);
+ descendingSorter = new ComparatorFactorySelectionSorter<>(comparator, SelectionSorterOrder.DESCENDING);
}
}
@@ -643,8 +675,8 @@ public UniEnumeratingFilter getEntityMovablePredicate() {
return (UniEnumeratingFilter) entityMovablePredicate;
}
- public SelectionSorter getDecreasingDifficultySorter() {
- return decreasingDifficultySorter;
+ public SelectionSorter getDescendingSorter() {
+ return descendingSorter;
}
public Collection getGenuineVariableNameSet() {
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/BasicVariableDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/BasicVariableDescriptor.java
index 8972425907..920cbdc56a 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/BasicVariableDescriptor.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/BasicVariableDescriptor.java
@@ -1,5 +1,8 @@
package ai.timefold.solver.core.impl.domain.variable.descriptor;
+import java.util.Comparator;
+
+import ai.timefold.solver.core.api.domain.common.ComparatorFactory;
import ai.timefold.solver.core.api.domain.variable.PlanningVariable;
import ai.timefold.solver.core.api.domain.variable.PlanningVariableGraphType;
import ai.timefold.solver.core.impl.domain.common.accessor.MemberAccessor;
@@ -41,8 +44,60 @@ protected void processPropertyAnnotations(DescriptorPolicy descriptorPolicy) {
processAllowsUnassigned(planningVariableAnnotation);
processChained(planningVariableAnnotation);
processValueRangeRefs(descriptorPolicy, planningVariableAnnotation.valueRangeProviderRefs());
- processStrength(planningVariableAnnotation.strengthComparatorClass(),
- planningVariableAnnotation.strengthWeightFactoryClass());
+ var sortingProperties = assertSortingProperties(planningVariableAnnotation);
+ processSorting(sortingProperties.comparatorPropertyName(), sortingProperties.comparatorClass(),
+ sortingProperties.comparatorFactoryPropertyName(), sortingProperties.comparatorFactoryClass());
+ }
+
+ private SortingProperties assertSortingProperties(PlanningVariable planningVariableAnnotation) {
+ // Comparator property
+ var strengthComparatorClass = planningVariableAnnotation.strengthComparatorClass();
+ var comparatorClass = planningVariableAnnotation.comparatorClass();
+ if (strengthComparatorClass != null
+ && PlanningVariable.NullComparator.class.isAssignableFrom(strengthComparatorClass)) {
+ strengthComparatorClass = null;
+ }
+ if (comparatorClass != null && PlanningVariable.NullComparator.class.isAssignableFrom(comparatorClass)) {
+ comparatorClass = null;
+ }
+ if (strengthComparatorClass != null && comparatorClass != null) {
+ throw new IllegalStateException(
+ "The entityClass (%s) property (%s) cannot have a %s (%s) and a %s (%s) at the same time.".formatted(
+ entityDescriptor.getEntityClass(), variableMemberAccessor.getName(), "strengthComparatorClass",
+ strengthComparatorClass.getName(), "comparatorClass", comparatorClass.getName()));
+ }
+ // Comparator factory property
+ var strengthWeightFactoryClass = planningVariableAnnotation.strengthWeightFactoryClass();
+ var comparatorFactoryClass = planningVariableAnnotation.comparatorFactoryClass();
+ if (strengthWeightFactoryClass != null
+ && PlanningVariable.NullComparatorFactory.class.isAssignableFrom(strengthWeightFactoryClass)) {
+ strengthWeightFactoryClass = null;
+ }
+ if (comparatorFactoryClass != null
+ && PlanningVariable.NullComparatorFactory.class.isAssignableFrom(comparatorFactoryClass)) {
+ comparatorFactoryClass = null;
+ }
+ if (strengthWeightFactoryClass != null && comparatorFactoryClass != null) {
+ throw new IllegalStateException(
+ "The entityClass (%s) property (%s) cannot have a %s (%s) and a %s (%s) at the same time.".formatted(
+ entityDescriptor.getEntityClass(), variableMemberAccessor.getName(), "strengthWeightFactoryClass",
+ strengthWeightFactoryClass.getName(), "comparatorFactoryClass", comparatorFactoryClass.getName()));
+ }
+ // Selected settings
+ var selectedComparatorPropertyName = "comparatorClass";
+ var selectedComparatorClass = comparatorClass;
+ var selectedComparatorFactoryPropertyName = "comparatorFactoryClass";
+ var selectedComparatorFactoryClass = comparatorFactoryClass;
+ if (strengthComparatorClass != null) {
+ selectedComparatorPropertyName = "strengthComparatorClass";
+ selectedComparatorClass = strengthComparatorClass;
+ }
+ if (strengthWeightFactoryClass != null) {
+ selectedComparatorFactoryPropertyName = "strengthWeightFactoryClass";
+ selectedComparatorFactoryClass = strengthWeightFactoryClass;
+ }
+ return new SortingProperties(selectedComparatorPropertyName, selectedComparatorClass,
+ selectedComparatorFactoryPropertyName, selectedComparatorFactoryClass);
}
private void processAllowsUnassigned(PlanningVariable planningVariableAnnotation) {
@@ -130,4 +185,9 @@ public SelectionFilter getMovableChainedTrailingValueFilter()
return movableChainedTrailingValueFilter;
}
+ private record SortingProperties(String comparatorPropertyName, Class extends Comparator> comparatorClass,
+ String comparatorFactoryPropertyName, Class extends ComparatorFactory> comparatorFactoryClass) {
+
+ }
+
}
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java
index 28d1bc48be..a749abf63f 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/domain/variable/descriptor/GenuineVariableDescriptor.java
@@ -8,6 +8,7 @@
import java.util.Comparator;
import java.util.stream.Stream;
+import ai.timefold.solver.core.api.domain.common.ComparatorFactory;
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider;
import ai.timefold.solver.core.api.domain.variable.PlanningListVariable;
@@ -19,10 +20,9 @@
import ai.timefold.solver.core.impl.domain.policy.DescriptorPolicy;
import ai.timefold.solver.core.impl.domain.valuerange.descriptor.FromSolutionPropertyValueRangeDescriptor;
import ai.timefold.solver.core.impl.domain.valuerange.descriptor.ValueRangeDescriptor;
+import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorFactorySelectionSorter;
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.ComparatorSelectionSorter;
import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorter;
-import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.SelectionSorterWeightFactory;
-import ai.timefold.solver.core.impl.heuristic.selector.common.decorator.WeightFactorySelectionSorter;
/**
* @param the solution type, the class with the {@link PlanningSolution} annotation
@@ -30,8 +30,8 @@
public abstract class GenuineVariableDescriptor extends VariableDescriptor {
private ValueRangeDescriptor valueRangeDescriptor;
- private SelectionSorter increasingStrengthSorter;
- private SelectionSorter decreasingStrengthSorter;
+ private SelectionSorter ascendingSorter;
+ private SelectionSorter descendingSorter;
// ************************************************************************
// Constructors and simple getters/setters
@@ -155,36 +155,33 @@ private ValueRangeDescriptor buildValueRangeDescriptor(DescriptorPoli
}
}
- protected void processStrength(Class extends Comparator> strengthComparatorClass,
- Class extends SelectionSorterWeightFactory> strengthWeightFactoryClass) {
- if (strengthComparatorClass == PlanningVariable.NullStrengthComparator.class) {
- strengthComparatorClass = null;
+ @SuppressWarnings("rawtypes")
+ protected void processSorting(String comparatorPropertyName, Class extends Comparator> comparatorClass,
+ String comparatorFactoryPropertyName, Class extends ComparatorFactory> comparatorFactoryClass) {
+ if (comparatorClass != null && PlanningVariable.NullComparator.class.isAssignableFrom(comparatorClass)) {
+ comparatorClass = null;
}
- if (strengthWeightFactoryClass == PlanningVariable.NullStrengthWeightFactory.class) {
- strengthWeightFactoryClass = null;
+ if (comparatorFactoryClass != null
+ && PlanningVariable.NullComparatorFactory.class.isAssignableFrom(comparatorFactoryClass)) {
+ comparatorFactoryClass = null;
}
- if (strengthComparatorClass != null && strengthWeightFactoryClass != null) {
- throw new IllegalStateException("The entityClass (" + entityDescriptor.getEntityClass()
- + ") property (" + variableMemberAccessor.getName()
- + ") cannot have a strengthComparatorClass (" + strengthComparatorClass.getName()
- + ") and a strengthWeightFactoryClass (" + strengthWeightFactoryClass.getName()
- + ") at the same time.");
+ if (comparatorClass != null && comparatorFactoryClass != null) {
+ throw new IllegalStateException(
+ "The entityClass (%s) property (%s) cannot have a %s (%s) and a %s (%s) at the same time.".formatted(
+ entityDescriptor.getEntityClass(), variableMemberAccessor.getName(), comparatorPropertyName,
+ comparatorClass.getName(), comparatorFactoryPropertyName, comparatorFactoryClass.getName()));
}
- if (strengthComparatorClass != null) {
- Comparator