I am attempting to solve a simple proof of concept problem using Optaplanner, and I am running into an inconsistency. The documentation (and other SO questions) seems to indicate that ConstraintFactory.from()
should only return initialized planning entities. (like here)
I have the following planning entity (relevant code only):
@PlanningEntity
public class Enrollment {
@PlanningId
private UUID id;
private Student student;
@PlanningVariable(valueRangeProviderRefs = "possibleLessons", nullable = true)
private Lesson lesson;
// No-arg constructor required for OptaPlanner
public Enrollment() {
}
public Enrollment(UUID id, Student student) {
this.id = id;
this.student = student;
}
public Student getStudent() {
return student;
}
public Lesson getLesson() {
return lesson;
}
}
and tried to introduce the following constraint:
private Constraint studentsLikePreferredSubjects(ConstraintFactory constraintFactory) {
return constraintFactory
.from(Enrollment.class)
.filter(enrollment -> enrollment.getStudent().getPreferredSubjects().contains(enrollment.getLesson().getSubject()))
.reward("Student likes subject bonus", HardSoftScore.ONE_SOFT);
}
This code does NOT work, since I get a nullpointerexception on getLesson
: Cannot invoke "Lesson.getSubject()" because the return value of "Enrollment.getLesson()" is null
However, adding a filter to filter out null-lessons DOES work:
private Constraint studentsLikePreferredSubjects(ConstraintFactory constraintFactory) {
return constraintFactory
.from(Enrollment.class)
.filter(enrollment -> enrollment.getLesson() != null)
.filter(enrollment -> enrollment.getStudent().getPreferredSubjects().contains(enrollment.getLesson().getSubject()))
.reward("Student likes subject bonus", HardSoftScore.ONE_SOFT);
}
Of course I could just use this code, but I don't understand why it is necessary: I would expect no Enrollment
s with null
Lesson
s to be contained in the stream, since I am using from
(and not fromUnfiltered
). Can anyone explain this behaviour?
ConstraintFactory.from()
only returns initialized planning entities, unless nullable = true
(by default, nullable is false, so from()
filters).
In your code, nullable = true
(you're doing overconstrained planning), so from()
returns all entities.