Search code examples
javaoptaplanner

Optaplanner ConstraintFactory - from seems to return uninitialized entities


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 Enrollments with null Lessons to be contained in the stream, since I am using from (and not fromUnfiltered). Can anyone explain this behaviour?


Solution

  • 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.