Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package ai.timefold.solver.core.api.domain.specification;

import java.util.function.Consumer;

/**
* Builder for configuring a cascading update shadow variable.
*
* @param <S> the solution type
* @param <E> the entity type
*/
public interface CascadingUpdateShadowBuilder<S, E> {

CascadingUpdateShadowBuilder<S, E> updateMethod(Consumer<E> updater);

CascadingUpdateShadowBuilder<S, E> sources(String... sourcePaths);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package ai.timefold.solver.core.api.domain.specification;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;

import ai.timefold.solver.core.api.domain.solution.cloner.SolutionCloner;

/**
* Describes how to clone a planning solution using lambdas.
* <p>
* The specification contains a complete cloning recipe: for every cloneable class (solution, entities,
* {@code @DeepPlanningClone} facts), it lists all fields with their getter/setter lambdas and
* a pre-classified {@link DeepCloneDecision} that determines how each field value is handled during cloning.
*
* @param solutionFactory creates a new empty solution instance
* @param solutionProperties all fields on the solution class (including inherited), with clone decisions
* @param cloneableClasses cloning recipes for entities and {@code @DeepPlanningClone} facts, keyed by exact class
* @param entityClasses all entity classes (for runtime entity detection in cloneMap lookups)
* @param deepCloneClasses all {@code @DeepPlanningClone}-annotated classes
* @param customCloner optional custom cloner (null if using lambda-based cloning)
* @param <S> the solution type
*/
public record CloningSpecification<S>(
Supplier<S> solutionFactory,
List<PropertyCopyDescriptor> solutionProperties,
Map<Class<?>, CloneableClassDescriptor> cloneableClasses,
Set<Class<?>> entityClasses,
Set<Class<?>> deepCloneClasses,
SolutionCloner<S> customCloner) {

/**
* Describes how to copy a single field during cloning.
*
* @param name the field name (for debugging)
* @param getter reads the field value from an object
* @param setter writes the field value to an object
* @param deepCloneDecision how this field's value should be handled during cloning
* @param cloneTimeValidationMessage if non-null, an error message to throw at clone time
* (used for deferred validation, e.g. {@code @DeepPlanningClone} on a {@code @PlanningVariable}
* whose type is not deep-cloneable)
*/
public record PropertyCopyDescriptor(
String name,
Function<Object, Object> getter,
BiConsumer<Object, Object> setter,
DeepCloneDecision deepCloneDecision,
String cloneTimeValidationMessage) {

public PropertyCopyDescriptor(
String name,
Function<Object, Object> getter,
BiConsumer<Object, Object> setter,
DeepCloneDecision deepCloneDecision) {
this(name, getter, setter, deepCloneDecision, null);
}
}

/**
* Pre-classified decision for how a field value is handled during cloning.
* Determined at specification-build time so no runtime type inspection is needed.
*/
public enum DeepCloneDecision {
/** Immutable type: direct copy (no cloning needed). */
SHALLOW,
/** May be an entity or deep-clone type: resolve from cloneMap. */
RESOLVE_ENTITY_REFERENCE,
/** {@code @DeepPlanningClone} type: always deep clone. */
ALWAYS_DEEP,
/** Collection needing element resolution/deep-cloning. */
DEEP_COLLECTION,
/** Map needing key/value resolution/deep-cloning. */
DEEP_MAP,
/** Array needing element resolution/deep-cloning. */
DEEP_ARRAY,
/**
* Non-immutable type where the field's declared type is not known to be deep-cloneable,
* but the runtime value's class might be (e.g. a subclass annotated with {@code @DeepPlanningClone}).
* At clone time: check if the value's actual class is deep-cloneable, and deep-clone if so.
* Otherwise, shallow copy.
*/
SHALLOW_OR_DEEP_BY_RUNTIME_TYPE
}

/**
* Cloning recipe for a single cloneable class (entity or {@code @DeepPlanningClone} fact).
*
* @param clazz the class
* @param factory creates a new empty instance (no-arg constructor)
* @param properties all fields (including inherited), with clone decisions
*/
public record CloneableClassDescriptor(
Class<?> clazz,
Supplier<Object> factory,
List<PropertyCopyDescriptor> properties) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package ai.timefold.solver.core.api.domain.specification;

import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

/**
* Builder for configuring lambda-based solution cloning.
* <p>
* This builder allows users to define a complete cloning recipe: for the solution class,
* every entity class, and any {@code @DeepPlanningClone} fact classes, all fields are declared
* with their getter/setter lambdas and a {@link CloningSpecification.DeepCloneDecision}.
*
* @param <S> the solution type
*/
public interface CloningSpecificationBuilder<S> {

/**
* Sets the factory that creates new empty solution instances.
*/
CloningSpecificationBuilder<S> solutionFactory(Supplier<S> factory);

/**
* Declares a shallow-copy property on the solution class.
*/
<V> CloningSpecificationBuilder<S> solutionProperty(String name, Function<S, V> getter, BiConsumer<S, V> setter);

/**
* Declares a property on the solution class with an explicit deep clone decision.
*/
<V> CloningSpecificationBuilder<S> solutionProperty(String name, Function<S, V> getter, BiConsumer<S, V> setter,
CloningSpecification.DeepCloneDecision decision);

/**
* Registers an entity class with its no-arg constructor and property definitions.
*/
<E> CloningSpecificationBuilder<S> entityClass(Class<E> entityClass, Supplier<E> factory,
Consumer<CloneableClassBuilder<E>> config);

/**
* Registers an entity class with its no-arg constructor, without additional property definitions.
*/
default <E> CloningSpecificationBuilder<S> entityFactory(Class<E> entityClass, Supplier<E> factory) {
return entityClass(entityClass, factory, e -> {
});
}

/**
* Registers a {@code @DeepPlanningClone} fact class with its no-arg constructor and property definitions.
*/
<E> CloningSpecificationBuilder<S> deepCloneFact(Class<E> factClass, Supplier<E> factory,
Consumer<CloneableClassBuilder<E>> config);

/**
* Builder for defining properties on a cloneable class (entity or deep-clone fact).
*
* @param <E> the class type
*/
interface CloneableClassBuilder<E> {

/**
* Declares a shallow-copy property.
*/
<V> CloneableClassBuilder<E> shallowProperty(String name, Function<E, V> getter, BiConsumer<E, V> setter);

/**
* Declares a property that may reference an entity (resolved from cloneMap).
*/
<V> CloneableClassBuilder<E> entityRefProperty(String name, Function<E, V> getter, BiConsumer<E, V> setter);

/**
* Declares a property that should always be deep-cloned.
*/
<V> CloneableClassBuilder<E> deepProperty(String name, Function<E, V> getter, BiConsumer<E, V> setter);

/**
* Declares a collection property that needs element resolution/deep-cloning.
*/
<V> CloneableClassBuilder<E> deepCollectionProperty(String name, Function<E, V> getter,
BiConsumer<E, V> setter);

/**
* Declares a map property that needs key/value resolution/deep-cloning.
*/
<V> CloneableClassBuilder<E> deepMapProperty(String name, Function<E, V> getter, BiConsumer<E, V> setter);

/**
* Declares an array property that needs element resolution/deep-cloning.
*/
<V> CloneableClassBuilder<E> deepArrayProperty(String name, Function<E, V> getter, BiConsumer<E, V> setter);

/**
* Declares a property with an explicit deep clone decision.
*/
<V> CloneableClassBuilder<E> property(String name, Function<E, V> getter, BiConsumer<E, V> setter,
CloningSpecification.DeepCloneDecision decision);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package ai.timefold.solver.core.api.domain.specification;

import java.util.function.Function;

import ai.timefold.solver.core.api.domain.solution.ConstraintWeightOverrides;

/**
* Describes constraint weight overrides on a planning solution.
*
* @param getter reads the constraint weight overrides from the solution
* @param <S> the solution type
*/
public record ConstraintWeightSpecification<S>(
Function<S, ConstraintWeightOverrides<?>> getter) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package ai.timefold.solver.core.api.domain.specification;

import java.util.function.Function;

/**
* Builder for configuring a declarative shadow variable.
*
* @param <S> the solution type
* @param <E> the entity type
* @param <V> the shadow variable value type
*/
public interface DeclarativeShadowBuilder<S, E, V> {

DeclarativeShadowBuilder<S, E, V> supplier(Function<E, V> supplier);

DeclarativeShadowBuilder<S, E, V> sources(String... sourcePaths);

DeclarativeShadowBuilder<S, E, V> alignmentKey(String key);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package ai.timefold.solver.core.api.domain.specification;

import java.util.Collection;
import java.util.function.BiConsumer;
import java.util.function.Function;

import org.jspecify.annotations.Nullable;

/**
* Describes an entity collection property on a planning solution.
*
* @param name the property name
* @param getter reads the entity collection from the solution
* @param setter writes the entity collection to the solution (may be null)
* @param isSingular true if this is a singular {@code @PlanningEntityProperty} (not a collection)
* @param <S> the solution type
*/
public record EntityCollectionSpecification<S>(
String name,
Function<S, ? extends Collection<?>> getter,
@Nullable BiConsumer<S, Object> setter,
boolean isSingular) {

/**
* Constructor without setter (programmatic API, always treated as collection).
*/
public EntityCollectionSpecification(String name, Function<S, ? extends Collection<?>> getter) {
this(name, getter, null, false);
}

/**
* Constructor without setter (annotation path).
*/
public EntityCollectionSpecification(String name, Function<S, ? extends Collection<?>> getter, boolean isSingular) {
this(name, getter, null, isSingular);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package ai.timefold.solver.core.api.domain.specification;

import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.ToIntFunction;

/**
* Describes a planning entity.
*
* @param entityClass the entity class
* @param planningIdGetter optional getter for the planning ID
* @param difficultyComparator optional comparator for entity difficulty sorting
* @param difficultyComparatorFactoryClass optional factory class for solution-aware entity difficulty sorting
* @param pinnedPredicate optional predicate to determine if an entity is pinned
* @param pinToIndexFunction optional function to determine the pin-to-index
* @param variables the genuine planning variables
* @param shadows the shadow variables
* @param entityScopedValueRanges value ranges scoped to this entity
* @param <S> the solution type
*/
public record EntitySpecification<S>(
Class<?> entityClass,
Function<?, ?> planningIdGetter,
java.util.function.BiConsumer<?, Object> planningIdSetter,
Comparator<?> difficultyComparator,
Class<?> difficultyComparatorFactoryClass,
Predicate<?> pinnedPredicate,
ToIntFunction<?> pinToIndexFunction,
List<VariableSpecification<S>> variables,
List<ShadowSpecification<S>> shadows,
List<ValueRangeSpecification<S>> entityScopedValueRanges) {

/**
* Backward-compatible constructor without planningIdSetter and difficultyComparatorFactoryClass.
*/
public EntitySpecification(
Class<?> entityClass,
Function<?, ?> planningIdGetter,
Comparator<?> difficultyComparator,
Predicate<?> pinnedPredicate,
ToIntFunction<?> pinToIndexFunction,
List<VariableSpecification<S>> variables,
List<ShadowSpecification<S>> shadows,
List<ValueRangeSpecification<S>> entityScopedValueRanges) {
this(entityClass, planningIdGetter, null, difficultyComparator, null,
pinnedPredicate, pinToIndexFunction, variables, shadows, entityScopedValueRanges);
}

/**
* Backward-compatible constructor without planningIdSetter.
*/
public EntitySpecification(
Class<?> entityClass,
Function<?, ?> planningIdGetter,
Comparator<?> difficultyComparator,
Class<?> difficultyComparatorFactoryClass,
Predicate<?> pinnedPredicate,
ToIntFunction<?> pinToIndexFunction,
List<VariableSpecification<S>> variables,
List<ShadowSpecification<S>> shadows,
List<ValueRangeSpecification<S>> entityScopedValueRanges) {
this(entityClass, planningIdGetter, null, difficultyComparator, difficultyComparatorFactoryClass,
pinnedPredicate, pinToIndexFunction, variables, shadows, entityScopedValueRanges);
}
}
Loading
Loading