Skip to content

Commit 90d4720

Browse files
chore: use a custom exception for inconsistent solution
1 parent 7973b63 commit 90d4720

4 files changed

Lines changed: 47 additions & 12 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package ai.timefold.solver.core.api.domain.variable;
2+
3+
import java.util.List;
4+
5+
import org.jspecify.annotations.NullMarked;
6+
7+
@NullMarked
8+
public class InconsistentSolutionException extends RuntimeException {
9+
private final Object solution;
10+
private final List<Object> involvedEntityList;
11+
12+
public InconsistentSolutionException(String feature, Object solution, List<Object> involvedEntityList) {
13+
super("The solution (%s) is inconsistent. %s requires a consistent solution.".formatted(solution, feature));
14+
this.solution = solution;
15+
this.involvedEntityList = involvedEntityList;
16+
}
17+
18+
@SuppressWarnings("unchecked")
19+
public <T> T getSolution() {
20+
return (T) solution;
21+
}
22+
23+
@SuppressWarnings("unchecked")
24+
public <T> List<T> getInvolvedEntityList() {
25+
return (List<T>) involvedEntityList;
26+
}
27+
}

core/src/main/java/ai/timefold/solver/core/impl/score/director/AbstractScoreDirector.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,8 +343,12 @@ protected void afterSetWorkingSolution() {
343343
// Do nothing
344344
}
345345

346+
public List<Object> getInconsistentEntities() {
347+
return shadowVariableSupport.getInconsistentEntities();
348+
}
349+
346350
public void unassignInconsistentEntities() {
347-
var inconsistentEntities = shadowVariableSupport.getInconsistentEntities();
351+
var inconsistentEntities = getInconsistentEntities();
348352
if (listVariableStateSupply != null) {
349353
var listVariableDescriptor = listVariableStateSupply.getSourceVariableDescriptor();
350354
var listElementClass = listVariableStateSupply.getSourceVariableDescriptor().getElementType();

core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolutionManager.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.util.function.Function;
66

77
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
8+
import ai.timefold.solver.core.api.domain.variable.InconsistentSolutionException;
89
import ai.timefold.solver.core.api.score.Score;
910
import ai.timefold.solver.core.api.score.analysis.ScoreAnalysis;
1011
import ai.timefold.solver.core.api.solver.RecommendedAssignment;
@@ -87,14 +88,12 @@ private <Result_> Result_ callScoreDirector(String feature, Solution_ solution,
8788
if (solutionUpdatePolicy.isScoreUpdateEnabled()) {
8889
var score = scoreDirector.calculateScore();
8990
if (score.isInvalid()) {
90-
throw new IllegalArgumentException("""
91-
The solution (%s) is inconsistent. %s requires a consistent solution to be passed.
92-
""".formatted(solution, feature));
91+
var inconsistentEntities = scoreDirector.getInconsistentEntities();
92+
throw new InconsistentSolutionException(feature, nonNullSolution, inconsistentEntities);
9393
}
9494
} else if (!scoreDirector.isLastVariableUpdateWasSuccessful()) {
95-
throw new IllegalArgumentException("""
96-
The solution (%s) is inconsistent. %s requires a consistent solution to be passed.
97-
""".formatted(solution, feature));
95+
var inconsistentEntities = scoreDirector.getInconsistentEntities();
96+
throw new InconsistentSolutionException(feature, nonNullSolution, inconsistentEntities);
9897
}
9998
return function.apply(scoreDirector);
10099
}

core/src/test/java/ai/timefold/solver/core/api/solver/SolutionManagerTest.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.util.List;
1010
import java.util.function.Function;
1111

12+
import ai.timefold.solver.core.api.domain.variable.InconsistentSolutionException;
1213
import ai.timefold.solver.core.api.score.HardSoftScore;
1314
import ai.timefold.solver.core.api.score.Score;
1415
import ai.timefold.solver.core.config.score.director.ScoreDirectorFactoryConfig;
@@ -137,9 +138,11 @@ void updateInconsistent(SolutionManagerSource solutionManagerSource) {
137138
assertThat(solutionManager).isNotNull();
138139

139140
assertThatCode(() -> solutionManager.update(inconsistentSolution))
140-
.isInstanceOf(IllegalArgumentException.class)
141+
.isInstanceOf(InconsistentSolutionException.class)
141142
.hasMessageContainingAll("The solution (",
142-
"is inconsistent", "Solution update", "requires a consistent solution");
143+
"is inconsistent", "Solution update", "requires a consistent solution")
144+
.hasFieldOrPropertyWithValue("solution", inconsistentSolution)
145+
.hasFieldOrPropertyWithValue("involvedEntityList", List.of(valueA1, valueA2));
143146
}
144147

145148
private void assertShadowedListValueAllNull(SoftAssertions softly, TestdataListValueWithShadowHistory current) {
@@ -196,10 +199,12 @@ void updateOnlyShadowVariablesInconsistent(SolutionManagerSource solutionManager
196199
var solutionManager = solutionManagerSource.createSolutionManager(solverFactory);
197200
assertThat(solutionManager).isNotNull();
198201

199-
assertThatCode(() -> solutionManager.update(inconsistentSolution, SolutionUpdatePolicy.UPDATE_SHADOW_VARIABLES_ONLY))
200-
.isInstanceOf(IllegalArgumentException.class)
202+
assertThatCode(() -> solutionManager.update(inconsistentSolution))
203+
.isInstanceOf(InconsistentSolutionException.class)
201204
.hasMessageContainingAll("The solution (",
202-
"is inconsistent", "Solution update", "requires a consistent solution");
205+
"is inconsistent", "Solution update", "requires a consistent solution")
206+
.hasFieldOrPropertyWithValue("solution", inconsistentSolution)
207+
.hasFieldOrPropertyWithValue("involvedEntityList", List.of(valueA1, valueA2));
203208
}
204209

205210
@ParameterizedTest

0 commit comments

Comments
 (0)