• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

TAKETODAY / today-infrastructure / 18154768944

01 Oct 2025 07:26AM UTC coverage: 81.882% (-0.005%) from 81.887%
18154768944

push

github

web-flow
Merge pull request #290 from TAKETODAY/dev/jspecify

jspecify

59788 of 78013 branches covered (76.64%)

Branch coverage included in aggregate %.

141239 of 167496 relevant lines covered (84.32%)

3.6 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.94
today-context/src/main/java/infra/context/condition/ConditionMessage.java
1
/*
2
 * Copyright 2017 - 2025 the original author or authors.
3
 *
4
 * This program is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU General Public License as published by
6
 * the Free Software Foundation, either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see [https://www.gnu.org/licenses/]
16
 */
17

18
package infra.context.condition;
19

20
import org.jspecify.annotations.Nullable;
21

22
import java.lang.annotation.Annotation;
23
import java.util.ArrayList;
24
import java.util.Arrays;
25
import java.util.Collection;
26
import java.util.Collections;
27
import java.util.Objects;
28

29
import infra.lang.Assert;
30
import infra.util.ClassUtils;
31
import infra.util.ObjectUtils;
32
import infra.util.StringUtils;
33

34
/**
35
 * A message associated with a {@link ConditionOutcome}. Provides a fluent builder style
36
 * API to encourage consistency across all condition messages.
37
 *
38
 * @author Phillip Webb
39
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
40
 * @since 4.0
41
 */
