View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.lang3.text;
18  
19  import org.junit.Test;
20  import org.junit.Before;
21  import static org.junit.Assert.*;
22  import static org.apache.commons.lang3.JavaVersion.JAVA_1_4;
23  
24  import java.text.ChoiceFormat;
25  import java.text.DateFormat;
26  import java.text.FieldPosition;
27  import java.text.Format;
28  import java.text.MessageFormat;
29  import java.text.NumberFormat;
30  import java.text.ParsePosition;
31  import java.util.Arrays;
32  import java.util.Calendar;
33  import java.util.Collections;
34  import java.util.HashMap;
35  import java.util.HashSet;
36  import java.util.Locale;
37  import java.util.Map;
38  
39  import org.apache.commons.lang3.SystemUtils;
40  
41  /**
42   * Test case for {@link ExtendedMessageFormat}.
43   *
44   * @since 2.4
45   * @version $Id: ExtendedMessageFormatTest.java 1666679 2015-03-14 12:46:02Z britter $
46   */
47  public class ExtendedMessageFormatTest {
48  
49      private final Map<String, FormatFactory> registry = new HashMap<String, FormatFactory>();
50  
51      @Before
52      public void setUp() throws Exception {
53          registry.put("lower", new LowerCaseFormatFactory());
54          registry.put("upper", new UpperCaseFormatFactory());
55      }
56  
57      /**
58       * Test extended formats.
59       */
60      @Test
61      public void testExtendedFormats() {
62          final String pattern = "Lower: {0,lower} Upper: {1,upper}";
63          final ExtendedMessageFormat emf = new ExtendedMessageFormat(pattern, registry);
64          assertPatternsEqual("TOPATTERN", pattern, emf.toPattern());
65          assertEquals("Lower: foo Upper: BAR", emf.format(new Object[] {"foo", "bar"}));
66          assertEquals("Lower: foo Upper: BAR", emf.format(new Object[] {"Foo", "Bar"}));
67          assertEquals("Lower: foo Upper: BAR", emf.format(new Object[] {"FOO", "BAR"}));
68          assertEquals("Lower: foo Upper: BAR", emf.format(new Object[] {"FOO", "bar"}));
69          assertEquals("Lower: foo Upper: BAR", emf.format(new Object[] {"foo", "BAR"}));
70      }
71  
72      /**
73       * Test Bug LANG-477 - out of memory error with escaped quote
74       */
75      @Test
76      public void testEscapedQuote_LANG_477() {
77          final String pattern = "it''s a {0,lower} 'test'!";
78          final ExtendedMessageFormat emf = new ExtendedMessageFormat(pattern, registry);
79          assertEquals("it's a dummy test!", emf.format(new Object[] {"DUMMY"}));
80      }
81  
82      /**
83       * Test Bug LANG-917 - IndexOutOfBoundsException and/or infinite loop when using a choice pattern
84       */
85      @Test
86      public void testEmbeddedPatternInChoice() {
87          final String pattern = "Hi {0,lower}, got {1,choice,0#none|1#one|1<{1,number}}, {2,upper}!";
88          final ExtendedMessageFormat emf = new ExtendedMessageFormat(pattern, registry);
89          assertEquals(emf.format(new Object[] {"there", 3, "great"}), "Hi there, got 3, GREAT!");
90      }
91  
92      /**
93       * Test Bug LANG-948 - Exception while using ExtendedMessageFormat and escaping braces
94       */
95      @Test
96      public void testEscapedBraces_LANG_948() {
97          // message without placeholder because braces are escaped by quotes 
98          final String pattern = "Message without placeholders '{}'";
99          final ExtendedMessageFormat emf = new ExtendedMessageFormat(pattern, registry);
100         assertEquals("Message without placeholders {}", emf.format(new Object[] {"DUMMY"}));
101 
102         // message with placeholder because quotes are escaped by quotes 
103         final String pattern2 = "Message with placeholder ''{0}''";
104         final ExtendedMessageFormat emf2 = new ExtendedMessageFormat(pattern2, registry);
105         assertEquals("Message with placeholder 'DUMMY'", emf2.format(new Object[] {"DUMMY"}));
106     }
107 
108     /**
109      * Test extended and built in formats.
110      */
111     @Test
112     public void testExtendedAndBuiltInFormats() {
113         final Calendar cal = Calendar.getInstance();
114         cal.set(2007, Calendar.JANUARY, 23, 18, 33, 05);
115         final Object[] args = new Object[] {"John Doe", cal.getTime(), Double.valueOf("12345.67")};
116         final String builtinsPattern = "DOB: {1,date,short} Salary: {2,number,currency}";
117         final String extendedPattern = "Name: {0,upper} ";
118         final String pattern = extendedPattern + builtinsPattern;
119 
120         final HashSet<Locale> testLocales = new HashSet<Locale>();
121         testLocales.addAll(Arrays.asList(DateFormat.getAvailableLocales()));
122         testLocales.retainAll(Arrays.asList(NumberFormat.getAvailableLocales()));
123         testLocales.add(null);
124 
125         for (final Locale locale : testLocales) {
126             final MessageFormat builtins = createMessageFormat(builtinsPattern, locale);
127             final String expectedPattern = extendedPattern + builtins.toPattern();
128             DateFormat df = null;
129             NumberFormat nf = null;
130             ExtendedMessageFormat emf = null;
131             if (locale == null) {
132                 df = DateFormat.getDateInstance(DateFormat.SHORT);
133                 nf = NumberFormat.getCurrencyInstance();
134                 emf = new ExtendedMessageFormat(pattern, registry);
135             } else {
136                 df = DateFormat.getDateInstance(DateFormat.SHORT, locale);
137                 nf = NumberFormat.getCurrencyInstance(locale);
138                 emf = new ExtendedMessageFormat(pattern, locale, registry);
139             }
140             final StringBuilder expected = new StringBuilder();
141             expected.append("Name: ");
142             expected.append(args[0].toString().toUpperCase());
143             expected.append(" DOB: ");
144             expected.append(df.format(args[1]));
145             expected.append(" Salary: ");
146             expected.append(nf.format(args[2]));
147             assertPatternsEqual("pattern comparison for locale " + locale, expectedPattern, emf.toPattern());
148             assertEquals(String.valueOf(locale), expected.toString(), emf.format(args));
149         }
150     }
151 
152 //    /**
153 //     * Test extended formats with choice format.
154 //     *
155 //     * NOTE: FAILING - currently sub-formats not supported
156 //     */
157 //    public void testExtendedWithChoiceFormat() {
158 //        String pattern = "Choice: {0,choice,1.0#{1,lower}|2.0#{1,upper}}";
159 //        ExtendedMessageFormat emf = new ExtendedMessageFormat(pattern, registry);
160 //        assertPatterns(null, pattern, emf.toPattern());
161 //        try {
162 //            assertEquals("one", emf.format(new Object[] {Integer.valueOf(1), "ONE"}));
163 //            assertEquals("TWO", emf.format(new Object[] {Integer.valueOf(2), "two"}));
164 //        } catch (IllegalArgumentException e) {
165 //            // currently sub-formats not supported
166 //        }
167 //    }
168 
169 //    /**
170 //     * Test mixed extended and built-in formats with choice format.
171 //     *
172 //     * NOTE: FAILING - currently sub-formats not supported
173 //     */
174 //    public void testExtendedAndBuiltInWithChoiceFormat() {
175 //        String pattern = "Choice: {0,choice,1.0#{0} {1,lower} {2,number}|2.0#{0} {1,upper} {2,number,currency}}";
176 //        Object[] lowArgs  = new Object[] {Integer.valueOf(1), "Low",  Double.valueOf("1234.56")};
177 //        Object[] highArgs = new Object[] {Integer.valueOf(2), "High", Double.valueOf("9876.54")};
178 //        Locale[] availableLocales = ChoiceFormat.getAvailableLocales();
179 //        Locale[] testLocales = new Locale[availableLocales.length + 1];
180 //        testLocales[0] = null;
181 //        System.arraycopy(availableLocales, 0, testLocales, 1, availableLocales.length);
182 //        for (int i = 0; i < testLocales.length; i++) {
183 //            NumberFormat nf = null;
184 //            NumberFormat cf = null;
185 //            ExtendedMessageFormat emf = null;
186 //            if (testLocales[i] == null) {
187 //                nf = NumberFormat.getNumberInstance();
188 //                cf = NumberFormat.getCurrencyInstance();
189 //                emf = new ExtendedMessageFormat(pattern, registry);
190 //            } else {
191 //                nf = NumberFormat.getNumberInstance(testLocales[i]);
192 //                cf = NumberFormat.getCurrencyInstance(testLocales[i]);
193 //                emf = new ExtendedMessageFormat(pattern, testLocales[i], registry);
194 //            }
195 //            assertPatterns(null, pattern, emf.toPattern());
196 //            try {
197 //                String lowExpected = lowArgs[0] + " low "    + nf.format(lowArgs[2]);
198 //                String highExpected = highArgs[0] + " HIGH "  + cf.format(highArgs[2]);
199 //                assertEquals(lowExpected,  emf.format(lowArgs));
200 //                assertEquals(highExpected, emf.format(highArgs));
201 //            } catch (IllegalArgumentException e) {
202 //                // currently sub-formats not supported
203 //            }
204 //        }
205 //    }
206 
207     /**
208      * Test the built in choice format.
209      */
210     @Test
211     public void testBuiltInChoiceFormat() {
212         final Object[] values = new Number[] {Integer.valueOf(1), Double.valueOf("2.2"), Double.valueOf("1234.5")};
213         String choicePattern = null;
214         final Locale[] availableLocales = ChoiceFormat.getAvailableLocales();
215 
216         choicePattern = "{0,choice,1#One|2#Two|3#Many {0,number}}";
217         for (final Object value : values) {
218             checkBuiltInFormat(value + ": " + choicePattern, new Object[] {value}, availableLocales);
219         }
220 
221         choicePattern = "{0,choice,1#''One''|2#\"Two\"|3#''{Many}'' {0,number}}";
222         for (final Object value : values) {
223             checkBuiltInFormat(value + ": " + choicePattern, new Object[] {value}, availableLocales);
224         }
225     }
226 
227     /**
228      * Test the built in date/time formats
229      */
230     @Test
231     public void testBuiltInDateTimeFormat() {
232         final Calendar cal = Calendar.getInstance();
233         cal.set(2007, Calendar.JANUARY, 23, 18, 33, 05);
234         final Object[] args = new Object[] {cal.getTime()};
235         final Locale[] availableLocales = DateFormat.getAvailableLocales();
236 
237         checkBuiltInFormat("1: {0,date,short}",    args, availableLocales);
238         checkBuiltInFormat("2: {0,date,medium}",   args, availableLocales);
239         checkBuiltInFormat("3: {0,date,long}",     args, availableLocales);
240         checkBuiltInFormat("4: {0,date,full}",     args, availableLocales);
241         checkBuiltInFormat("5: {0,date,d MMM yy}", args, availableLocales);
242         checkBuiltInFormat("6: {0,time,short}",    args, availableLocales);
243         checkBuiltInFormat("7: {0,time,medium}",   args, availableLocales);
244         checkBuiltInFormat("8: {0,time,long}",     args, availableLocales);
245         checkBuiltInFormat("9: {0,time,full}",     args, availableLocales);
246         checkBuiltInFormat("10: {0,time,HH:mm}",   args, availableLocales);
247         checkBuiltInFormat("11: {0,date}",         args, availableLocales);
248         checkBuiltInFormat("12: {0,time}",         args, availableLocales);
249     }
250 
251     @Test
252     public void testOverriddenBuiltinFormat() {
253         final Calendar cal = Calendar.getInstance();
254         cal.set(2007, Calendar.JANUARY, 23);
255         final Object[] args = new Object[] {cal.getTime()};
256         final Locale[] availableLocales = DateFormat.getAvailableLocales();
257         final Map<String, ? extends FormatFactory> dateRegistry = Collections.singletonMap("date", new OverrideShortDateFormatFactory());
258 
259         //check the non-overridden builtins:
260         checkBuiltInFormat("1: {0,date}", dateRegistry,          args, availableLocales);
261         checkBuiltInFormat("2: {0,date,medium}", dateRegistry,   args, availableLocales);
262         checkBuiltInFormat("3: {0,date,long}", dateRegistry,     args, availableLocales);
263         checkBuiltInFormat("4: {0,date,full}", dateRegistry,     args, availableLocales);
264         checkBuiltInFormat("5: {0,date,d MMM yy}", dateRegistry, args, availableLocales);
265 
266         //check the overridden format:
267         for (int i = -1; i < availableLocales.length; i++) {
268             final Locale locale = i < 0 ? null : availableLocales[i];
269             final MessageFormat dateDefault = createMessageFormat("{0,date}", locale);
270             final String pattern = "{0,date,short}";
271             final ExtendedMessageFormat dateShort = new ExtendedMessageFormat(pattern, locale, dateRegistry);
272             assertEquals("overridden date,short format", dateDefault.format(args), dateShort.format(args));
273             assertEquals("overridden date,short pattern", pattern, dateShort.toPattern());
274         }
275     }
276 
277     /**
278      * Test the built in number formats.
279      */
280     @Test
281     public void testBuiltInNumberFormat() {
282         final Object[] args = new Object[] {Double.valueOf("6543.21")};
283         final Locale[] availableLocales = NumberFormat.getAvailableLocales();
284         checkBuiltInFormat("1: {0,number}",            args, availableLocales);
285         checkBuiltInFormat("2: {0,number,integer}",    args, availableLocales);
286         checkBuiltInFormat("3: {0,number,currency}",   args, availableLocales);
287         checkBuiltInFormat("4: {0,number,percent}",    args, availableLocales);
288         checkBuiltInFormat("5: {0,number,00000.000}",  args, availableLocales);
289     }
290 
291     /**
292      * Test equals() and hashcode.
293      */
294     @Test
295     public void testEqualsHashcode() {
296         final Map<String, ? extends FormatFactory> fmtRegistry = Collections.singletonMap("testfmt", new LowerCaseFormatFactory());
297         final Map<String, ? extends FormatFactory> otherRegitry = Collections.singletonMap("testfmt", new UpperCaseFormatFactory());
298 
299         final String pattern = "Pattern: {0,testfmt}";
300         final ExtendedMessageFormat emf = new ExtendedMessageFormat(pattern, Locale.US, fmtRegistry);
301 
302         ExtendedMessageFormat other = null;
303 
304         // Same object
305         assertTrue("same, equals()",   emf.equals(emf));
306         assertTrue("same, hashcode()", emf.hashCode() == emf.hashCode());
307 
308         // Equal Object
309         other = new ExtendedMessageFormat(pattern, Locale.US, fmtRegistry);
310         assertTrue("equal, equals()",   emf.equals(other));
311         assertTrue("equal, hashcode()", emf.hashCode() == other.hashCode());
312 
313         // Different Class
314         other = new OtherExtendedMessageFormat(pattern, Locale.US, fmtRegistry);
315         assertFalse("class, equals()",  emf.equals(other));
316         assertTrue("class, hashcode()", emf.hashCode() == other.hashCode()); // same hashcode
317         
318         // Different pattern
319         other = new ExtendedMessageFormat("X" + pattern, Locale.US, fmtRegistry);
320         assertFalse("pattern, equals()",   emf.equals(other));
321         assertFalse("pattern, hashcode()", emf.hashCode() == other.hashCode());
322 
323         // Different registry
324         other = new ExtendedMessageFormat(pattern, Locale.US, otherRegitry);
325         assertFalse("registry, equals()",   emf.equals(other));
326         assertFalse("registry, hashcode()", emf.hashCode() == other.hashCode());
327 
328         // Different Locale
329         other = new ExtendedMessageFormat(pattern, Locale.FRANCE, fmtRegistry);
330         assertFalse("locale, equals()",  emf.equals(other));
331         assertTrue("locale, hashcode()", emf.hashCode() == other.hashCode()); // same hashcode
332     }
333 
334     /**
335      * Test a built in format for the specified Locales, plus <code>null</code> Locale.
336      * @param pattern MessageFormat pattern
337      * @param args MessageFormat arguments
338      * @param locales to test
339      */
340     private void checkBuiltInFormat(final String pattern, final Object[] args, final Locale[] locales) {
341         checkBuiltInFormat(pattern, null, args, locales);
342     }
343 
344     /**
345      * Test a built in format for the specified Locales, plus <code>null</code> Locale.
346      * @param pattern MessageFormat pattern
347      * @param fmtRegistry FormatFactory registry to use
348      * @param args MessageFormat arguments
349      * @param locales to test
350      */
351     private void checkBuiltInFormat(final String pattern, final Map<String, ?> fmtRegistry, final Object[] args, final Locale[] locales) {
352         checkBuiltInFormat(pattern, fmtRegistry, args, (Locale) null);
353         for (final Locale locale : locales) {
354             checkBuiltInFormat(pattern, fmtRegistry, args, locale);
355         }
356     }
357 
358     /**
359      * Create an ExtendedMessageFormat for the specified pattern and locale and check the
360      * formated output matches the expected result for the parameters.
361      * @param pattern string
362      * @param registryUnused map (currently unused)
363      * @param args Object[]
364      * @param locale Locale
365      */
366     private void checkBuiltInFormat(final String pattern, final Map<String, ?> registryUnused, final Object[] args, final Locale locale) {
367         final StringBuilder buffer = new StringBuilder();
368         buffer.append("Pattern=[");
369         buffer.append(pattern);
370         buffer.append("], locale=[");
371         buffer.append(locale);
372         buffer.append("]");
373         final MessageFormat mf = createMessageFormat(pattern, locale);
374         // System.out.println(buffer + ", result=[" + mf.format(args) +"]");
375         ExtendedMessageFormat emf = null;
376         if (locale == null) {
377             emf = new ExtendedMessageFormat(pattern);
378         } else {
379             emf = new ExtendedMessageFormat(pattern, locale);
380         }
381         assertEquals("format "    + buffer.toString(), mf.format(args), emf.format(args));
382         assertPatternsEqual("toPattern " + buffer.toString(), mf.toPattern(),  emf.toPattern());
383     }
384 
385     //can't trust what MessageFormat does with toPattern() pre 1.4:
386     private void assertPatternsEqual(final String message, final String expected, final String actual) {
387         if (SystemUtils.isJavaVersionAtLeast(JAVA_1_4)) {
388             assertEquals(message, expected, actual);
389         }
390     }
391 
392     /**
393      * Replace MessageFormat(String, Locale) constructor (not available until JDK 1.4).
394      * @param pattern string
395      * @param locale Locale
396      * @return MessageFormat
397      */
398     private MessageFormat createMessageFormat(final String pattern, final Locale locale) {
399         final MessageFormat result = new MessageFormat(pattern);
400         if (locale != null) {
401             result.setLocale(locale);
402             result.applyPattern(pattern);
403         }
404         return result;
405     }
406 
407     // ------------------------ Test Formats ------------------------
408 
409     /**
410      * {@link Format} implementation which converts to lower case.
411      */
412     private static class LowerCaseFormat extends Format {
413         private static final long serialVersionUID = 1L;
414 
415         @Override
416         public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) {
417             return toAppendTo.append(((String)obj).toLowerCase());
418         }
419         @Override
420         public Object parseObject(final String source, final ParsePosition pos) {throw new UnsupportedOperationException();}
421     }
422 
423     /**
424      * {@link Format} implementation which converts to upper case.
425      */
426     private static class UpperCaseFormat extends Format {
427         private static final long serialVersionUID = 1L;
428 
429         @Override
430         public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) {
431             return toAppendTo.append(((String)obj).toUpperCase());
432         }
433         @Override
434         public Object parseObject(final String source, final ParsePosition pos) {throw new UnsupportedOperationException();}
435     }
436 
437 
438     // ------------------------ Test Format Factories ---------------
439     /**
440      * {@link FormatFactory} implementation for lower case format.
441      */
442     private static class LowerCaseFormatFactory implements FormatFactory {
443         private static final Format LOWER_INSTANCE = new LowerCaseFormat();
444         @Override
445         public Format getFormat(final String name, final String arguments, final Locale locale) {
446             return LOWER_INSTANCE;
447         }
448     }
449     /**
450      * {@link FormatFactory} implementation for upper case format.
451      */
452     private static class UpperCaseFormatFactory implements FormatFactory {
453         private static final Format UPPER_INSTANCE = new UpperCaseFormat();
454         @Override
455         public Format getFormat(final String name, final String arguments, final Locale locale) {
456             return UPPER_INSTANCE;
457         }
458     }
459     /**
460      * {@link FormatFactory} implementation to override date format "short" to "default".
461      */
462     private static class OverrideShortDateFormatFactory implements FormatFactory {
463         @Override
464         public Format getFormat(final String name, final String arguments, final Locale locale) {
465             return !"short".equals(arguments) ? null
466                     : locale == null ? DateFormat
467                             .getDateInstance(DateFormat.DEFAULT) : DateFormat
468                             .getDateInstance(DateFormat.DEFAULT, locale);
469         }
470     }
471 
472     /**
473      * Alternative ExtendedMessageFormat impl.
474      */
475     private static class OtherExtendedMessageFormat extends ExtendedMessageFormat {
476         private static final long serialVersionUID = 1L;
477 
478         public OtherExtendedMessageFormat(final String pattern, final Locale locale,
479                 final Map<String, ? extends FormatFactory> registry) {
480             super(pattern, locale, registry);
481         }
482         
483     }
484 
485 }