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