* Applies when the stream comes off of a {@link ConstraintFactory#forEach(Class)} family of methods.
diff --git a/core/constraint-streams/src/test/java/ai/timefold/solver/constraint/streams/bavet/BavetRegressionTest.java b/core/constraint-streams/src/test/java/ai/timefold/solver/constraint/streams/bavet/BavetRegressionTest.java
index 6755e9fa88..664a90ff44 100644
--- a/core/constraint-streams/src/test/java/ai/timefold/solver/constraint/streams/bavet/BavetRegressionTest.java
+++ b/core/constraint-streams/src/test/java/ai/timefold/solver/constraint/streams/bavet/BavetRegressionTest.java
@@ -189,7 +189,7 @@ public void filteringJoinNullConflictDifferentNodes() {
InnerScoreDirector
- * This does not apply to {@link PlanningListVariable#allowsUnassignedValues()}.
- * Elements can be unassigned (not present in any list) and this method will still include them.
- * This situation needs to be handled by adding a {@link InverseRelationShadowVariable} on the element,
- * exposing it via a new method such as {@code getUnassigned()},
- * and then calling {@link UniConstraintStream#filter(Predicate)} with that method.
- *
- * Example:
- *
- *
- * {@code
- * @PlanningEntity
- * public class Vehicle {
- *
- * @PlanningListVariable(allowsUnassignedValues = true)
- * List
- *
- *
* @param sourceClass never null
* @param the type of the matched problem fact or {@link PlanningEntity planning entity}
* @return never null
*/
UniConstraintStream forEach(Class sourceClass);
+ /**
+ * As defined by {@link #forEachIncludingUnassigned(Class)}.
+ *
+ * @deprecated Use {@link #forEachIncludingUnassigned(Class)} instead.
+ */
+ @Deprecated(forRemoval = true, since = "1.7.0")
+ default UniConstraintStream forEachIncludingNullVars(Class sourceClass) {
+ return forEachIncludingUnassigned(sourceClass);
+ }
+
/**
* As defined by {@link #forEach(Class)},
- * but without any filtering of null {@link PlanningEntity planning entity} variables.
+ * but without any filtering of unassigned {@link PlanningEntity planning entities}
+ * (for {@link PlanningVariable#allowsUnassigned()})
+ * or shadow entities not assigned to any applicable list variable
+ * (for {@link PlanningListVariable#allowsUnassignedValues()}).
*
* @param sourceClass never null
* @param the type of the matched problem fact or {@link PlanningEntity planning entity}
* @return never null
*/
- UniConstraintStream forEachIncludingNullVars(Class sourceClass);
+ UniConstraintStream forEachIncludingUnassigned(Class sourceClass);
/**
* Create a new {@link BiConstraintStream} for every unique combination of A and another A with a higher {@link PlanningId}.
@@ -233,7 +201,7 @@ default BiConstraintStream forEachUniquePair(Class sourceClass, BiJ
* Calls to the {@link #forEach(Class)} family of methods will now filter out planning entities with null variables,
* so most constraints no longer need to do null checks,
* but the constraint that penalizes unassigned entities (typically a medium constraint)
- * must now use {@link #forEachIncludingNullVars(Class)} instead.
+ * must now use {@link #forEachIncludingUnassigned(Class)} instead.
* Subsequent joins and conditional propagation calls will now also consistently filter out planning entities with null
* variables.
*
@@ -260,7 +228,7 @@ default BiConstraintStream forEachUniquePair(Class sourceClass, BiJ
/**
* This method is deprecated.
- * Migrate uses of this method to {@link #forEachIncludingNullVars(Class)},
+ * Migrate uses of this method to {@link #forEachIncludingUnassigned(Class)},
* but first understand that subsequent joins and conditional propagation calls
* ({@link UniConstraintStream#ifExists} etc.)
* will now also consistently filter out planning entities with null variables.
@@ -270,7 +238,7 @@ default BiConstraintStream forEachUniquePair(Class sourceClass, BiJ
* As defined by {@link #from(Class)},
* but without any filtering of uninitialized {@link PlanningEntity planning entities}.
*
- * @deprecated Prefer {@link #forEachIncludingNullVars(Class)}.
+ * @deprecated Prefer {@link #forEachIncludingUnassigned(Class)}.
* @param fromClass never null
* @param the type of the matched problem fact or {@link PlanningEntity planning entity}
* @return never null
diff --git a/docs/src/modules/ROOT/pages/constraints-and-score/score-calculation.adoc b/docs/src/modules/ROOT/pages/constraints-and-score/score-calculation.adoc
index e3b9cd3632..71afea3a3a 100644
--- a/docs/src/modules/ROOT/pages/constraints-and-score/score-calculation.adoc
+++ b/docs/src/modules/ROOT/pages/constraints-and-score/score-calculation.adoc
@@ -221,12 +221,12 @@ or a xref:using-timefold-solver/modeling-planning-problems.adoc#planningEntities
and has no `null` genuine planning variables.
To include instances with a `null` genuine planning variable,
-replace the `forEach()` building block by `forEachIncludingNullVars()`:
+replace the `forEach()` building block by `forEachIncludingUnassigned()`:
[source,java,options="nowrap"]
----
private Constraint penalizeAllShifts(ConstraintFactory factory) {
- return factory.forEachIncludingNullVars(Shift.class)
+ return factory.forEachIncludingUnassigned(Shift.class)
.penalize(HardSoftScore.ONE_SOFT)
.asConstraint("A shift");
}
diff --git a/docs/src/modules/ROOT/pages/using-timefold-solver/modeling-planning-problems.adoc b/docs/src/modules/ROOT/pages/using-timefold-solver/modeling-planning-problems.adoc
index 363966692b..fdbd791719 100644
--- a/docs/src/modules/ROOT/pages/using-timefold-solver/modeling-planning-problems.adoc
+++ b/docs/src/modules/ROOT/pages/using-timefold-solver/modeling-planning-problems.adoc
@@ -334,7 +334,7 @@ To allow an initialized planning variable to be ``null``, set `allows_unassigned
[NOTE]
====
Constraint Streams filter out planning entities with a `null` planning variable by default.
-Use xref:constraints-and-score/score-calculation.adoc#constraintStreamsForEach[forEachIncludingNullVars()] to avoid such unwanted behaviour.
+Use xref:constraints-and-score/score-calculation.adoc#constraintStreamsForEach[forEachIncludingUnassigned()] to avoid such unwanted behaviour.
====
Timefold Solver will automatically add the value `null` to the value range.
diff --git a/examples/src/main/java/ai/timefold/solver/examples/conferencescheduling/score/ConferenceSchedulingConstraintProvider.java b/examples/src/main/java/ai/timefold/solver/examples/conferencescheduling/score/ConferenceSchedulingConstraintProvider.java
index 7f94c7f685..8f354e9d0b 100644
--- a/examples/src/main/java/ai/timefold/solver/examples/conferencescheduling/score/ConferenceSchedulingConstraintProvider.java
+++ b/examples/src/main/java/ai/timefold/solver/examples/conferencescheduling/score/ConferenceSchedulingConstraintProvider.java
@@ -138,7 +138,7 @@ Constraint roomConflict(ConstraintFactory factory) {
}
Constraint speakerUnavailableTimeslot(ConstraintFactory factory) {
- return factory.forEachIncludingNullVars(Talk.class)
+ return factory.forEachIncludingUnassigned(Talk.class)
.filter(talk -> talk.getTimeslot() != null)
.join(Speaker.class,
filtering((talk, speaker) -> talk.hasSpeaker(speaker)
diff --git a/examples/src/main/java/ai/timefold/solver/examples/meetingscheduling/score/MeetingSchedulingConstraintProvider.java b/examples/src/main/java/ai/timefold/solver/examples/meetingscheduling/score/MeetingSchedulingConstraintProvider.java
index 9705189f1a..3fc0a13969 100644
--- a/examples/src/main/java/ai/timefold/solver/examples/meetingscheduling/score/MeetingSchedulingConstraintProvider.java
+++ b/examples/src/main/java/ai/timefold/solver/examples/meetingscheduling/score/MeetingSchedulingConstraintProvider.java
@@ -51,7 +51,7 @@ protected Constraint roomConflict(ConstraintFactory constraintFactory) {
}
protected Constraint avoidOvertime(ConstraintFactory constraintFactory) {
- return constraintFactory.forEachIncludingNullVars(MeetingAssignment.class)
+ return constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class)
.filter(meetingAssignment -> meetingAssignment.getStartingTimeGrain() != null)
.ifNotExists(TimeGrain.class,
equal(MeetingAssignment::getLastTimeGrainIndex, TimeGrain::getGrainIndex))
@@ -82,7 +82,7 @@ protected Constraint requiredAttendanceConflict(ConstraintFactory constraintFact
}
protected Constraint requiredRoomCapacity(ConstraintFactory constraintFactory) {
- return constraintFactory.forEachIncludingNullVars(MeetingAssignment.class)
+ return constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class)
.filter(meetingAssignment -> meetingAssignment.getRequiredCapacity() > meetingAssignment.getRoomCapacity())
.penalizeConfigurable(
meetingAssignment -> meetingAssignment.getRequiredCapacity() - meetingAssignment.getRoomCapacity())
@@ -90,7 +90,7 @@ protected Constraint requiredRoomCapacity(ConstraintFactory constraintFactory) {
}
protected Constraint startAndEndOnSameDay(ConstraintFactory constraintFactory) {
- return constraintFactory.forEachIncludingNullVars(MeetingAssignment.class)
+ return constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class)
.filter(meetingAssignment -> meetingAssignment.getStartingTimeGrain() != null)
.join(TimeGrain.class,
equal(MeetingAssignment::getLastTimeGrainIndex, TimeGrain::getGrainIndex),
@@ -108,11 +108,11 @@ protected Constraint requiredAndPreferredAttendanceConflict(ConstraintFactory co
return constraintFactory.forEach(RequiredAttendance.class)
.join(PreferredAttendance.class,
equal(RequiredAttendance::getPerson, PreferredAttendance::getPerson))
- .join(constraintFactory.forEachIncludingNullVars(MeetingAssignment.class)
+ .join(constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class)
.filter(assignment -> assignment.getStartingTimeGrain() != null),
equal((requiredAttendance, preferredAttendance) -> requiredAttendance.getMeeting(),
MeetingAssignment::getMeeting))
- .join(constraintFactory.forEachIncludingNullVars(MeetingAssignment.class)
+ .join(constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class)
.filter(assignment -> assignment.getStartingTimeGrain() != null),
equal((requiredAttendance, preferredAttendance, leftAssignment) -> preferredAttendance.getMeeting(),
MeetingAssignment::getMeeting),
@@ -131,11 +131,11 @@ protected Constraint requiredAndPreferredAttendanceConflict(ConstraintFactory co
protected Constraint preferredAttendanceConflict(ConstraintFactory constraintFactory) {
return constraintFactory.forEachUniquePair(PreferredAttendance.class,
equal(PreferredAttendance::getPerson))
- .join(constraintFactory.forEachIncludingNullVars(MeetingAssignment.class)
+ .join(constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class)
.filter(assignment -> assignment.getStartingTimeGrain() != null),
equal((leftAttendance, rightAttendance) -> leftAttendance.getMeeting(),
MeetingAssignment::getMeeting))
- .join(constraintFactory.forEachIncludingNullVars(MeetingAssignment.class)
+ .join(constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class)
.filter(assignment -> assignment.getStartingTimeGrain() != null),
equal((leftAttendance, rightAttendance, leftAssignment) -> rightAttendance.getMeeting(),
MeetingAssignment::getMeeting),
@@ -156,16 +156,16 @@ protected Constraint preferredAttendanceConflict(ConstraintFactory constraintFac
// ************************************************************************
protected Constraint doMeetingsAsSoonAsPossible(ConstraintFactory constraintFactory) {
- return constraintFactory.forEachIncludingNullVars(MeetingAssignment.class)
+ return constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class)
.filter(meetingAssignment -> meetingAssignment.getStartingTimeGrain() != null)
.penalizeConfigurable(MeetingAssignment::getLastTimeGrainIndex)
.asConstraint("Do all meetings as soon as possible");
}
protected Constraint oneBreakBetweenConsecutiveMeetings(ConstraintFactory constraintFactory) {
- return constraintFactory.forEachIncludingNullVars(MeetingAssignment.class)
+ return constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class)
.filter(meetingAssignment -> meetingAssignment.getStartingTimeGrain() != null)
- .join(constraintFactory.forEachIncludingNullVars(MeetingAssignment.class)
+ .join(constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class)
.filter(assignment -> assignment.getStartingTimeGrain() != null),
equal(MeetingAssignment::getLastTimeGrainIndex,
(rightAssignment) -> rightAssignment.getStartingTimeGrain().getGrainIndex() - 1))
@@ -174,9 +174,9 @@ protected Constraint oneBreakBetweenConsecutiveMeetings(ConstraintFactory constr
}
protected Constraint overlappingMeetings(ConstraintFactory constraintFactory) {
- return constraintFactory.forEachIncludingNullVars(MeetingAssignment.class)
+ return constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class)
.filter(meetingAssignment -> meetingAssignment.getStartingTimeGrain() != null)
- .join(constraintFactory.forEachIncludingNullVars(MeetingAssignment.class)
+ .join(constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class)
.filter(meetingAssignment -> meetingAssignment.getStartingTimeGrain() != null),
greaterThan((leftAssignment) -> leftAssignment.getMeeting().getId(),
(rightAssignment) -> rightAssignment.getMeeting().getId()),
@@ -188,7 +188,7 @@ protected Constraint overlappingMeetings(ConstraintFactory constraintFactory) {
}
protected Constraint assignLargerRoomsFirst(ConstraintFactory constraintFactory) {
- return constraintFactory.forEachIncludingNullVars(MeetingAssignment.class)
+ return constraintFactory.forEachIncludingUnassigned(MeetingAssignment.class)
.filter(meetingAssignment -> meetingAssignment.getRoom() != null)
.join(Room.class,
lessThan(MeetingAssignment::getRoomCapacity, Room::getCapacity))
diff --git a/examples/src/main/java/ai/timefold/solver/examples/pas/score/PatientAdmissionScheduleConstraintProvider.java b/examples/src/main/java/ai/timefold/solver/examples/pas/score/PatientAdmissionScheduleConstraintProvider.java
index 7be198b0da..d42db0d947 100644
--- a/examples/src/main/java/ai/timefold/solver/examples/pas/score/PatientAdmissionScheduleConstraintProvider.java
+++ b/examples/src/main/java/ai/timefold/solver/examples/pas/score/PatientAdmissionScheduleConstraintProvider.java
@@ -59,7 +59,7 @@ public Constraint sameBedInSameNightConstraint(ConstraintFactory constraintFacto
}
public Constraint femaleInMaleRoomConstraint(ConstraintFactory constraintFactory) {
- return constraintFactory.forEachIncludingNullVars(BedDesignation.class)
+ return constraintFactory.forEachIncludingUnassigned(BedDesignation.class)
.filter(bd -> bd.getPatientGender() == Gender.FEMALE
&& bd.getRoomGenderLimitation() == GenderLimitation.MALE_ONLY)
.penalize(HardMediumSoftScore.ofHard(50), BedDesignation::getAdmissionPartNightCount)
@@ -67,7 +67,7 @@ public Constraint femaleInMaleRoomConstraint(ConstraintFactory constraintFactory
}
public Constraint maleInFemaleRoomConstraint(ConstraintFactory constraintFactory) {
- return constraintFactory.forEachIncludingNullVars(BedDesignation.class)
+ return constraintFactory.forEachIncludingUnassigned(BedDesignation.class)
.filter(bd -> bd.getPatientGender() == Gender.MALE
&& bd.getRoomGenderLimitation() == GenderLimitation.FEMALE_ONLY)
.penalize(HardMediumSoftScore.ofHard(50), BedDesignation::getAdmissionPartNightCount)
@@ -89,9 +89,9 @@ public Constraint differentGenderInSameGenderRoomInSameNightConstraint(Constrain
}
public Constraint departmentMinimumAgeConstraint(ConstraintFactory constraintFactory) {
- return constraintFactory.forEachIncludingNullVars(Department.class)
+ return constraintFactory.forEachIncludingUnassigned(Department.class)
.filter(d -> d.getMinimumAge() != null)
- .join(constraintFactory.forEachIncludingNullVars(BedDesignation.class),
+ .join(constraintFactory.forEachIncludingUnassigned(BedDesignation.class),
equal(Function.identity(), BedDesignation::getDepartment),
greaterThan(Department::getMinimumAge, BedDesignation::getPatientAge))
.penalize(HardMediumSoftScore.ofHard(100),
@@ -100,9 +100,9 @@ public Constraint departmentMinimumAgeConstraint(ConstraintFactory constraintFac
}
public Constraint departmentMaximumAgeConstraint(ConstraintFactory constraintFactory) {
- return constraintFactory.forEachIncludingNullVars(Department.class)
+ return constraintFactory.forEachIncludingUnassigned(Department.class)
.filter(d -> d.getMaximumAge() != null)
- .join(constraintFactory.forEachIncludingNullVars(BedDesignation.class),
+ .join(constraintFactory.forEachIncludingUnassigned(BedDesignation.class),
equal(Function.identity(), BedDesignation::getDepartment),
lessThan(Department::getMaximumAge, BedDesignation::getPatientAge))
.penalize(HardMediumSoftScore.ofHard(100),
@@ -124,7 +124,7 @@ public Constraint requiredPatientEquipmentConstraint(ConstraintFactory constrain
//Medium
public Constraint assignEveryPatientToABedConstraint(ConstraintFactory constraintFactory) {
- return constraintFactory.forEachIncludingNullVars(BedDesignation.class)
+ return constraintFactory.forEachIncludingUnassigned(BedDesignation.class)
.filter(bd -> bd.getBed() == null)
.penalize(HardMediumSoftScore.ONE_MEDIUM, BedDesignation::getAdmissionPartNightCount)
.asConstraint("assignEveryPatientToABed");