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 1436770 2013-01-22 07:09:45Z ggregory $
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 extended and built in formats.
84       */
85      @Test
86      public void testExtendedAndBuiltInFormats() {
87          final Calendar cal = Calendar.getInstance();
88          cal.set(2007, Calendar.JANUARY, 23, 18, 33, 05);
89          final Object[] args = new Object[] {"John Doe", cal.getTime(), Double.valueOf("12345.67")};
90          final String builtinsPattern = "DOB: {1,date,short} Salary: {2,number,currency}";
91          final String extendedPattern = "Name: {0,upper} ";
92          final String pattern = extendedPattern + builtinsPattern;
93  
94          final HashSet<Locale> testLocales = new HashSet<Locale>();
95          testLocales.addAll(Arrays.asList(DateFormat.getAvailableLocales()));
96          testLocales.retainAll(Arrays.asList(NumberFormat.getAvailableLocales()));
97          testLocales.add(null);
98  
99          for (final Locale locale : testLocales) {
100             final MessageFormat builtins = createMessageFormat(builtinsPattern, locale);
101             final String expectedPattern = extendedPattern + builtins.toPattern();
102             DateFormat df = null;
103             NumberFormat nf = null;
104             ExtendedMessageFormat emf = null;
105             if (locale == null) {
106                 df = DateFormat.getDateInstance(DateFormat.SHORT);
107                 nf = NumberFormat.getCurrencyInstance();
108                 emf = new ExtendedMessageFormat(pattern, registry);
109             } else {
110                 df = DateFormat.getDateInstance(DateFormat.SHORT, locale);
111                 nf = NumberFormat.getCurrencyInstance(locale);
112                 emf = new ExtendedMessageFormat(pattern, locale, registry);
113             }
114             final StringBuilder expected = new StringBuilder();
115             expected.append("Name: ");
116             expected.append(args[0].toString().toUpperCase());
117             expected.append(" DOB: ");
118             expected.append(df.format(args[1]));
119             expected.append(" Salary: ");
120             expected.append(nf.format(args[2]));
121             assertPatternsEqual("pattern comparison for locale " + locale, expectedPattern, emf.toPattern());
122             assertEquals(String.valueOf(locale), expected.toString(), emf.format(args));
123         }
124     }
125 
126 //    /**
127 //     * Test extended formats with choice format.
128 //     *
129 //     * NOTE: FAILING - currently sub-formats not supported
130 //     */
131 //    public void testExtendedWithChoiceFormat() {
132 //        String pattern = "Choice: {0,choice,1.0#{1,lower}|2.0#{1,upper}}";
133 //        ExtendedMessageFormat emf = new ExtendedMessageFormat(pattern, registry);
134 //        assertPatterns(null, pattern, emf.toPattern());
135 //        try {
136 //            assertEquals("one", emf.format(new Object[] {Integer.valueOf(1), "ONE"}));
137 //            assertEquals("TWO", emf.format(new Object[] {Integer.valueOf(2), "two"}));
138 //        } catch (IllegalArgumentException e) {
139 //            // currently sub-formats not supported
140 //        }
141 //    }
142 
143 //    /**
144 //     * Test mixed extended and built-in formats with choice format.
145 //     *
146 //     * NOTE: FAILING - currently sub-formats not supported
147 //     */
148 //    public void testExtendedAndBuiltInWithChoiceFormat() {
149 //        String pattern = "Choice: {0,choice,1.0#{0} {1,lower} {2,number}|2.0#{0} {1,upper} {2,number,currency}}";
150 //        Object[] lowArgs  = new Object[] {Integer.valueOf(1), "Low",  Double.valueOf("1234.56")};
151 //        Object[] highArgs = new Object[] {Integer.valueOf(2), "High", Double.valueOf("9876.54")};
152 //        Locale[] availableLocales = ChoiceFormat.getAvailableLocales();
153 //        Locale[] testLocales = new Locale[availableLocales.length + 1];
154 //        testLocales[0] = null;
155 //        System.arraycopy(availableLocales, 0, testLocales, 1, availableLocales.length);
156 //        for (int i = 0; i < testLocales.length; i++) {
157 //            NumberFormat nf = null;
158 //            NumberFormat cf = null;
159 //            ExtendedMessageFormat emf = null;
160 //            if (testLocales[i] == null) {
161 //                nf = NumberFormat.getNumberInstance();
162 //                cf = NumberFormat.getCurrencyInstance();
163 //                emf = new ExtendedMessageFormat(pattern, registry);
164 //            } else {
165 //                nf = NumberFormat.getNumberInstance(testLocales[i]);
166 //                cf = NumberFormat.getCurrencyInstance(testLocales[i]);
167 //                emf = new ExtendedMessageFormat(pattern, testLocales[i], registry);
168 //            }
169 //            assertPatterns(null, pattern, emf.toPattern());
170 //            try {
171 //                String lowExpected = lowArgs[0] + " low "    + nf.format(lowArgs[2]);
172 //                String highExpected = highArgs[0] + " HIGH "  + cf.format(highArgs[2]);
173 //                assertEquals(lowExpected,  emf.format(lowArgs));
174 //                assertEquals(highExpected, emf.format(highArgs));
175 //            } catch (IllegalArgumentException e) {
176 //                // currently sub-formats not supported
177 //            }
178 //        }
179 //    }
180 
181     /**
182      * Test the built in choice format.
183      */
184     @Test
185     public void testBuiltInChoiceFormat() {
186         final Object[] values = new Number[] {Integer.valueOf(1), Double.valueOf("2.2"), Double.valueOf("1234.5")};
187         String choicePattern = null;
188         final Locale[] availableLocales = ChoiceFormat.getAvailableLocales();
189 
190         choicePattern = "{0,choice,1#One|2#Two|3#Many {0,number}}";
191         for (final Object value : values) {
192             checkBuiltInFormat(value + ": " + choicePattern, new Object[] {value}, availableLocales);
193         }
194 
195         choicePattern = "{0,choice,1#''One''|2#\"Two\"|3#''{Many}'' {0,number}}";
196         for (final Object value : values) {
197             checkBuiltInFormat(value + ": " + choicePattern, new Object[] {value}, availableLocales);
198         }
199     }
200 
201     /**
202      * Test the built in date/time formats
203      */
204     @Test
205     public void testBuiltInDateTimeFormat() {
206         final Calendar cal = Calendar.getInstance();
207         cal.set(2007, Calendar.JANUARY, 23, 18, 33, 05);
208         final Object[] args = new Object[] {cal.getTime()};
209         final Locale[] availableLocales = DateFormat.getAvailableLocales();
210 
211         checkBuiltInFormat("1: {0,date,short}",    args, availableLocales);
212         checkBuiltInFormat("2: {0,date,medium}",   args, availableLocales);
213         checkBuiltInFormat("3: {0,date,long}",     args, availableLocales);
214         checkBuiltInFormat("4: {0,date,full}",     args, availableLocales);
215         checkBuiltInFormat("5: {0,date,d MMM yy}", args, availableLocales);
216         checkBuiltInFormat("6: {0,time,short}",    args, availableLocales);
217         checkBuiltInFormat("7: {0,time,medium}",   args, availableLocales);
218         checkBuiltInFormat("8: {0,time,long}",     args, availableLocales);
219         checkBuiltInFormat("9: {0,time,full}",     args, availableLocales);
220         checkBuiltInFormat("10: {0,time,HH:mm}",   args, availableLocales);
221         checkBuiltInFormat("11: {0,date}",         args, availableLocales);
222         checkBuiltInFormat("12: {0,time}",         args, availableLocales);
223     }
224 
225     @Test
226     public void testOverriddenBuiltinFormat() {
227         final Calendar cal = Calendar.getInstance();
228         cal.set(2007, Calendar.JANUARY, 23);
229         final Object[] args = new Object[] {cal.getTime()};
230         final Locale[] availableLocales = DateFormat.getAvailableLocales();
231         final Map<String, ? extends FormatFactory> registry = Collections.singletonMap("date", new OverrideShortDateFormatFactory());
232 
233         //check the non-overridden builtins:
234         checkBuiltInFormat("1: {0,date}", registry,          args, availableLocales);
235         checkBuiltInFormat("2: {0,date,medium}", registry,   args, availableLocales);
236         checkBuiltInFormat("3: {0,date,long}", registry,     args, availableLocales);
237         checkBuiltInFormat("4: {0,date,full}", registry,     args, availableLocales);
238         checkBuiltInFormat("5: {0,date,d MMM yy}", registry, args, availableLocales);
239 
240         //check the overridden format:
241         for (int i = -1; i < availableLocales.length; i++) {
242             final Locale locale = i < 0 ? null : availableLocales[i];
243             final MessageFormat dateDefault = createMessageFormat("{0,date}", locale);
244             final String pattern = "{0,date,short}";
245             final ExtendedMessageFormat dateShort = new ExtendedMessageFormat(pattern, locale, registry);
246             assertEquals("overridden date,short format", dateDefault.format(args), dateShort.format(args));
247             assertEquals("overridden date,short pattern", pattern, dateShort.toPattern());
248         }
249     }
250 
251     /**
252      * Test the built in number formats.
253      */
254     @Test
255     public void testBuiltInNumberFormat() {
256         final Object[] args = new Object[] {Double.valueOf("6543.21")};
257         final Locale[] availableLocales = NumberFormat.getAvailableLocales();
258         checkBuiltInFormat("1: {0,number}",            args, availableLocales);
259         checkBuiltInFormat("2: {0,number,integer}",    args, availableLocales);
260         checkBuiltInFormat("3: {0,number,currency}",   args, availableLocales);
261         checkBuiltInFormat("4: {0,number,percent}",    args, availableLocales);
262         checkBuiltInFormat("5: {0,number,00000.000}",  args, availableLocales);
263     }
264 
265     /**
266      * Test equals() and hashcode.
267      */
268     @Test
269     public void testEqualsHashcode() {
270         final Map<String, ? extends FormatFactory> registry = Collections.singletonMap("testfmt", new LowerCaseFormatFactory());
271         final Map<String, ? extends FormatFactory> otherRegitry = Collections.singletonMap("testfmt", new UpperCaseFormatFactory());
272 
273         final String pattern = "Pattern: {0,testfmt}";
274         final ExtendedMessageFormat emf = new ExtendedMessageFormat(pattern, Locale.US, registry);
275 
276         ExtendedMessageFormat other = null;
277 
278         // Same object
279         assertTrue("same, equals()",   emf.equals(emf));
280         assertTrue("same, hashcode()", emf.hashCode() == emf.hashCode());
281 
282         // Equal Object
283         other = new ExtendedMessageFormat(pattern, Locale.US, registry);
284         assertTrue("equal, equals()",   emf.equals(other));
285         assertTrue("equal, hashcode()", emf.hashCode() == other.hashCode());
286 
287         // Different Class
288         other = new OtherExtendedMessageFormat(pattern, Locale.US, registry);
289         assertFalse("class, equals()",  emf.equals(other));
290         assertTrue("class, hashcode()", emf.hashCode() == other.hashCode()); // same hashcode
291         
292         // Different pattern
293         other = new ExtendedMessageFormat("X" + pattern, Locale.US, registry);
294         assertFalse("pattern, equals()",   emf.equals(other));
295         assertFalse("pattern, hashcode()", emf.hashCode() == other.hashCode());
296 
297         // Different registry
298         other = new ExtendedMessageFormat(pattern, Locale.US, otherRegitry);
299         assertFalse("registry, equals()",   emf.equals(other));
300         assertFalse("registry, hashcode()", emf.hashCode() == other.hashCode());
301 
302         // Different Locale
303         other = new ExtendedMessageFormat(pattern, Locale.FRANCE, registry);
304         assertFalse("locale, equals()",  emf.equals(other));
305         assertTrue("locale, hashcode()", emf.hashCode() == other.hashCode()); // same hashcode
306     }
307 
308     /**
309      * Test a built in format for the specified Locales, plus <code>null</code> Locale.
310      * @param pattern MessageFormat pattern
311      * @param args MessageFormat arguments
312      * @param locales to test
313      */
314     private void checkBuiltInFormat(final String pattern, final Object[] args, final Locale[] locales) {
315         checkBuiltInFormat(pattern, null, args, locales);
316     }
317 
318     /**
319      * Test a built in format for the specified Locales, plus <code>null</code> Locale.
320      * @param pattern MessageFormat pattern
321      * @param registry FormatFactory registry to use
322      * @param args MessageFormat arguments
323      * @param locales to test
324      */
325     private void checkBuiltInFormat(final String pattern, final Map<String, ?> registry, final Object[] args, final Locale[] locales) {
326         checkBuiltInFormat(pattern, registry, args, (Locale) null);
327         for (final Locale locale : locales) {
328             checkBuiltInFormat(pattern, registry, args, locale);
329         }
330     }
331 
332     /**
333      * Create an ExtendedMessageFormat for the specified pattern and locale and check the
334      * formated output matches the expected result for the parameters.
335      * @param pattern string
336      * @param registry map
337      * @param args Object[]
338      * @param locale Locale
339      */
340     private void checkBuiltInFormat(final String pattern, final Map<String, ?> registry, final Object[] args, final Locale locale) {
341         final StringBuilder buffer = new StringBuilder();
342         buffer.append("Pattern=[");
343         buffer.append(pattern);
344         buffer.append("], locale=[");
345         buffer.append(locale);
346         buffer.append("]");
347         final MessageFormat mf = createMessageFormat(pattern, locale);
348         // System.out.println(buffer + ", result=[" + mf.format(args) +"]");
349         ExtendedMessageFormat emf = null;
350         if (locale == null) {
351             emf = new ExtendedMessageFormat(pattern);
352         } else {
353             emf = new ExtendedMessageFormat(pattern, locale);
354         }
355         assertEquals("format "    + buffer.toString(), mf.format(args), emf.format(args));
356         assertPatternsEqual("toPattern " + buffer.toString(), mf.toPattern(),  emf.toPattern());
357     }
358 
359     //can't trust what MessageFormat does with toPattern() pre 1.4:
360     private void assertPatternsEqual(final String message, final String expected, final String actual) {
361         if (SystemUtils.isJavaVersionAtLeast(JAVA_1_4)) {
362             assertEquals(message, expected, actual);
363         }
364     }
365 
366     /**
367      * Replace MessageFormat(String, Locale) constructor (not available until JDK 1.4).
368      * @param pattern string
369      * @param locale Locale
370      * @return MessageFormat
371      */
372     private MessageFormat createMessageFormat(final String pattern, final Locale locale) {
373         final MessageFormat result = new MessageFormat(pattern);
374         if (locale != null) {
375             result.setLocale(locale);
376             result.applyPattern(pattern);
377         }
378         return result;
379     }
380 
381     // ------------------------ Test Formats ------------------------
382 
383     /**
384      * {@link Format} implementation which converts to lower case.
385      */
386     private static class LowerCaseFormat extends Format {
387         @Override
388         public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) {
389             return toAppendTo.append(((String)obj).toLowerCase());
390         }
391         @Override
392         public Object parseObject(final String source, final ParsePosition pos) {throw new UnsupportedOperationException();}
393     }
394 
395     /**
396      * {@link Format} implementation which converts to upper case.
397      */
398     private static class UpperCaseFormat extends Format {
399         @Override
400         public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) {
401             return toAppendTo.append(((String)obj).toUpperCase());
402         }
403         @Override
404         public Object parseObject(final String source, final ParsePosition pos) {throw new UnsupportedOperationException();}
405     }
406 
407 
408     // ------------------------ Test Format Factories ---------------
409     /**
410      * {@link FormatFactory} implementation for lower case format.
411      */
412     private static class LowerCaseFormatFactory implements FormatFactory {
413         private static final Format LOWER_INSTANCE = new LowerCaseFormat();
414         @Override
415         public Format getFormat(final String name, final String arguments, final Locale locale) {
416             return LOWER_INSTANCE;
417         }
418     }
419     /**
420      * {@link FormatFactory} implementation for upper case format.
421      */
422     private static class UpperCaseFormatFactory implements FormatFactory {
423         private static final Format UPPER_INSTANCE = new UpperCaseFormat();
424         @Override
425         public Format getFormat(final String name, final String arguments, final Locale locale) {
426             return UPPER_INSTANCE;
427         }
428     }
429     /**
430      * {@link FormatFactory} implementation to override date format "short" to "default".
431      */
432     private static class OverrideShortDateFormatFactory implements FormatFactory {
433         @Override
434         public Format getFormat(final String name, final String arguments, final Locale locale) {
435             return !"short".equals(arguments) ? null
436                     : locale == null ? DateFormat
437                             .getDateInstance(DateFormat.DEFAULT) : DateFormat
438                             .getDateInstance(DateFormat.DEFAULT, locale);
439         }
440     }
441 
442     /**
443      * Alternative ExtendedMessageFormat impl.
444      */
445     private static class OtherExtendedMessageFormat extends ExtendedMessageFormat {
446         public OtherExtendedMessageFormat(final String pattern, final Locale locale,
447                 final Map<String, ? extends FormatFactory> registry) {
448             super(pattern, locale, registry);
449         }
450         
451     }
452 
453 }