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 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   * Test case for {@link ExtendedMessageFormat}.
42   */
43  public class ExtendedMessageFormatTest {
44  
45      /**
46       * {@link Format} implementation which converts to lower case.
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       * Alternative ExtendedMessageFormat impl.
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       * {@link FormatFactory} implementation to override date format "short" to "default".
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       * {@link Format} implementation which converts to upper case.
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      * Create an ExtendedMessageFormat for the specified pattern and locale and check the formatted output matches the expected result for the parameters.
106      *
107      * @param pattern        string
108      * @param registryUnused map (currently unused)
109      * @param args           Object[]
110      * @param locale         Locale
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         // System.out.println(buffer + ", result=[" + mf.format(args) +"]");
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 //     * Test extended formats with choice format.
133 //     *
134 //     * NOTE: FAILING - currently sub-formats not supported
135 //     */
136 //    public void testExtendedWithChoiceFormat() {
137 //        String pattern = "Choice: {0,choice,1.0#{1,lower}|2.0#{1,upper}}";
138 //        ExtendedMessageFormat emf = new ExtendedMessageFormat(pattern, registry);
139 //        assertPatterns(null, pattern, emf.toPattern());
140 //        try {
141 //            assertEquals("one", emf.format(new Object[] {Integer.valueOf(1), "ONE"}));
142 //            assertEquals("TWO", emf.format(new Object[] {Integer.valueOf(2), "two"}));
143 //        } catch (IllegalArgumentException e) {
144 //            // currently sub-formats not supported
145 //        }
146 //    }
147 
148 //    /**
149 //     * Test mixed extended and built-in formats with choice format.
150 //     *
151 //     * NOTE: FAILING - currently sub-formats not supported
152 //     */
153 //    public void testExtendedAndBuiltInWithChoiceFormat() {
154 //        String pattern = "Choice: {0,choice,1.0#{0} {1,lower} {2,number}|2.0#{0} {1,upper} {2,number,currency}}";
155 //        Object[] lowArgs  = new Object[] {Integer.valueOf(1), "Low",  Double.valueOf("1234.56")};
156 //        Object[] highArgs = new Object[] {Integer.valueOf(2), "High", Double.valueOf("9876.54")};
157 //        Locale[] availableLocales = ChoiceFormat.getAvailableLocales();
158 //        Locale[] testLocales = new Locale[availableLocales.length + 1];
159 //        testLocales[0] = null;
160 //        System.arraycopy(availableLocales, 0, testLocales, 1, availableLocales.length);
161 //        for (int i = 0; i < testLocales.length; i++) {
162 //            NumberFormat nf = null;
163 //            NumberFormat cf = null;
164 //            ExtendedMessageFormat emf = null;
165 //            if (testLocales[i] == null) {
166 //                nf = NumberFormat.getNumberInstance();
167 //                cf = NumberFormat.getCurrencyInstance();
168 //                emf = new ExtendedMessageFormat(pattern, registry);
169 //            } else {
170 //                nf = NumberFormat.getNumberInstance(testLocales[i]);
171 //                cf = NumberFormat.getCurrencyInstance(testLocales[i]);
172 //                emf = new ExtendedMessageFormat(pattern, testLocales[i], registry);
173 //            }
174 //            assertPatterns(null, pattern, emf.toPattern());
175 //            try {
176 //                String lowExpected = lowArgs[0] + " low "    + nf.format(lowArgs[2]);
177 //                String highExpected = highArgs[0] + " HIGH "  + cf.format(highArgs[2]);
178 //                assertEquals(lowExpected,  emf.format(lowArgs));
179 //                assertEquals(highExpected, emf.format(highArgs));
180 //            } catch (IllegalArgumentException e) {
181 //                // currently sub-formats not supported
182 //            }
183 //        }
184 //    }
185 
186     /**
187      * Test a built in format for the specified Locales, plus {@code null} Locale.
188      *
189      * @param pattern     MessageFormat pattern
190      * @param fmtRegistry FormatFactory registry to use
191      * @param args        MessageFormat arguments
192      * @param locales     to test
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      * Test a built in format for the specified Locales, plus {@code null} Locale.
203      *
204      * @param pattern MessageFormat pattern
205      * @param args    MessageFormat arguments
206      * @param locales to test
207      */
208     private void checkBuiltInFormat(final String pattern, final Object[] args, final Locale[] locales) {
209         checkBuiltInFormat(pattern, null, args, locales);
210     }
211 
212     /**
213      * Replace MessageFormat(String, Locale) constructor (not available until JDK 1.4).
214      *
215      * @param pattern string
216      * @param locale  Locale
217      * @return MessageFormat
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      * Test the built in choice format.
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      * Test the built in date/time formats
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      * Test the built in number formats.
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      * Test Bug TEXT-106 - Exception while using ExtendedMessageFormat and choice format element with quote just before brace end
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      * Test Bug LANG-917 - IndexOutOfBoundsException and/or infinite loop when using a choice pattern
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      * Test equals() and hashCode().
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         // Same object
337         assertEquals(emf, emf, "same, equals()");
338         assertEquals(emf.hashCode(), emf.hashCode(), "same, hashCode()");
339 
340         assertNotEquals(null, emf, "null, equals");
341 
342         // Equal Object
343         other = new ExtendedMessageFormat(pattern, Locale.US, fmtRegistry);
344         assertEquals(emf, other, "equal, equals()");
345         assertEquals(emf.hashCode(), other.hashCode(), "equal, hashCode()");
346 
347         // Different Class
348         other = new OtherExtendedMessageFormat(pattern, Locale.US, fmtRegistry);
349         assertNotEquals(emf, other, "class, equals()");
350         assertEquals(emf.hashCode(), other.hashCode(), "class, hashCode()"); // same hash code
351 
352         // Different pattern
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         // Different registry
358         other = new ExtendedMessageFormat(pattern, Locale.US, otherRegistry);
359         assertNotEquals(emf, other, "registry, equals()");
360         assertNotEquals(emf.hashCode(), other.hashCode(), "registry, hashCode()");
361 
362         // Different Locale
363         other = new ExtendedMessageFormat(pattern, Locale.FRANCE, fmtRegistry);
364         assertNotEquals(emf, other, "locale, equals()");
365         assertEquals(emf.hashCode(), other.hashCode(), "locale, hashCode()"); // same hash code
366     }
367 
368     /**
369      * Test Bug LANG-948 - Exception while using ExtendedMessageFormat and escaping braces
370      */
371     @Test
372     public void testEscapedBraces_LANG_948() {
373         // message without placeholder because braces are escaped by quotes
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         // message with placeholder because quotes are escaped by quotes
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      * Test Bug LANG-477 - out of memory error with escaped quote
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      * Test extended and built in formats.
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      * Test extended formats.
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         // check the non-overridden builtins:
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         // check the overridden format:
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 }