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  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     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     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     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     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     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     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     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     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     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     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     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     void testFailsToCreateExtendedMessageFormatTakingTwoArgumentsThrowsIllegalArgumentExceptionFive() {
454         assertThrowsExactly(IllegalArgumentException.class, () -> new ExtendedMessageFormat("j/[_D9{0,\"&'+0o", new HashMap<>()));
455     }
456 
457     @Test
458     void testFailsToCreateExtendedMessageFormatTakingTwoArgumentsThrowsIllegalArgumentExceptionFour() {
459         assertThrowsExactly(IllegalArgumentException.class, () -> new ExtendedMessageFormat("RD,nXhM{}{", new HashMap<>()));
460     }
461 
462     @Test
463     void testFailsToCreateExtendedMessageFormatTakingTwoArgumentsThrowsIllegalArgumentExceptionOne() {
464         assertThrowsExactly(IllegalArgumentException.class, () -> new ExtendedMessageFormat("agdXdkR;T1{9 ^,LzXf?", new HashMap<>()));
465     }
466 
467     @Test
468     void testFailsToCreateExtendedMessageFormatTakingTwoArgumentsThrowsIllegalArgumentExceptionThree() {
469         assertThrowsExactly(IllegalArgumentException.class, () -> new ExtendedMessageFormat("9jLh_D9{ ", new HashMap<>()));
470     }
471 
472     @Test
473     void testFailsToCreateExtendedMessageFormatTakingTwoArgumentsThrowsIllegalArgumentExceptionTwo() {
474         assertThrowsExactly(IllegalArgumentException.class, () -> new ExtendedMessageFormat("a5XdkR;T1{9 ,LzXf?", new HashMap<>()));
475     }
476 
477     @Test
478     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     void testSetFormatByArgumentIndexIsUnsupported() {
505         assertThrowsExactly(UnsupportedOperationException.class, () -> new ExtendedMessageFormat("").setFormatByArgumentIndex(0, new LowerCaseFormat()));
506     }
507 
508     @Test
509     void testSetFormatIsUnsupported() {
510         assertThrowsExactly(UnsupportedOperationException.class, () -> new ExtendedMessageFormat("").setFormat(0, new LowerCaseFormat()));
511     }
512 
513     @Test
514     void testSetFormatsByArgumentIndex() {
515         assertThrowsExactly(UnsupportedOperationException.class,
516                 () -> new ExtendedMessageFormat("").setFormatsByArgumentIndex(new Format[] { new LowerCaseFormat(), new UpperCaseFormat() }));
517     }
518 
519     @Test
520     void testSetFormatsIsUnsupported() {
521         assertThrowsExactly(UnsupportedOperationException.class,
522                 () -> new ExtendedMessageFormat("").setFormats(new Format[] { new LowerCaseFormat(), new UpperCaseFormat() }));
523     }
524 
525 }