Skip to content

Commit d5ed7e1

Browse files
fix: Do not fail on solution/entity classes as interfaces (#934)
The superclass of an interface is `null`. This caused this line of code to throw an exception for solution classes that are interface: ```java var superclass = bottomClass.getSuperclass(); lineageClassList.addAll(getAllAnnotatedLineageClasses(superclass, annotation)); ``` Since `getAllAnnotatedLineageClasses` expected superclass to not be `null`. `getAllAnnotatedLineageClasses` now returns an empty list for `null` arguments.
1 parent 35b4c7b commit d5ed7e1

File tree

6 files changed

+145
-1
lines changed

6 files changed

+145
-1
lines changed

core/src/main/java/ai/timefold/solver/core/config/util/ConfigUtils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ public static int resolvePoolSize(String propertyName, String value, String... m
310310

311311
public static List<Class<?>> getAllAnnotatedLineageClasses(Class<?> bottomClass,
312312
Class<? extends Annotation> annotation) {
313-
if (!bottomClass.isAnnotationPresent(annotation)) {
313+
if (bottomClass == null || !bottomClass.isAnnotationPresent(annotation)) {
314314
return Collections.emptyList();
315315
}
316316
List<Class<?>> lineageClassList = new ArrayList<>();

core/src/test/java/ai/timefold/solver/core/config/solver/SolverConfigTest.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@
4848
import ai.timefold.solver.core.impl.testdata.domain.TestdataValue;
4949
import ai.timefold.solver.core.impl.testdata.domain.extended.TestdataAnnotatedExtendedEntity;
5050
import ai.timefold.solver.core.impl.testdata.domain.extended.TestdataAnnotatedExtendedSolution;
51+
import ai.timefold.solver.core.impl.testdata.domain.interface_domain.TestdataInterfaceConstraintProvider;
52+
import ai.timefold.solver.core.impl.testdata.domain.interface_domain.TestdataInterfaceEntity;
53+
import ai.timefold.solver.core.impl.testdata.domain.interface_domain.TestdataInterfaceSolution;
5154
import ai.timefold.solver.core.impl.testdata.domain.record.TestdataRecordEntity;
5255
import ai.timefold.solver.core.impl.testdata.domain.record.TestdataRecordSolution;
5356

@@ -226,6 +229,20 @@ void variableWithPlanningIdIsARecord() {
226229
Assertions.assertThatNoException().isThrownBy(() -> solver.solve(solution));
227230
}
228231

232+
@Test
233+
void domainClassesAreInterfaces() {
234+
var solverConfig = new SolverConfig()
235+
.withSolutionClass(TestdataInterfaceSolution.class)
236+
.withEntityClasses(TestdataInterfaceEntity.class)
237+
.withConstraintProviderClass(TestdataInterfaceConstraintProvider.class)
238+
.withPhases(new ConstructionHeuristicPhaseConfig()); // Run CH and finish.
239+
var solver = SolverFactory.create(solverConfig)
240+
.buildSolver();
241+
242+
var solution = TestdataInterfaceSolution.generateSolution();
243+
Assertions.assertThatNoException().isThrownBy(() -> solver.solve(solution));
244+
}
245+
229246
@Test
230247
void entityWithTwoPlanningListVariables() {
231248
var solverConfig = new SolverConfig()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package ai.timefold.solver.core.impl.testdata.domain.interface_domain;
2+
3+
import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore;
4+
import ai.timefold.solver.core.api.score.stream.Constraint;
5+
import ai.timefold.solver.core.api.score.stream.ConstraintFactory;
6+
import ai.timefold.solver.core.api.score.stream.ConstraintProvider;
7+
8+
public class TestdataInterfaceConstraintProvider implements ConstraintProvider {
9+
10+
@Override
11+
public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
12+
return new Constraint[] { alwaysPenalizingConstraint(constraintFactory) };
13+
}
14+
15+
private Constraint alwaysPenalizingConstraint(ConstraintFactory constraintFactory) {
16+
return constraintFactory.forEach(TestdataInterfaceEntity.class)
17+
.penalize(SimpleScore.ONE)
18+
.asConstraint("Always penalize");
19+
}
20+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package ai.timefold.solver.core.impl.testdata.domain.interface_domain;
2+
3+
import ai.timefold.solver.core.api.domain.entity.PlanningEntity;
4+
import ai.timefold.solver.core.api.domain.variable.PlanningVariable;
5+
6+
@PlanningEntity
7+
public interface TestdataInterfaceEntity {
8+
@PlanningVariable
9+
TestdataInterfaceValue getValue();
10+
11+
void setValue(TestdataInterfaceValue value);
12+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package ai.timefold.solver.core.impl.testdata.domain.interface_domain;
2+
3+
import java.util.List;
4+
5+
import ai.timefold.solver.core.api.domain.solution.PlanningEntityCollectionProperty;
6+
import ai.timefold.solver.core.api.domain.solution.PlanningScore;
7+
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
8+
import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider;
9+
import ai.timefold.solver.core.api.score.buildin.simple.SimpleScore;
10+
import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor;
11+
12+
@PlanningSolution
13+
public interface TestdataInterfaceSolution {
14+
static SolutionDescriptor<TestdataInterfaceSolution> buildSolutionDescriptor() {
15+
return SolutionDescriptor.buildSolutionDescriptor(TestdataInterfaceSolution.class, TestdataInterfaceEntity.class);
16+
}
17+
18+
static TestdataInterfaceSolution generateSolution() {
19+
var out = new TestdataInterfaceSolution() {
20+
private List<TestdataInterfaceEntity> entityList;
21+
private List<TestdataInterfaceValue> valueList;
22+
private SimpleScore score;
23+
24+
@Override
25+
public List<TestdataInterfaceEntity> getEntityList() {
26+
return entityList;
27+
}
28+
29+
@Override
30+
public void setEntityList(List<TestdataInterfaceEntity> entityList) {
31+
this.entityList = entityList;
32+
}
33+
34+
@Override
35+
public List<TestdataInterfaceValue> getValueList() {
36+
return valueList;
37+
}
38+
39+
@Override
40+
public void setValueList(List<TestdataInterfaceValue> valueList) {
41+
this.valueList = valueList;
42+
}
43+
44+
@Override
45+
public SimpleScore getScore() {
46+
return score;
47+
}
48+
49+
@Override
50+
public void setScore(SimpleScore score) {
51+
this.score = score;
52+
}
53+
};
54+
55+
out.setEntityList(List.of(
56+
new TestdataInterfaceEntity() {
57+
private TestdataInterfaceValue value;
58+
59+
@Override
60+
public TestdataInterfaceValue getValue() {
61+
return value;
62+
}
63+
64+
@Override
65+
public void setValue(TestdataInterfaceValue value) {
66+
this.value = value;
67+
}
68+
}));
69+
70+
out.setValueList(List.of(
71+
new TestdataInterfaceValue() {
72+
}));
73+
74+
return out;
75+
}
76+
77+
@PlanningEntityCollectionProperty
78+
List<TestdataInterfaceEntity> getEntityList();
79+
80+
void setEntityList(List<TestdataInterfaceEntity> entityList);
81+
82+
@ValueRangeProvider
83+
List<TestdataInterfaceValue> getValueList();
84+
85+
void setValueList(List<TestdataInterfaceValue> valueList);
86+
87+
@PlanningScore
88+
SimpleScore getScore();
89+
90+
void setScore(SimpleScore score);
91+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package ai.timefold.solver.core.impl.testdata.domain.interface_domain;
2+
3+
public interface TestdataInterfaceValue {
4+
}

0 commit comments

Comments
 (0)