15
15
*/
16
16
package am .ik .yavi .core ;
17
17
18
+ import am .ik .yavi .jsr305 .Nullable ;
19
+ import am .ik .yavi .message .MessageFormatter ;
20
+ import am .ik .yavi .message .SimpleMessageFormatter ;
18
21
import java .util .Arrays ;
19
22
import java .util .Locale ;
20
23
import java .util .function .Function ;
21
24
22
- import am .ik .yavi .message .MessageFormatter ;
23
-
24
25
public class ConstraintViolation {
25
26
26
27
private final Object [] args ;
@@ -35,14 +36,26 @@ public class ConstraintViolation {
35
36
36
37
private final String name ;
37
38
38
- public ConstraintViolation (String name , String messageKey , String defaultMessageFormat , Object [] args ,
39
- MessageFormatter messageFormatter , Locale locale ) {
39
+ /**
40
+ * Creates a new constraint violation.
41
+ * @param name the field or property name that this constraint violation applies to
42
+ * @param messageKey the key used to look up localized messages
43
+ * @param defaultMessageFormat the default message format to use when no localized
44
+ * message is found
45
+ * @param args the arguments to be used when formatting the message
46
+ * @param messageFormatter the message formatter to be used
47
+ * @param locale the locale to be used for message localization
48
+ * @deprecated Use {@link #builder()} instead for a more fluent and type-safe API
49
+ */
50
+ @ Deprecated
51
+ public ConstraintViolation (String name , String messageKey , String defaultMessageFormat , @ Nullable Object [] args ,
52
+ @ Nullable MessageFormatter messageFormatter , @ Nullable Locale locale ) {
40
53
this .name = name ;
41
54
this .messageKey = messageKey ;
42
55
this .defaultMessageFormat = defaultMessageFormat ;
43
- this .args = args ;
44
- this .messageFormatter = messageFormatter ;
45
- this .locale = locale ;
56
+ this .args = args == null ? new Object [ 0 ] : args ;
57
+ this .messageFormatter = messageFormatter == null ? SimpleMessageFormatter . getInstance () : messageFormatter ;
58
+ this .locale = locale == null ? Locale . getDefault () : locale ;
46
59
}
47
60
48
61
public Object [] args () {
@@ -84,6 +97,11 @@ public Object violatedValue() {
84
97
}
85
98
86
99
/**
100
+ * Returns a new ConstraintViolation with the name transformed using the provided
101
+ * function. If the arguments array is not empty, the first element will be updated
102
+ * with the new name.
103
+ * @param rename the function to transform the current name
104
+ * @return a new ConstraintViolation with the renamed name
87
105
* @since 0.7.0
88
106
*/
89
107
public ConstraintViolation rename (Function <? super String , String > rename ) {
@@ -97,10 +115,332 @@ public ConstraintViolation rename(Function<? super String, String> rename) {
97
115
}
98
116
99
117
/**
118
+ * Returns a new ConstraintViolation with the name indexed by appending "[index]" to
119
+ * the current name.
120
+ * @param index the index to append to the name
121
+ * @return a new ConstraintViolation with the indexed name
100
122
* @since 0.7.0
101
123
*/
102
124
public ConstraintViolation indexed (int index ) {
103
125
return this .rename (name -> name + "[" + index + "]" );
104
126
}
105
127
128
+ /**
129
+ * Creates a new builder for constructing ConstraintViolation instances using a
130
+ * fluent, type-safe API. This builder implements a staged builder pattern to enforce
131
+ * required properties while making optional properties truly optional.
132
+ * @return the first stage of the builder which requires setting the name property
133
+ * @since 0.15.0
134
+ */
135
+ public static StagedBuilders .Name builder () {
136
+ return new Builder ();
137
+ }
138
+
139
+ /**
140
+ * Implementation of the staged builder pattern for creating ConstraintViolation
141
+ * instances. This builder class implements all the staged interfaces to provide a
142
+ * fluent and type-safe way to construct ConstraintViolation objects.
143
+ *
144
+ * @since 0.15.0
145
+ */
146
+ public static class Builder implements StagedBuilders .Name , StagedBuilders .MessageKey ,
147
+ StagedBuilders .DefaultMessageFormat , StagedBuilders .Optionals {
148
+
149
+ /** The field name that the constraint violation applies to */
150
+ private String name ;
151
+
152
+ /** The message key for internationalization */
153
+ private String messageKey ;
154
+
155
+ /** The default message format if no localized message is found */
156
+ private String defaultMessageFormat ;
157
+
158
+ /** The arguments to be used when formatting the message */
159
+ private Object [] args ;
160
+
161
+ /** The message formatter to be used for formatting the message */
162
+ private MessageFormatter messageFormatter ;
163
+
164
+ /** The locale to be used for message localization */
165
+ private Locale locale ;
166
+
167
+ /**
168
+ * Private constructor to prevent direct instantiation. Use
169
+ * {@link ConstraintViolation#builder()} instead.
170
+ */
171
+ private Builder () {
172
+ }
173
+
174
+ /**
175
+ * Sets the name of the field or property that this constraint violation applies
176
+ * to.
177
+ * @param name the field or property name
178
+ * @return the next stage of the builder for method chaining
179
+ */
180
+ public Builder name (String name ) {
181
+ this .name = name ;
182
+ return this ;
183
+ }
184
+
185
+ /**
186
+ * Sets the message key for internationalization lookup.
187
+ * @param messageKey the key used to look up localized messages
188
+ * @return the next stage of the builder for method chaining
189
+ */
190
+ public Builder messageKey (String messageKey ) {
191
+ this .messageKey = messageKey ;
192
+ return this ;
193
+ }
194
+
195
+ /**
196
+ * Sets the default message format to use when no localized message is found.
197
+ * @param defaultMessageFormat the default message format string
198
+ * @return the next stage of the builder for method chaining
199
+ */
200
+ public Builder defaultMessageFormat (String defaultMessageFormat ) {
201
+ this .defaultMessageFormat = defaultMessageFormat ;
202
+ return this ;
203
+ }
204
+
205
+ /**
206
+ * Sets the arguments to be used when formatting the message.
207
+ * @param args the arguments to be used in message formatting
208
+ * @return this builder for method chaining
209
+ */
210
+ public Builder args (Object ... args ) {
211
+ this .args = args ;
212
+ return this ;
213
+ }
214
+
215
+ /**
216
+ * Sets the arguments to be used when formatting the message, automatically
217
+ * including the name as the first argument.
218
+ *
219
+ * This method is more convenient than {@link #args(Object...)} when the name
220
+ * needs to be the first argument in the message, which is a common pattern for
221
+ * constraint violations.
222
+ * @param args the arguments to be used in message formatting (excluding the name)
223
+ * @return this builder for method chaining
224
+ */
225
+ public Builder argsWithPrependedName (Object ... args ) {
226
+ Object [] argsWithName = new Object [args .length + 1 ];
227
+ argsWithName [0 ] = this .name ;
228
+ System .arraycopy (args , 0 , argsWithName , 1 , args .length );
229
+ this .args = argsWithName ;
230
+ return this ;
231
+ }
232
+
233
+ /**
234
+ * Sets the arguments to be used when formatting the message, automatically
235
+ * prepending the name as the first argument and appending the violatedValue as
236
+ * the last argument.
237
+ *
238
+ * This method provides a complete solution for the most common constraint
239
+ * violation formatting pattern by automatically handling both the name and
240
+ * violated value positioning.
241
+ * @param args optional arguments to be used in message formatting (excluding both
242
+ * name and violated value)
243
+ * @param violatedValue the value object that violated the constraint, actual
244
+ * value is retrieved by calling value() method
245
+ * @return this builder for method chaining
246
+ */
247
+ public Builder argsWithPrependedNameAndAppendedViolatedValue (Object [] args , ViolatedValue violatedValue ) {
248
+ Object [] completeArgs = new Object [args .length + 2 ];
249
+ completeArgs [0 ] = this .name ;
250
+ System .arraycopy (args , 0 , completeArgs , 1 , args .length );
251
+ completeArgs [args .length + 1 ] = violatedValue .value ();
252
+ this .args = completeArgs ;
253
+ return this ;
254
+ }
255
+
256
+ /**
257
+ * Sets the message formatter to be used for message formatting. If not specified,
258
+ * a default {@link SimpleMessageFormatter} will be used.
259
+ * @param messageFormatter the message formatter to use
260
+ * @return this builder for method chaining
261
+ */
262
+ public Builder messageFormatter (MessageFormatter messageFormatter ) {
263
+ this .messageFormatter = messageFormatter ;
264
+ return this ;
265
+ }
266
+
267
+ /**
268
+ * Sets the locale to be used for message localization. If not specified, the
269
+ * system default locale will be used.
270
+ * @param locale the locale to use for message localization
271
+ * @return this builder for method chaining
272
+ */
273
+ public Builder locale (Locale locale ) {
274
+ this .locale = locale ;
275
+ return this ;
276
+ }
277
+
278
+ /**
279
+ * Builds a new {@link ConstraintViolation} instance with the configured
280
+ * properties.
281
+ * @return a new {@link ConstraintViolation} instance
282
+ */
283
+ public ConstraintViolation build () {
284
+ return new ConstraintViolation (name , messageKey , defaultMessageFormat , args , messageFormatter , locale );
285
+ }
286
+
287
+ }
288
+
289
+ /**
290
+ * Container interface for the staged builder interfaces. This interface hierarchy
291
+ * enables a type-safe builder pattern that enforces required properties to be set in
292
+ * a specific order before optional properties.
293
+ *
294
+ * @since 0.15.0
295
+ */
296
+ public interface StagedBuilders {
297
+
298
+ /**
299
+ * First stage of the builder which requires setting the name property.
300
+ */
301
+ interface Name {
302
+
303
+ /**
304
+ * Sets the name of the field or property that this constraint violation
305
+ * applies to.
306
+ * @param name the field or property name
307
+ * @return the next stage of the builder which requires setting the message
308
+ * key
309
+ */
310
+ MessageKey name (String name );
311
+
312
+ }
313
+
314
+ /**
315
+ * Second stage of the builder which requires setting the message key property.
316
+ */
317
+ interface MessageKey {
318
+
319
+ String DEFAULT_MESSAGE_KEY = "_" ;
320
+
321
+ /**
322
+ * Sets the message key for internationalization lookup.
323
+ * @param messageKey the key used to look up localized messages
324
+ * @return the next stage of the builder which requires setting the default
325
+ * message format
326
+ */
327
+ DefaultMessageFormat messageKey (String messageKey );
328
+
329
+ /**
330
+ * Convenient shortcut builder method that creates a complete constraint
331
+ * violation in a single call. This method automatically:
332
+ * <ul>
333
+ * <li>Sets the message key to the default value
334
+ * ({@link #DEFAULT_MESSAGE_KEY})</li>
335
+ * <li>Uses the provided message as the default message format</li>
336
+ * <li>Prepends the field name as the first argument in the argument list</li>
337
+ * <li>Builds and returns the final ConstraintViolation object</li>
338
+ * </ul>
339
+ *
340
+ * <p>
341
+ * This method is particularly useful for simple constraint violations where
342
+ * complex message formatting or internationalization is not required.
343
+ * @param message the message text to be used as the default message format
344
+ * @return a fully constructed {@link ConstraintViolation} instance with the
345
+ * specified message
346
+ * @since 0.15.0
347
+ */
348
+ default ConstraintViolation message (String message ) {
349
+ return this .messageKey (DEFAULT_MESSAGE_KEY )
350
+ .defaultMessageFormat (message )
351
+ .argsWithPrependedName ()
352
+ .build ();
353
+ }
354
+
355
+ }
356
+
357
+ /**
358
+ * Third stage of the builder which requires setting the default message format
359
+ * property.
360
+ */
361
+ interface DefaultMessageFormat {
362
+
363
+ /**
364
+ * Sets the default message format to use when no localized message is found.
365
+ * @param defaultMessageFormat the default message format string
366
+ * @return the final stage of the builder where all remaining properties are
367
+ * optional
368
+ */
369
+ Optionals defaultMessageFormat (String defaultMessageFormat );
370
+
371
+ }
372
+
373
+ /**
374
+ * Final stage of the builder where all remaining properties are optional. The
375
+ * build() method can be called at any point from this stage.
376
+ */
377
+ interface Optionals {
378
+
379
+ /**
380
+ * Sets the arguments to be used when formatting the message.
381
+ * @param args the arguments to be used in message formatting
382
+ * @return this builder for method chaining
383
+ */
384
+ Optionals args (Object ... args );
385
+
386
+ /**
387
+ * Sets the arguments to be used when formatting the message, automatically
388
+ * including the name as the first argument.
389
+ *
390
+ * This method is more convenient than {@link #args(Object...)} when the name
391
+ * needs to be the first argument in the message, which is a common pattern
392
+ * for constraint violations.
393
+ * @param args the arguments to be used in message formatting (excluding the
394
+ * name)
395
+ * @return this builder for method chaining
396
+ */
397
+ Optionals argsWithPrependedName (Object ... args );
398
+
399
+ /**
400
+ * Sets the arguments to be used when formatting the message, automatically
401
+ * prepending the name as the first argument and appending the violatedValue
402
+ * as the last argument.
403
+ *
404
+ * This method provides a complete solution for the most common constraint
405
+ * violation formatting pattern by automatically handling both the name and
406
+ * violated value positioning.
407
+ * @param args optional arguments to be used in message formatting (excluding
408
+ * both name and violated value)
409
+ * @param violatedValue the value object that violated the constraint, actual
410
+ * value is retrieved by calling value() method
411
+ * @return this builder for method chaining
412
+ */
413
+ Optionals argsWithPrependedNameAndAppendedViolatedValue (Object [] args , ViolatedValue violatedValue );
414
+
415
+ /**
416
+ * Sets the message formatter to be used for message formatting. If not
417
+ * prepending the name as the first argument and appending the violatedValue
418
+ * as the last argument.
419
+ *
420
+ * This method provides a complete solution for the most common constraint
421
+ * violation formatting pattern by automatically handling both the name and
422
+ * violated value positioning.
423
+ * @return this builder for method chaining
424
+ */
425
+ Optionals messageFormatter (MessageFormatter messageFormatter );
426
+
427
+ /**
428
+ * Sets the locale to be used for message localization. If not specified, the
429
+ * system default locale will be used.
430
+ * @param locale the locale to use for message localization
431
+ * @return this builder for method chaining
432
+ */
433
+ Optionals locale (Locale locale );
434
+
435
+ /**
436
+ * Builds a new {@link ConstraintViolation} instance with the configured
437
+ * properties.
438
+ * @return a new {@link ConstraintViolation} instance
439
+ */
440
+ ConstraintViolation build ();
441
+
442
+ }
443
+
444
+ }
445
+
106
446
}
0 commit comments