42
public final class ConditionMessage {
43

44
  @Nullable
45
  private final String message;
46

47
  private ConditionMessage() {
48
    this(null);
3✔
49
  }
1✔
50

51
  private ConditionMessage(@Nullable String message) {
2✔
52
    this.message = message;
3✔
53
  }
1✔
54

55
  private ConditionMessage(ConditionMessage prior, String message) {
2✔
56
    this.message = prior.isEmpty() ? message : prior + "; " + message;
11✔
57
  }
1✔
58

59
  /**
60
   * Return {@code true} if the message is empty.
61
   *
62
   * @return if the message is empty
63
   */
64
  public boolean isEmpty() {
65
    return StringUtils.isEmpty(this.message);
4✔
66
  }
67

68
  @Override
69
  public boolean equals(Object object) {
70
    if (this == object)
3!
71
      return true;
×
72
    if (!(object instanceof ConditionMessage that))
7!
73
      return false;
×
74
    return Objects.equals(message, that.message);
6✔
75
  }
76

77
  @Override
78
  public int hashCode() {
79
    return Objects.hash(message);
9✔
80
  }
81

82
  @Override
83
  public String toString() {
84
    return this.message != null ? this.message : "";
8✔
85
  }
86

87
  /**
88
   * Return a new {@link ConditionMessage} based on the instance and an appended
89
   * message.
90
   *
91
   * @param message the message to append
92
   * @return a new {@link ConditionMessage} instance
93
   */
94
  public ConditionMessage append(@Nullable String message) {
95
    if (StringUtils.isEmpty(message)) {
3✔
96
      return this;
2✔
97
    }
98
    if (StringUtils.isEmpty(this.message)) {
4✔
99
      return new ConditionMessage(message);
5✔
100
    }
101

102
    return new ConditionMessage(this.message + " " + message);
8✔
103
  }
104

105
  /**
106
   * Return a new builder to construct a new {@link ConditionMessage} based on the
107
   * instance and a new condition outcome.
108
   *
109
   * @param condition the condition
110
   * @param details details of the condition
111
   * @return a {@link Builder} builder
112
   * @see #andCondition(String, Object...)
113
   * @see #forCondition(Class, Object...)
114
   */
115
  public Builder andCondition(Class<? extends Annotation> condition, Object... details) {
116
    Assert.notNull(condition, "Condition is required");
3✔
117
    return andCondition("@" + ClassUtils.getShortName(condition), details);
7✔
118
  }
119

120
  /**
121
   * Return a new builder to construct a new {@link ConditionMessage} based on the
122
   * instance and a new condition outcome.
123
   *
124
   * @param condition the condition
125
   * @param details details of the condition
126
   * @return a {@link Builder} builder
127
   * @see #andCondition(Class, Object...)
128
   * @see #forCondition(String, Object...)
129
   */
130
  public Builder andCondition(String condition, Object... details) {
131
    Assert.notNull(condition, "Condition is required");
3✔
132
    String detail = StringUtils.arrayToDelimitedString(details, " ");
4✔
133
    if (StringUtils.isNotEmpty(detail)) {
3✔
134
      return new Builder(condition + " " + detail);
8✔
135
    }
136
    return new Builder(condition);
6✔
137
  }
138

139
  /**
140
   * Factory method to return a new empty {@link ConditionMessage}.
141
   *
142
   * @return a new empty {@link ConditionMessage}
143
   */
144
  public static ConditionMessage empty() {
145
    return new ConditionMessage();
4✔
146
  }
147

148
  /**
149
   * Factory method to create a new {@link ConditionMessage} with a specific message.
150
   *
151
   * @param message the source message (may be a format string if {@code args} are
152
   * specified)
153
   * @param args format arguments for the message
154
   * @return a new {@link ConditionMessage} instance
155
   */
156
  public static ConditionMessage of(String message, Object... args) {
157
    if (ObjectUtils.isEmpty(args)) {
3!
158
      return new ConditionMessage(message);
5✔
159
    }
160
    return new ConditionMessage(String.format(message, args));
×
161
  }
162

163
  /**
164
   * Factory method to create a new {@link ConditionMessage} comprised of the specified
165
   * messages.
166
   *
167
   * @param messages the source messages (may be {@code null})
168
   * @return a new {@link ConditionMessage} instance
169
   */
170
  public static ConditionMessage of(@Nullable Collection<? extends ConditionMessage> messages) {
171
    ConditionMessage result = new ConditionMessage();
4✔
172
    if (messages != null) {
2✔
173
      for (ConditionMessage message : messages) {
10✔
174
        result = new ConditionMessage(result, message.toString());
7✔
175
      }
1✔
176
    }
177
    return result;
2✔
178
  }
179

180
  /**
181
   * Factory method for a builder to construct a new {@link ConditionMessage} for a
182
   * condition.
183
   *
184
   * @param condition the condition
185
   * @param details details of the condition
186
   * @return a {@link Builder} builder
187
   * @see #forCondition(String, Object...)
188
   * @see #andCondition(String, Object...)
189
   */
190
  public static Builder forCondition(Class<? extends Annotation> condition, Object... details) {
191
    return new ConditionMessage().andCondition(condition, details);
7✔
192
  }
193

194
  /**
195
   * Factory method for a builder to construct a new {@link ConditionMessage} for a
196
   * condition.
197
   *
198
   * @param condition the condition
199
   * @param details details of the condition
200
   * @return a {@link Builder} builder
201
   * @see #forCondition(Class, Object...)
202
   * @see #andCondition(String, Object...)
203
   */
204
  public static Builder forCondition(String condition, Object... details) {
205
    return new ConditionMessage().andCondition(condition, details);
7✔
206
  }
207

208
  /**
209
   * Builder used to create a {@link ConditionMessage} for a condition.
210
   */
211
  public final class Builder {
212

213
    private final String condition;
214

215
    private Builder(String condition) {
5✔
216
      this.condition = condition;
3✔
217
    }
1✔
218

219
    /**
220
     * Indicate that an exact result was found. For example
221
     * {@code foundExactly("foo")} results in the message "found foo".
222
     *
223
     * @param result the result that was found
224
     * @return a built {@link ConditionMessage}
225
     */
226
    public ConditionMessage foundExactly(Object result) {
227
      return found("").items(result);
11✔
228
    }
229

230
    /**
231
     * Indicate that one or more results were found. For example
232
     * {@code found("bean").items("x")} results in the message "found bean x".
233
     *
234
     * @param article the article found
235
     * @return an {@link ItemsBuilder}
236
     */
237
    public ItemsBuilder found(String article) {
238
      return found(article, article);
5✔
239
    }
240

241
    /**
242
     * Indicate that one or more results were found. For example
243
     * {@code found("bean", "beans").items("x", "y")} results in the message "found
244
     * beans x, y".
245
     *
246
     * @param singular the article found in singular form
247
     * @param plural the article found in plural form
248
     * @return an {@link ItemsBuilder}
249
     */
250
    public ItemsBuilder found(String singular, String plural) {
251
      return new ItemsBuilder(this, "found", singular, plural);
10✔
252
    }
253

254
    /**
255
     * Indicate that one or more results were not found. For example
256
     * {@code didNotFind("bean").items("x")} results in the message "did not find bean
257
     * x".
258
     *
259
     * @param article the article found
260
     * @return an {@link ItemsBuilder}
261
     */
262
    public ItemsBuilder didNotFind(String article) {
263
      return didNotFind(article, article);
5✔
264
    }
265

266
    /**
267
     * Indicate that one or more results were found. For example
268
     * {@code didNotFind("bean", "beans").items("x", "y")} results in the message "did
269
     * not find beans x, y".
270
     *
271
     * @param singular the article found in singular form
272
     * @param plural the article found in plural form
273
     * @return an {@link ItemsBuilder}
274
     */
275
    public ItemsBuilder didNotFind(String singular, String plural) {
276
      return new ItemsBuilder(this, "did not find", singular, plural);
10✔
277
    }
278

279
    /**
280
     * Indicates a single result. For example {@code resultedIn("yes")} results in the
281
     * message "resulted in yes".
282
     *
283
     * @param result the result
284
     * @return a built {@link ConditionMessage}
285
     */
286
    public ConditionMessage resultedIn(Object result) {
287
      return because("resulted in " + result);
6✔
288
    }
289

290
    /**
291
     * Indicates something is available. For example {@code available("money")}
292
     * results in the message "money is available".
293
     *
294
     * @param item the item that is available
295
     * @return a built {@link ConditionMessage}
296
     */
297
    public ConditionMessage available(String item) {
298
      return because(item + " is available");
5✔
299
    }
300

301
    /**
302
     * Indicates something is not available. For example {@code notAvailable("time")}
303
     * results in the message "time is not available".
304
     *
305
     * @param item the item that is not available
306
     * @return a built {@link ConditionMessage}
307
     */
308
    public ConditionMessage notAvailable(String item) {
309
      return because(item + " is not available");
5✔
310
    }
311

312
    /**
313
     * Indicates the reason. For example {@code because("running Linux")} results in
314
     * the message "running Linux".
315
     *
316
     * @param reason the reason for the message
317
     * @return a built {@link ConditionMessage}
318
     */
319
    public ConditionMessage because(@Nullable String reason) {
320
      if (StringUtils.isNotEmpty(reason)) {
3✔
321
        return new ConditionMessage(
5✔
322
                ConditionMessage.this,
323
                StringUtils.isNotEmpty(this.condition) ? this.condition + " " + reason : reason);
11✔
324
      }
325
      return new ConditionMessage(ConditionMessage.this, this.condition);
8✔
326
    }
327

328
  }
329

330
  /**
331
   * Builder used to create an {@link ItemsBuilder} for a condition.
332
   */
333
  public final class ItemsBuilder {
334

335
    private final Builder condition;
336

337
    private final String reason;
338

339
    private final String singular;
340

341
    private final String plural;
342

343
    private ItemsBuilder(Builder condition, String reason, String singular, String plural) {
5✔
344
      this.condition = condition;
3✔
345
      this.reason = reason;
3✔
346
      this.singular = singular;
3✔
347
      this.plural = plural;
3✔
348
    }
1✔
349

350
    /**
351
     * Used when no items are available. For example
352
     * {@code didNotFind("any beans").atAll()} results in the message "did not find
353
     * any beans".
354
     *
355
     * @return a built {@link ConditionMessage}
356
     */
357
    public ConditionMessage atAll() {
358
      return items(Collections.emptyList());
4✔
359
    }
360

361
    /**
362
     * Indicate the items. For example
363
     * {@code didNotFind("bean", "beans").items("x", "y")} results in the message "did
364
     * not find beans x, y".
365
     *
366
     * @param items the items (may be {@code null})
367
     * @return a built {@link ConditionMessage}
368
     */
369
    public ConditionMessage items(Object... items) {
370
      return items(Style.NORMAL, items);
5✔
371
    }
372

373
    /**
374
     * Indicate the items. For example
375
     * {@code didNotFind("bean", "beans").items("x", "y")} results in the message "did
376
     * not find beans x, y".
377
     *
378
     * @param style the render style
379
     * @param items the items (may be {@code null})
380
     * @return a built {@link ConditionMessage}
381
     */
382
    public ConditionMessage items(Style style, @Nullable Object... items) {
383
      return items(style, (items != null) ? Arrays.asList(items) : null);
9!
384
    }
385

386
    /**
387
     * Indicate the items. For example
388
     * {@code didNotFind("bean", "beans").items(Collections.singleton("x")} results in
389
     * the message "did not find bean x".
390
     *
391
     * @param items the source of the items (may be {@code null})
392
     * @return a built {@link ConditionMessage}
393
     */
394
    public ConditionMessage items(Collection<?> items) {
395
      return items(Style.NORMAL, items);
5✔
396
    }
397

398
    /**
399
     * Indicate the items with a {@link Style}. For example
400
     * {@code didNotFind("bean", "beans").items(Style.QUOTE, Collections.singleton("x")}
401
     * results in the message "did not find bean 'x'".
402
     *
403
     * @param style the render style
404
     * @param items the source of the items (may be {@code null})
405
     * @return a built {@link ConditionMessage}
406
     */
407
    public ConditionMessage items(Style style, @Nullable Collection<?> items) {
408
      Assert.notNull(style, "Style is required");
3✔
409
      StringBuilder message = new StringBuilder(this.reason);
6✔
410
      items = style.applyTo(items);
4✔
411
      if ((items == null || items.size() <= 1)
8✔
412
              && StringUtils.isNotEmpty(this.singular)) {
2✔
413
        message.append(" ").append(this.singular);
8✔
414
      }
415
      else if (StringUtils.isNotEmpty(this.plural)) {
4✔
416
        message.append(" ").append(this.plural);
7✔
417
      }
418
      if (items != null && !items.isEmpty()) {
5✔
419
        message.append(" ").append(StringUtils.collectionToDelimitedString(items, ", "));
8✔
420
      }
421
      return this.condition.because(message.toString());
6✔
422
    }
423

424
  }
425

426
  /**
427
   * Render styles.
428
   */
429
  public enum Style {
3✔
430

431
    /**
432
     * Render with normal styling.
433
     */
434
    NORMAL {
11✔
435
      @Override
436
      protected Object applyToItem(Object item) {
437
        return item;
2✔
438
      }
439

440
    },
441

442
    /**
443
     * Render with the item surrounded by quotes.
444
     */
445
    QUOTE {
11✔
446
      @Override
447
      @Nullable
448
      protected String applyToItem(@Nullable Object item) {
449
        return (item != null) ? "'" + item + "'" : null;
7!
450
      }
451

452
    };
453

454
    @Nullable
455
    public Collection<?> applyTo(@Nullable Collection<?> items) {
456
      if (items == null) {
2✔
457
        return null;
2✔
458
      }
459
      ArrayList<Object> result = new ArrayList<>(items.size());
6✔
460
      for (Object item : items) {
9✔
461
        result.add(applyToItem(item));
6✔
462
      }
1✔
463
      return result;
2✔
464
    }
465

466
    @Nullable
467
    protected abstract Object applyToItem(Object item);
468

469
  }
470

471
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc