1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.text;
18
19 import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
20 import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
21 import static org.junit.jupiter.api.Assertions.assertEquals;
22 import static org.junit.jupiter.api.Assertions.assertNotEquals;
23
24 import java.text.DateFormat;
25 import java.text.FieldPosition;
26 import java.text.Format;
27 import java.text.MessageFormat;
28 import java.text.NumberFormat;
29 import java.text.ParsePosition;
30 import java.util.Arrays;
31 import java.util.Calendar;
32 import java.util.Collections;
33 import java.util.HashMap;
34 import java.util.HashSet;
35 import java.util.Locale;
36 import java.util.Map;
37
38 import org.junit.jupiter.api.BeforeEach;
39 import org.junit.jupiter.api.Test;
40
41
42
43
44 public class ExtendedMessageFormatTest {
45
46
47
48
49 private static final class LowerCaseFormat extends Format {
50 static final Format INSTANCE = new LowerCaseFormat();
51 static final FormatFactory FACTORY = (n, a, l) -> LowerCaseFormat.INSTANCE;
52 private static final long serialVersionUID = 1L;
53
54 @Override
55 public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) {
56 return toAppendTo.append(((String) obj).toLowerCase(Locale.ROOT));
57 }
58
59 @Override
60 public Object parseObject(final String source, final ParsePosition pos) {
61 throw new UnsupportedOperationException();
62 }
63 }
64
65
66
67
68 private static final class OtherExtendedMessageFormat extends ExtendedMessageFormat {
69 private static final long serialVersionUID = 1L;
70
71 OtherExtendedMessageFormat(final String pattern, final Locale locale,
72 final Map<String, ? extends FormatFactory> registry) {
73 super(pattern, locale, registry);
74 }
75 }
76
77
78
79
80 private static final class OverrideShortDateFormatFactory {
81 static final FormatFactory FACTORY = (n, a, l) -> !"short".equals(a) ? null
82 : l == null ? DateFormat
83 .getDateInstance(DateFormat.DEFAULT) : DateFormat
84 .getDateInstance(DateFormat.DEFAULT, l);
85 }
86
87
88
89
90 private static final class UpperCaseFormat extends Format {
91 static final Format INSTANCE = new UpperCaseFormat();
92 static final FormatFactory FACTORY = (n, a, l) -> UpperCaseFormat.INSTANCE;
93 private static final long serialVersionUID = 1L;
94
95 @Override
96 public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) {
97 return toAppendTo.append(((String) obj).toUpperCase(Locale.ROOT));
98 }
99
100 @Override
101 public Object parseObject(final String source, final ParsePosition pos) {
102 throw new UnsupportedOperationException();
103 }
104 }
105
106 private final Map<String, FormatFactory> registry = new HashMap<>();
107
108
109
110
111
112
113
114
115
116 private void checkBuiltInFormat(final String pattern, final Map<String, ?> registryUnused, final Object[] args,
117 final Locale locale) {
118 final StringBuilder buffer = new StringBuilder();
119 buffer.append("Pattern=[");
120 buffer.append(pattern);
121 buffer.append("], locale=[");
122 buffer.append(locale);
123 buffer.append("]");
124 final MessageFormat mf = createMessageFormat(pattern, locale);
125
126 ExtendedMessageFormat emf = null;
127 if (locale == null) {
128 emf = new ExtendedMessageFormat(pattern);
129 } else {
130 emf = new ExtendedMessageFormat(pattern, locale);
131 }
132 assertEquals(mf.format(args), emf.format(args), "format " + buffer.toString());
133 assertEquals(mf.toPattern(), emf.toPattern(), "toPattern " + buffer.toString());
134 }
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198 private void checkBuiltInFormat(final String pattern, final Map<String, ?> fmtRegistry, final Object[] args,
199 final Locale[] locales) {
200 checkBuiltInFormat(pattern, fmtRegistry, args, (Locale) null);
201 for (final Locale locale : locales) {
202 checkBuiltInFormat(pattern, fmtRegistry, args, locale);
203 }
204 }
205
206
207
208
209
210
211
212 private void checkBuiltInFormat(final String pattern, final Object[] args, final Locale[] locales) {
213 checkBuiltInFormat(pattern, null, args, locales);
214 }
215
216
217
218
219
220
221
222 private MessageFormat createMessageFormat(final String pattern, final Locale locale) {
223 final MessageFormat result = new MessageFormat(pattern);
224 if (locale != null) {
225 result.setLocale(locale);
226 result.applyPattern(pattern);
227 }
228 return result;
229 }
230
231 @BeforeEach
232 public void setUp() {
233 registry.put("lower", LowerCaseFormat.FACTORY);
234 registry.put("upper", UpperCaseFormat.FACTORY);
235 }
236
237
238
239
240 @Test
241 public void testBuiltInChoiceFormat() {
242 final Object[] values = new Number[] {1, Double.valueOf("2.2"), Double.valueOf("1234.5")};
243 String choicePattern;
244 final Locale[] availableLocales = NumberFormat.getAvailableLocales();
245
246 choicePattern = "{0,choice,1#One|2#Two|3#Many {0,number}}";
247 for (final Object value : values) {
248 checkBuiltInFormat(value + ": " + choicePattern, new Object[] {value}, availableLocales);
249 }
250
251 choicePattern = "{0,choice,1#''One''|2#\"Two\"|3#''{Many}'' {0,number}}";
252 for (final Object value : values) {
253 checkBuiltInFormat(value + ": " + choicePattern, new Object[] {value}, availableLocales);
254 }
255 }
256
257
258
259
260 @Test
261 public void testBuiltInDateTimeFormat() {
262 final Calendar cal = Calendar.getInstance();
263 cal.set(2007, Calendar.JANUARY, 23, 18, 33, 5);
264 final Object[] args = {cal.getTime()};
265 final Locale[] availableLocales = DateFormat.getAvailableLocales();
266
267 checkBuiltInFormat("1: {0,date,short}", args, availableLocales);
268 checkBuiltInFormat("2: {0,date,medium}", args, availableLocales);
269 checkBuiltInFormat("3: {0,date,long}", args, availableLocales);
270 checkBuiltInFormat("4: {0,date,full}", args, availableLocales);
271 checkBuiltInFormat("5: {0,date,d MMM yy}", args, availableLocales);
272 checkBuiltInFormat("6: {0,time,short}", args, availableLocales);
273 checkBuiltInFormat("7: {0,time,medium}", args, availableLocales);
274 checkBuiltInFormat("8: {0,time,long}", args, availableLocales);
275 checkBuiltInFormat("9: {0,time,full}", args, availableLocales);
276 checkBuiltInFormat("10: {0,time,HH:mm}", args, availableLocales);
277 checkBuiltInFormat("11: {0,date}", args, availableLocales);
278 checkBuiltInFormat("12: {0,time}", args, availableLocales);
279 }
280
281
282
283
284 @Test
285 public void testBuiltInNumberFormat() {
286 final Object[] args = {Double.valueOf("6543.21")};
287 final Locale[] availableLocales = NumberFormat.getAvailableLocales();
288 checkBuiltInFormat("1: {0,number}", args, availableLocales);
289 checkBuiltInFormat("2: {0,number,integer}", args, availableLocales);
290 checkBuiltInFormat("3: {0,number,currency}", args, availableLocales);
291 checkBuiltInFormat("4: {0,number,percent}", args, availableLocales);
292 checkBuiltInFormat("5: {0,number,00000.000}", args, availableLocales);
293 }
294
295
296
297
298
299 @Test
300 public void testChoiceQuoteJustBeforeBraceEnd_TEXT_106() {
301 final String pattern2 = "Choice format element with quote just before brace end ''{0,choice,0#0|0<'1'}''";
302 final ExtendedMessageFormat emf = new ExtendedMessageFormat(pattern2, registry);
303 assertEquals("Choice format element with quote just before brace end '0'", emf.format(new Object[] {0}));
304 assertEquals("Choice format element with quote just before brace end '1'", emf.format(new Object[] {1}));
305 }
306
307 @Test
308 public void testCreatesExtendedMessageFormatTakingString() {
309 final ExtendedMessageFormat extendedMessageFormat =
310 new ExtendedMessageFormat("Unterminated format element at position ");
311 final Map<String, FormatFactory> map = new HashMap<>();
312 final ExtendedMessageFormat extendedMessageFormatTwo =
313 new ExtendedMessageFormat("Unterminated format element at position ", map);
314
315 assertEquals("Unterminated format element at position ", extendedMessageFormatTwo.toPattern());
316 assertNotEquals(extendedMessageFormat, extendedMessageFormatTwo);
317 }
318
319
320
321
322 @Test
323 public void testEmbeddedPatternInChoice() {
324 final String pattern = "Hi {0,lower}, got {1,choice,0#none|1#one|1<{1,number}}, {2,upper}!";
325 final ExtendedMessageFormat emf = new ExtendedMessageFormat(pattern, registry);
326 assertEquals("Hi there, got 3, GREAT!", emf.format(new Object[] {"there", 3, "great"}));
327 }
328
329
330
331
332 @Test
333 public void testEqualsHashcode() {
334 final Map<String, ? extends FormatFactory> fmtRegistry =
335 Collections.singletonMap("testfmt", LowerCaseFormat.FACTORY);
336 final Map<String, ? extends FormatFactory> otherRegistry =
337 Collections.singletonMap("testfmt", UpperCaseFormat.FACTORY);
338
339 final String pattern = "Pattern: {0,testfmt}";
340 final ExtendedMessageFormat emf = new ExtendedMessageFormat(pattern, Locale.US, fmtRegistry);
341
342 ExtendedMessageFormat other;
343
344
345 assertEquals(emf, emf, "same, equals()");
346 assertEquals(emf.hashCode(), emf.hashCode(), "same, hashCode()");
347
348 assertNotEquals(null, emf, "null, equals");
349
350
351 other = new ExtendedMessageFormat(pattern, Locale.US, fmtRegistry);
352 assertEquals(emf, other, "equal, equals()");
353 assertEquals(emf.hashCode(), other.hashCode(), "equal, hashCode()");
354
355
356 other = new OtherExtendedMessageFormat(pattern, Locale.US, fmtRegistry);
357 assertNotEquals(emf, other, "class, equals()");
358 assertEquals(emf.hashCode(), other.hashCode(), "class, hashCode()");
359
360
361 other = new ExtendedMessageFormat("X" + pattern, Locale.US, fmtRegistry);
362 assertNotEquals(emf, other, "pattern, equals()");
363 assertNotEquals(emf.hashCode(), other.hashCode(), "pattern, hashCode()");
364
365
366 other = new ExtendedMessageFormat(pattern, Locale.US, otherRegistry);
367 assertNotEquals(emf, other, "registry, equals()");
368 assertNotEquals(emf.hashCode(), other.hashCode(), "registry, hashCode()");
369
370
371 other = new ExtendedMessageFormat(pattern, Locale.FRANCE, fmtRegistry);
372 assertNotEquals(emf, other, "locale, equals()");
373 assertEquals(emf.hashCode(), other.hashCode(), "locale, hashCode()");
374 }
375
376
377
378
379 @Test
380 public void testEscapedBraces_LANG_948() {
381
382 final String pattern = "Message without placeholders '{}'";
383 final ExtendedMessageFormat emf = new ExtendedMessageFormat(pattern, registry);
384 assertEquals("Message without placeholders {}", emf.format(new Object[] {"DUMMY"}));
385
386
387 final String pattern2 = "Message with placeholder ''{0}''";
388 final ExtendedMessageFormat emf2 = new ExtendedMessageFormat(pattern2, registry);
389 assertEquals("Message with placeholder 'DUMMY'", emf2.format(new Object[] {"DUMMY"}));
390 }
391
392
393
394
395 @Test
396 public void testEscapedQuote_LANG_477() {
397 final String pattern = "it''s a {0,lower} 'test'!";
398 final ExtendedMessageFormat emf = new ExtendedMessageFormat(pattern, registry);
399 assertEquals("it's a dummy test!", emf.format(new Object[] {"DUMMY"}));
400 }
401
402
403
404
405 @Test
406 public void testExtendedAndBuiltInFormats() {
407 final Calendar cal = Calendar.getInstance();
408 cal.set(2007, Calendar.JANUARY, 23, 18, 33, 5);
409 final Object[] args = {"John Doe", cal.getTime(), Double.valueOf("12345.67")};
410 final String builtinsPattern = "DOB: {1,date,short} Salary: {2,number,currency}";
411 final String extendedPattern = "Name: {0,upper} ";
412 final String pattern = extendedPattern + builtinsPattern;
413
414 final HashSet<Locale> testLocales = new HashSet<>(Arrays.asList(DateFormat.getAvailableLocales()));
415 testLocales.retainAll(Arrays.asList(NumberFormat.getAvailableLocales()));
416 testLocales.add(null);
417
418 for (final Locale locale : testLocales) {
419 final MessageFormat builtins = createMessageFormat(builtinsPattern, locale);
420 final String expectedPattern = extendedPattern + builtins.toPattern();
421 DateFormat df = null;
422 NumberFormat nf = null;
423 ExtendedMessageFormat emf = null;
424 if (locale == null) {
425 df = DateFormat.getDateInstance(DateFormat.SHORT);
426 nf = NumberFormat.getCurrencyInstance();
427 emf = new ExtendedMessageFormat(pattern, registry);
428 } else {
429 df = DateFormat.getDateInstance(DateFormat.SHORT, locale);
430 nf = NumberFormat.getCurrencyInstance(locale);
431 emf = new ExtendedMessageFormat(pattern, locale, registry);
432 }
433 final StringBuilder expected = new StringBuilder();
434 expected.append("Name: ");
435 expected.append(args[0].toString().toUpperCase(Locale.ROOT));
436 expected.append(" DOB: ");
437 expected.append(df.format(args[1]));
438 expected.append(" Salary: ");
439 expected.append(nf.format(args[2]));
440 assertEquals(expectedPattern, emf.toPattern(), "pattern comparison for locale " + locale);
441 assertEquals(expected.toString(), emf.format(args), String.valueOf(locale));
442 }
443 }
444
445
446
447
448 @Test
449 public void testExtendedFormats() {
450 final String pattern = "Lower: {0,lower} Upper: {1,upper}";
451 final ExtendedMessageFormat emf = new ExtendedMessageFormat(pattern, registry);
452 assertEquals(pattern, emf.toPattern(), "TOPATTERN");
453 assertEquals("Lower: foo Upper: BAR", emf.format(new Object[] {"foo", "bar"}));
454 assertEquals("Lower: foo Upper: BAR", emf.format(new Object[] {"Foo", "Bar"}));
455 assertEquals("Lower: foo Upper: BAR", emf.format(new Object[] {"FOO", "BAR"}));
456 assertEquals("Lower: foo Upper: BAR", emf.format(new Object[] {"FOO", "bar"}));
457 assertEquals("Lower: foo Upper: BAR", emf.format(new Object[] {"foo", "BAR"}));
458 }
459
460 @Test
461 public void testFailsToCreateExtendedMessageFormatTakingTwoArgumentsThrowsIllegalArgumentExceptionFive() {
462 assertThatIllegalArgumentException().isThrownBy(() -> new ExtendedMessageFormat("j/[_D9{0,\"&'+0o", new HashMap<>()));
463 }
464
465 @Test
466 public void testFailsToCreateExtendedMessageFormatTakingTwoArgumentsThrowsIllegalArgumentExceptionFour() {
467 assertThatIllegalArgumentException().isThrownBy(() -> new ExtendedMessageFormat("RD,nXhM{}{", new HashMap<>()));
468 }
469
470 @Test
471 public void testFailsToCreateExtendedMessageFormatTakingTwoArgumentsThrowsIllegalArgumentExceptionOne() {
472 assertThatIllegalArgumentException().isThrownBy(() -> new ExtendedMessageFormat("agdXdkR;T1{9 ^,LzXf?", new HashMap<>()));
473 }
474
475 @Test
476 public void testFailsToCreateExtendedMessageFormatTakingTwoArgumentsThrowsIllegalArgumentExceptionThree() {
477 assertThatIllegalArgumentException().isThrownBy(() -> new ExtendedMessageFormat("9jLh_D9{ ", new HashMap<>()));
478 }
479
480 @Test
481 public void testFailsToCreateExtendedMessageFormatTakingTwoArgumentsThrowsIllegalArgumentExceptionTwo() {
482 assertThatIllegalArgumentException().isThrownBy(() -> new ExtendedMessageFormat("a5XdkR;T1{9 ,LzXf?", new HashMap<>()));
483 }
484
485 @Test
486 public void testOverriddenBuiltinFormat() {
487 final Calendar cal = Calendar.getInstance();
488 cal.set(2007, Calendar.JANUARY, 23);
489 final Object[] args = { cal.getTime() };
490 final Locale[] availableLocales = DateFormat.getAvailableLocales();
491 final Map<String, ? extends FormatFactory> dateRegistry = Collections.singletonMap("date", OverrideShortDateFormatFactory.FACTORY);
492
493
494 checkBuiltInFormat("1: {0,date}", dateRegistry, args, availableLocales);
495 checkBuiltInFormat("2: {0,date,medium}", dateRegistry, args, availableLocales);
496 checkBuiltInFormat("3: {0,date,long}", dateRegistry, args, availableLocales);
497 checkBuiltInFormat("4: {0,date,full}", dateRegistry, args, availableLocales);
498 checkBuiltInFormat("5: {0,date,d MMM yy}", dateRegistry, args, availableLocales);
499
500
501 for (int i = -1; i < availableLocales.length; i++) {
502 final Locale locale = i < 0 ? null : availableLocales[i];
503 final MessageFormat dateDefault = createMessageFormat("{0,date}", locale);
504 final String pattern = "{0,date,short}";
505 final ExtendedMessageFormat dateShort = new ExtendedMessageFormat(pattern, locale, dateRegistry);
506 assertEquals(dateDefault.format(args), dateShort.format(args), "overridden date,short format");
507 assertEquals(pattern, dateShort.toPattern(), "overridden date,short pattern");
508 }
509 }
510
511 @Test
512 public void testSetFormatByArgumentIndexIsUnsupported() {
513 assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> {
514 final ExtendedMessageFormat emf = new ExtendedMessageFormat("");
515 emf.setFormatByArgumentIndex(0, new LowerCaseFormat());
516 });
517 }
518 @Test
519 public void testSetFormatIsUnsupported() {
520 assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> {
521 final ExtendedMessageFormat emf = new ExtendedMessageFormat("");
522 emf.setFormat(0, new LowerCaseFormat());
523 });
524 }
525 @Test
526 public void testSetFormatsByArgumentIndex() {
527 assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> {
528 final ExtendedMessageFormat emf = new ExtendedMessageFormat("");
529 emf.setFormatsByArgumentIndex(new Format[] {new LowerCaseFormat(), new UpperCaseFormat()});
530 });
531 }
532
533 @Test
534 public void testSetFormatsIsUnsupported() {
535 assertThatExceptionOfType(UnsupportedOperationException.class).isThrownBy(() -> {
536 final ExtendedMessageFormat emf = new ExtendedMessageFormat("");
537 emf.setFormats(new Format[] {new LowerCaseFormat(), new UpperCaseFormat()});
538 });
539 }
540
541 }