8
8
import java .util .List ;
9
9
import java .util .Map ;
10
10
import java .util .Objects ;
11
- import java .util .stream .Collectors ;
12
11
import java .util .stream .Stream ;
13
12
14
13
import ai .timefold .solver .core .api .score .Score ;
@@ -60,6 +59,11 @@ static <Score_ extends Score<Score_>> ConstraintAnalysis<Score_> of(ConstraintRe
60
59
Objects .requireNonNull (score );
61
60
}
62
61
62
+ /**
63
+ * Return the match count of the constraint.
64
+ *
65
+ * @throws IllegalStateException if the {@link ConstraintAnalysis#matches()} is null
66
+ */
63
67
public int matchCount () {
64
68
if (matches == null ) {
65
69
throw new IllegalArgumentException ("""
@@ -86,9 +90,9 @@ static <Score_ extends Score<Score_>> ConstraintAnalysis<Score_> diff(
86
90
ConstraintAnalysis <Score_ > otherConstraintAnalysis ) {
87
91
if (constraintAnalysis == null ) {
88
92
if (otherConstraintAnalysis == null ) {
89
- throw new IllegalStateException ("""
90
- Impossible state: none of the score explanations provided constraint matches for a constraint (%s).
91
- """ .formatted (constraintRef ));
93
+ throw new IllegalStateException (
94
+ " Impossible state: none of the score explanations provided constraint matches for a constraint (%s)."
95
+ .formatted (constraintRef ));
92
96
}
93
97
// No need to compute diff; this constraint is not present in this score explanation.
94
98
return otherConstraintAnalysis .negate ();
@@ -99,9 +103,9 @@ static <Score_ extends Score<Score_>> ConstraintAnalysis<Score_> diff(
99
103
var matchAnalyses = constraintAnalysis .matches ();
100
104
var otherMatchAnalyses = otherConstraintAnalysis .matches ();
101
105
if ((matchAnalyses == null && otherMatchAnalyses != null ) || (matchAnalyses != null && otherMatchAnalyses == null )) {
102
- throw new IllegalStateException ("""
103
- Impossible state: Only one of the score analyses (%s, %s) provided match analyses for a constraint (%s)."" "
104
- .formatted (constraintAnalysis , otherConstraintAnalysis , constraintRef ));
106
+ throw new IllegalStateException (
107
+ " Impossible state: Only one of the score analyses (%s, %s) provided match analyses for a constraint (%s)."
108
+ .formatted (constraintAnalysis , otherConstraintAnalysis , constraintRef ));
105
109
}
106
110
// Compute the diff.
107
111
var constraintWeightDifference = constraintAnalysis .weight ().subtract (otherConstraintAnalysis .weight ());
@@ -118,9 +122,9 @@ static <Score_ extends Score<Score_>> ConstraintAnalysis<Score_> diff(
118
122
var otherMatchAnalysis = otherMatchAnalysisMap .get (justification );
119
123
if (matchAnalysis == null ) {
120
124
if (otherMatchAnalysis == null ) {
121
- throw new IllegalStateException ("""
122
- Impossible state: none of the match analyses provided for a constraint (%s).
123
- """ .formatted (constraintRef ));
125
+ throw new IllegalStateException (
126
+ " Impossible state: none of the match analyses provided for a constraint (%s)."
127
+ .formatted (constraintRef ));
124
128
}
125
129
// No need to compute diff; this match is not present in this score explanation.
126
130
return otherMatchAnalysis .negate ();
@@ -132,7 +136,7 @@ static <Score_ extends Score<Score_>> ConstraintAnalysis<Score_> diff(
132
136
justification );
133
137
}
134
138
})
135
- .collect ( Collectors . toList () );
139
+ .toList ();
136
140
return new ConstraintAnalysis <>(constraintRef , constraintWeightDifference , scoreDifference , result );
137
141
}
138
142
@@ -172,11 +176,12 @@ public String constraintName() {
172
176
173
177
/**
174
178
* Returns a diagnostic text that explains part of the score quality through the {@link ConstraintAnalysis} API.
179
+ * The string is built fresh every time the method is called.
175
180
*
176
181
* @return never null
177
182
*/
178
183
public String summarize () {
179
- StringBuilder summary = new StringBuilder ();
184
+ var summary = new StringBuilder ();
180
185
summary .append ("""
181
186
Explanation of score (%s):
182
187
Constraint matches:
@@ -191,25 +196,20 @@ Explanation of score (%s):
191
196
""" );
192
197
}
193
198
if (constraintMatches .isEmpty ()) {
194
- summary .append ("""
195
- % s: constraint (%s) has no matches.
196
- """ . formatted ( score (). toShortString (), constraintRef ().constraintName ()));
199
+ summary .append (
200
+ "%8s% s: constraint (%s) has no matches.\n " . formatted ( " " , score (). toShortString (),
201
+ constraintRef ().constraintName ()));
197
202
} else {
198
- summary .append ("""
199
- %s: constraint (%s) has %s matches:
200
- """ .formatted (score ().toShortString (), constraintRef ().constraintName (), constraintMatches .size ()));
203
+ summary .append ("%8s%s: constraint (%s) has %s matches:\n " .formatted (" " , score ().toShortString (),
204
+ constraintRef ().constraintName (), constraintMatches .size ()));
201
205
}
202
206
constraintMatches .stream ()
203
207
.sorted (matchScoreComparator )
204
208
.limit (DEFAULT_SUMMARY_CONSTRAINT_MATCH_LIMIT )
205
- .forEach (match -> summary .append ("""
206
- %s: justified with (%s)
207
- """ .formatted (match .score ().toShortString (),
209
+ .forEach (match -> summary .append ("%12S%s: justified with (%s)\n " .formatted (" " , match .score ().toShortString (),
208
210
match .justification ())));
209
211
if (constraintMatches .size () > DEFAULT_SUMMARY_CONSTRAINT_MATCH_LIMIT ) {
210
- summary .append ("""
211
- ...
212
- """ );
212
+ summary .append ("%12s%s\n " .formatted (" " , "..." ));
213
213
}
214
214
215
215
return summary .toString ();
0 commit comments