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