Skip to content

Commit bffa618

Browse files
committed
chore: address review comments
1 parent 0e7b8ec commit bffa618

12 files changed

Lines changed: 47 additions & 44 deletions

File tree

core/src/main/java/ai/timefold/solver/core/api/score/constraint/ConstraintRef.java

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,12 @@ public record ConstraintRef(String constraintName)
1717
implements
1818
Comparable<ConstraintRef> {
1919

20-
private static final char PACKAGE_SEPARATOR = '/';
21-
2220
public static ConstraintRef of(String constraintName) {
2321
return new ConstraintRef(constraintName);
2422
}
2523

2624
public ConstraintRef {
27-
var sanitized = AbstractConstraintBuilder.sanitize("constraintName", constraintName);
28-
if (sanitized.isEmpty()) {
29-
throw new IllegalArgumentException("The %s cannot be empty."
30-
.formatted("constraint name"));
31-
} else if (sanitized.contains("" + PACKAGE_SEPARATOR)) {
32-
throw new IllegalArgumentException("The %s (%s) cannot contain a package separator (%s)."
33-
.formatted("constraint name", sanitized, PACKAGE_SEPARATOR));
34-
}
35-
constraintName = sanitized;
25+
constraintName = AbstractConstraintBuilder.sanitize("constraintName", constraintName);
3626
}
3727

3828
@Override

core/src/main/java/ai/timefold/solver/core/api/score/stream/ConstraintBuilder.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ default Constraint asConstraint(String constraintName) {
2222
* placing the constraint in the {@link Constraint#DEFAULT_CONSTRAINT_GROUP default constraint group}.
2323
*
2424
* @param constraintName shows up in {@link ConstraintMatchTotal} during score justification
25+
* @param constraintDescription can contain any character, but it is recommended to keep it short and concise.
2526
*/
2627
default Constraint asConstraintDescribed(String constraintName, String constraintDescription) {
2728
return asConstraintDescribed(constraintName, constraintDescription, Constraint.DEFAULT_CONSTRAINT_GROUP);
@@ -38,6 +39,7 @@ default Constraint asConstraintDescribed(String constraintName, String constrain
3839
* and therefore doesn't need to be URL-friendly, or protected against injection attacks.
3940
*
4041
* @param constraintName shows up in {@link ConstraintMatchTotal} during score justification
42+
* @param constraintDescription can contain any character, but it is recommended to keep it short and concise.
4143
* @param constraintGroup not used by the solver directly, but may be used by external tools to group constraints together,
4244
* such as by their source or by their purpose
4345
*/

core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/AbstractConstraintBuilder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public final Constraint asConstraintDescribed(String constraintName, String cons
3838

3939
public static String sanitize(String fieldName, String fieldValue) {
4040
if (fieldValue == null || fieldValue.equalsIgnoreCase("null") || fieldValue.equalsIgnoreCase("nil")) {
41-
throw new IllegalArgumentException("The %s cannot be null.".formatted(fieldName));
41+
throw new IllegalArgumentException("The %s (%s) cannot be null.".formatted(fieldName, fieldValue));
4242
}
4343
if (!fieldValue.matches("^[a-zA-Z0-9]+[a-zA-Z0-9 _.'-]*$")) {
4444
throw new IllegalArgumentException(

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

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,13 @@ public final class DefaultSolverJob<Solution_> implements SolverJob<Solution_>,
5858
private final @Nullable Consumer<SolverJobStartedEvent<Solution_>> solverJobStartedConsumer;
5959
private final BiConsumer<? super Object, ? super Throwable> exceptionHandler;
6060

61-
private volatile SolverStatus solverStatus;
6261
private final CountDownLatch terminatedLatch;
6362
private final ReentrantLock solverStatusModifyingLock;
6463
private final AtomicBoolean terminatedEarly = new AtomicBoolean(false);
6564
private final BestSolutionHolder<Solution_> bestSolutionHolder = new BestSolutionHolder<>();
6665
private final AtomicReference<@Nullable ProblemSizeStatistics> temporaryProblemSizeStatistics = new AtomicReference<>();
6766

67+
private volatile SolverStatus solverStatus;
6868
private @Nullable Future<Solution_> finalBestSolutionFuture;
6969
private @Nullable ConsumerSupport<Solution_, Object> consumerSupport;
7070

@@ -88,9 +88,9 @@ public DefaultSolverJob(DefaultSolverManager<Solution_> solverManager, Solver<So
8888
this.firstInitializedSolutionConsumer = firstInitializedSolutionConsumer;
8989
this.solverJobStartedConsumer = solverJobStartedConsumer;
9090
this.exceptionHandler = exceptionHandler;
91-
solverStatus = SolverStatus.SOLVING_SCHEDULED;
92-
terminatedLatch = new CountDownLatch(1);
93-
solverStatusModifyingLock = new ReentrantLock();
91+
this.terminatedLatch = new CountDownLatch(1);
92+
this.solverStatusModifyingLock = new ReentrantLock();
93+
this.solverStatus = SolverStatus.SOLVING_SCHEDULED;
9494
}
9595

9696
public void setFinalBestSolutionFuture(Future<Solution_> finalBestSolutionFuture) {
@@ -268,7 +268,7 @@ public ProblemSizeStatistics getProblemSizeStatistics() {
268268
// before the solving has started.
269269
// Once the solving has started, the problem size statistics will be computed
270270
// using the ScoreDirector's hot ValueRangeManager.
271-
return temporaryProblemSizeStatistics.updateAndGet(oldStatistics -> {
271+
var result = temporaryProblemSizeStatistics.updateAndGet(oldStatistics -> {
272272
if (oldStatistics != null) {
273273
// If the problem size statistics were already computed, return them.
274274
// This can happen if the problem size statistics were computed before the solving started.
@@ -278,6 +278,11 @@ public ProblemSizeStatistics getProblemSizeStatistics() {
278278
var valueManager = ValueRangeManager.of(solutionDescriptor, problemFinder.apply(problemId));
279279
return valueManager.getProblemSizeStatistics();
280280
});
281+
// Avoids nullness issues reported by IDE which cannot actually happen.
282+
// The result can never be null, because none of the methods called in the lambda can return null,
283+
// and the lambda is the only way to set the value of the temporaryProblemSizeStatistics,
284+
// which is the only way for it to be null.
285+
return Objects.requireNonNull(result);
281286
}
282287

283288
public SolverTermination<Solution_> getSolverTermination() {
@@ -293,21 +298,33 @@ void close() {
293298

294299
/**
295300
* A listener that unlocks the solverStatusModifyingLock when Solving has started.
296-
*
297301
* It prevents the following scenario caused by unlocking before Solving started:
298302
*
299-
* Thread 1:
300-
* solverStatusModifyingLock.unlock()
301-
* >solver.solve(...) // executes second
302-
*
303-
* Thread 2:
304-
* case SOLVING_ACTIVE:
305-
* >solver.terminateEarly(); // executes first
303+
* <dl>
304+
* <dt>Thread 1</dt>
305+
* <dd>
306+
*
307+
* <pre>
308+
* solverStatusModifyingLock.unlock()
309+
* > solver.solve(...) // executes second
310+
* </pre>
311+
*
312+
* </dd>
313+
* <dt>Thread 2</dt>
314+
* <dd>
315+
*
316+
* <pre>
317+
* case SOLVING_ACTIVE:
318+
* >solver.terminateEarly(); // executes first
319+
* </pre>
320+
*
321+
* </dd>
322+
* </dl>
306323
*
307324
* The solver.solve() call resets the terminateEarly flag, and thus the solver will not be terminated
308325
* by the call, which means terminatedLatch will not be decremented, causing Thread 2 to wait forever
309326
* (at least until another Thread calls terminateEarly again).
310-
*
327+
* <p>
311328
* To prevent Thread 2 from potentially waiting forever, we only unlock the lock after the
312329
* solvingStarted phase lifecycle event is fired, meaning the terminateEarly flag will not be
313330
* reset and thus the solver will actually terminate.

docs/TODO.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,6 @@
3838
- [ ] `ProblemChangeDirector#lookUpWorkingObject()` no longer returns `Optional`.
3939
- [ ] `AutoDiscoverMemberType` is gone
4040
- [ ] Constraint name and group now forces strict validation.
41+
- [ ] `SolverManager` and `SolverJob` no longer have the `ProblemId_` type parameter.
4142

4243
Remove this file when done.

persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/AbstractJacksonRoundTripTest.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,6 @@ public abstract class AbstractJacksonRoundTripTest {
99
// Helper methods
1010
// ************************************************************************
1111

12-
protected static <W> W serializeAndDeserialize(W input) {
13-
return serializeAndDeserialize(new ObjectMapper(), input);
14-
}
15-
1612
protected static <W> W serializeAndDeserialize(ObjectMapper objectMapper, W input) {
1713
String jsonString;
1814
W output;

persistence/jackson/src/test/java/ai/timefold/solver/jackson/api/score/AbstractScoreJacksonRoundTripTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ public abstract class AbstractScoreJacksonRoundTripTest extends AbstractJacksonR
3131
if (expectedScore != null) {
3232
regex = "\\{\\s*" // Start of element
3333
+ "\"score\":\""
34-
+ expectedScore.toString().replaceAll("\\[", "\\\\[").replaceAll("\\]", "\\\\]") // Score
34+
+ expectedScore.toString().replaceAll("\\[", "\\\\[").replaceAll("]", "\\\\]") // Score
3535
+ "\""
36-
+ "\\s*\\}"; // End of element
36+
+ "\\s*}"; // End of element
3737
} else {
38-
regex = "\\{\"score\":null\\}"; // Start and end of element
38+
regex = "\\{\"score\":null}"; // Start and end of element
3939
}
4040
if (!jsonString.matches(regex)) {
4141
fail("Regular expression match failed.\nExpected regular expression: " + regex + "\nActual string: " + jsonString);

quarkus-integration/quarkus-jackson/runtime/src/test/java/ai/timefold/solver/quarkus/jackson/AbstractJacksonRoundTripTest.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,6 @@ public abstract class AbstractJacksonRoundTripTest {
1010
// Helper methods
1111
// ************************************************************************
1212

13-
protected static <W> W serializeAndDeserialize(W input) {
14-
return serializeAndDeserialize(new ObjectMapper(), input);
15-
}
16-
1713
protected static <W> W serializeAndDeserialize(ObjectMapper objectMapper, W input) {
1814
String jsonString;
1915
W output;

quarkus-integration/quarkus-jackson/runtime/src/test/java/ai/timefold/solver/quarkus/jackson/score/AbstractScoreJacksonRoundTripTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ public abstract class AbstractScoreJacksonRoundTripTest extends AbstractJacksonR
3232
if (expectedScore != null) {
3333
regex = "\\{\\s*" // Start of element
3434
+ "\"score\":\""
35-
+ expectedScore.toString().replaceAll("\\[", "\\\\[").replaceAll("\\]", "\\\\]") // Score
35+
+ expectedScore.toString().replaceAll("\\[", "\\\\[").replaceAll("]", "\\\\]") // Score
3636
+ "\""
37-
+ "\\s*\\}"; // End of element
37+
+ "\\s*}"; // End of element
3838
} else {
39-
regex = "\\{\"score\":null\\}"; // Start and end of element
39+
regex = "\\{\"score\":null}"; // Start and end of element
4040
}
4141
if (!jsonString.matches(regex)) {
4242
fail("Regular expression match failed.\nExpected regular expression: " + regex + "\nActual string: " + jsonString);

quarkus-integration/quarkus/runtime/src/main/java/ai/timefold/solver/quarkus/bean/UnavailableTimefoldBeanProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ <Solution_> SolverFactory<Solution_> solverFactory() {
3535
@DefaultBean
3636
@Dependent
3737
@Produces
38-
<Solution_, ProblemId_> SolverManager<Solution_> solverManager() {
38+
<Solution_> SolverManager<Solution_> solverManager() {
3939
throw createException(SolverManager.class);
4040
}
4141

0 commit comments

Comments
 (0)