| // © 2016 and later: Unicode, Inc. and others. |
| // License & terms of use: http://www.unicode.org/copyright.html |
| /* |
| ******************************************************************************* |
| * Copyright (C) 2007-2016, International Business Machines Corporation and |
| * others. All Rights Reserved. |
| ******************************************************************************* |
| */ |
| |
| package com.ibm.icu.dev.test.format; |
| |
| import java.text.FieldPosition; |
| import java.text.ParsePosition; |
| import java.util.Collection; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedHashSet; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| import com.ibm.icu.dev.test.TestFmwk; |
| import com.ibm.icu.text.DecimalFormat; |
| import com.ibm.icu.text.DecimalFormatSymbols; |
| import com.ibm.icu.text.MessageFormat; |
| import com.ibm.icu.text.NumberFormat; |
| import com.ibm.icu.text.PluralFormat; |
| import com.ibm.icu.text.PluralRules; |
| import com.ibm.icu.text.PluralRules.PluralType; |
| import com.ibm.icu.text.PluralRules.SampleType; |
| import com.ibm.icu.util.ULocale; |
| |
| /** |
| * @author tschumann (Tim Schumann) |
| * |
| */ |
| @RunWith(JUnit4.class) |
| public class PluralFormatUnitTest extends TestFmwk { |
| @Test |
| public void TestConstructor() { |
| // Test correct formatting of numbers. |
| PluralFormat plFmts[] = new PluralFormat[10]; |
| plFmts[0] = new PluralFormat(); |
| plFmts[0].applyPattern("other{#}"); |
| plFmts[1] = new PluralFormat(PluralRules.DEFAULT); |
| plFmts[1].applyPattern("other{#}"); |
| plFmts[2] = new PluralFormat(PluralRules.DEFAULT, "other{#}"); |
| plFmts[3] = new PluralFormat("other{#}"); |
| plFmts[4] = new PluralFormat(ULocale.getDefault()); |
| plFmts[4].applyPattern("other{#}"); |
| plFmts[5] = new PluralFormat(ULocale.getDefault(), PluralRules.DEFAULT); |
| plFmts[5].applyPattern("other{#}"); |
| plFmts[6] = new PluralFormat(ULocale.getDefault(), |
| PluralRules.DEFAULT, |
| "other{#}"); |
| plFmts[7] = new PluralFormat(ULocale.getDefault(), "other{#}"); |
| |
| // Constructors with Java Locale |
| plFmts[8] = new PluralFormat(Locale.getDefault()); |
| plFmts[8].applyPattern("other{#}"); |
| plFmts[9] = new PluralFormat(Locale.getDefault(), PluralRules.DEFAULT); |
| plFmts[9].applyPattern("other{#}"); |
| |
| // These plural formats should produce the same output as a |
| // NumberFormat for the default locale. |
| NumberFormat numberFmt = NumberFormat.getInstance(ULocale.getDefault()); |
| for (int n = 1; n < 13; n++) { |
| String result = numberFmt.format(n); |
| for (int k = 0; k < plFmts.length; ++k) { |
| TestFmwk.assertEquals("PluralFormat's output is not as expected", |
| result, plFmts[k].format(n)); |
| } |
| } |
| // Test some bigger numbers. |
| // Coverage: Use the format(Object, ...) version. |
| StringBuffer sb = new StringBuffer(); |
| FieldPosition ignore = new FieldPosition(-1); |
| for (int n = 100; n < 113; n++) { |
| String result = numberFmt.format(n*n); |
| for (int k = 0; k < plFmts.length; ++k) { |
| sb.delete(0, sb.length()); |
| String pfResult = plFmts[k].format(Long.valueOf(n*n), sb, ignore).toString(); |
| TestFmwk.assertEquals("PluralFormat's output is not as expected", result, pfResult); |
| } |
| } |
| } |
| |
| @Test |
| public void TestEquals() { |
| // There is neither clone() nor a copy constructor. |
| PluralFormat de_fee_1 = new PluralFormat(ULocale.GERMAN, PluralType.CARDINAL, "other{fee}"); |
| PluralFormat de_fee_2 = new PluralFormat(ULocale.GERMAN, PluralType.CARDINAL, "other{fee}"); |
| PluralFormat de_fi = new PluralFormat(ULocale.GERMAN, PluralType.CARDINAL, "other{fi}"); |
| PluralFormat fr_fee = new PluralFormat(ULocale.FRENCH, PluralType.CARDINAL, "other{fee}"); |
| assertTrue("different de_fee objects", de_fee_1 != de_fee_2); |
| assertTrue("equal de_fee objects", de_fee_1.equals(de_fee_2)); |
| assertFalse("different pattern strings", de_fee_1.equals(de_fi)); |
| assertFalse("different locales", de_fee_1.equals(fr_fee)); |
| } |
| |
| @Test |
| public void TestApplyPatternAndFormat() { |
| // Create rules for testing. |
| PluralRules oddAndEven = PluralRules.createRules("odd: n mod 2 is 1"); |
| { |
| // Test full specified case for testing RuleSet |
| PluralFormat plfOddAndEven = new PluralFormat(oddAndEven); |
| plfOddAndEven.applyPattern("odd{# is odd.} other{# is even.}"); |
| |
| // Test fall back to other. |
| PluralFormat plfOddOrEven = new PluralFormat(oddAndEven); |
| plfOddOrEven.applyPattern("other{# is odd or even.}"); |
| |
| NumberFormat numberFormat = |
| NumberFormat.getInstance(ULocale.getDefault()); |
| for (int i = 0; i < 22; ++i) { |
| assertEquals("Fallback to other gave wrong results", |
| numberFormat.format(i) + " is odd or even.", |
| plfOddOrEven.format(i)); |
| assertEquals("Fully specified PluralFormat gave wrong results", |
| numberFormat.format(i) + ((i%2 == 1) ? " is odd." |
| : " is even."), |
| plfOddAndEven.format(i)); |
| } |
| |
| // ICU 4.8 does not check for duplicate keywords any more. |
| PluralFormat pf = new PluralFormat(ULocale.ENGLISH, oddAndEven, |
| "odd{foo} odd{bar} other{foobar}"); |
| assertEquals("should use first occurrence of the 'odd' keyword", "foo", pf.format(1)); |
| pf.applyPattern("odd{foo} other{bar} other{foobar}"); |
| assertEquals("should use first occurrence of the 'other' keyword", "bar", pf.format(2)); |
| // This sees the first "other" before calling the PluralSelector which then selects "other". |
| pf.applyPattern("other{foo} odd{bar} other{foobar}"); |
| assertEquals("should use first occurrence of the 'other' keyword", "foo", pf.format(2)); |
| } |
| // omit other keyword. |
| try { |
| PluralFormat plFmt = new PluralFormat(oddAndEven); |
| plFmt.applyPattern("odd{foo}"); |
| errln("Not defining plural case other should result in an " + |
| "exception but did not."); |
| }catch (IllegalArgumentException e){} |
| |
| // ICU 4.8 does not check for unknown keywords any more. |
| { |
| PluralFormat pf = new PluralFormat(ULocale.ENGLISH, oddAndEven, "otto{foo} other{bar}"); |
| assertEquals("should ignore unknown keywords", "bar", pf.format(1)); |
| } |
| |
| // Test invalid keyword. |
| try { |
| PluralFormat plFmt = new PluralFormat(oddAndEven); |
| plFmt.applyPattern("*odd{foo} other{bar}"); |
| errln("Defining a message for an invalid keyword should result in " + |
| "an exception but did not."); |
| }catch (IllegalArgumentException e){} |
| |
| // Test invalid syntax |
| // -- comma between keyword{message} clauses |
| // -- space in keywords |
| // -- keyword{message1}{message2} |
| try { |
| PluralFormat plFmt = new PluralFormat(oddAndEven); |
| plFmt.applyPattern("odd{foo},other{bar}"); |
| errln("Separating keyword{message} items with other characters " + |
| "than space should provoke an exception but did not."); |
| }catch (IllegalArgumentException e){} |
| try { |
| PluralFormat plFmt = new PluralFormat(oddAndEven); |
| plFmt.applyPattern("od d{foo} other{bar}"); |
| errln("Spaces inside keywords should provoke an exception but " + |
| "did not."); |
| }catch (IllegalArgumentException e){} |
| try { |
| PluralFormat plFmt = new PluralFormat(oddAndEven); |
| plFmt.applyPattern("odd{foo}{foobar}other{foo}"); |
| errln("Defining multiple messages after a keyword should provoke " + |
| "an exception but did not."); |
| }catch (IllegalArgumentException e){} |
| |
| // Check that nested format is preserved. |
| { |
| PluralFormat plFmt = new PluralFormat(oddAndEven); |
| plFmt.applyPattern("odd{The number {0, number, #.#0} is odd.}" + |
| "other{The number {0, number, #.#0} is even.}"); |
| for (int i = 1; i < 3; ++i) { |
| assertEquals("format did not preserve a nested format string.", |
| ((i % 2 == 1) ? |
| "The number {0, number, #.#0} is odd." |
| : "The number {0, number, #.#0} is even."), |
| plFmt.format(i)); |
| } |
| |
| } |
| // Check that a pound sign in curly braces is preserved. |
| { |
| PluralFormat plFmt = new PluralFormat(oddAndEven); |
| plFmt.applyPattern("odd{The number {1,number,#} is odd.}" + |
| "other{The number {2,number,#} is even.}"); |
| for (int i = 1; i < 3; ++i) { |
| assertEquals("format did not preserve # inside curly braces.", |
| ((i % 2 == 1) ? "The number {1,number,#} is odd." |
| : "The number {2,number,#} is even."), |
| plFmt.format(i)); |
| } |
| |
| } |
| } |
| |
| |
| @Test |
| public void TestSamples() { |
| Map<ULocale,Set<ULocale>> same = new LinkedHashMap(); |
| for (ULocale locale : PluralRules.getAvailableULocales()) { |
| ULocale otherLocale = PluralRules.getFunctionalEquivalent(locale, null); |
| Set<ULocale> others = same.get(otherLocale); |
| if (others == null) same.put(otherLocale, others = new LinkedHashSet()); |
| others.add(locale); |
| continue; |
| } |
| for (ULocale locale0 : same.keySet()) { |
| PluralRules rules = PluralRules.forLocale(locale0); |
| String localeName = locale0.toString().length() == 0 ? "root" : locale0.toString(); |
| logln(localeName + "\t=\t" + same.get(locale0)); |
| logln(localeName + "\ttoString\t" + rules.toString()); |
| Set<String> keywords = rules.getKeywords(); |
| for (String keyword : keywords) { |
| Collection<Double> list = rules.getSamples(keyword); |
| if (list.size() == 0) { |
| // if there aren't any integer samples, get the decimal ones. |
| list = rules.getSamples(keyword, SampleType.DECIMAL); |
| } |
| |
| if (list == null || list.size() == 0) { |
| errln("Empty list for " + localeName + " : " + keyword); |
| } else { |
| logln("\t" + localeName + " : " + keyword + " ; " + list); |
| } |
| } |
| } |
| } |
| |
| @Test |
| public void TestSetLocale() { |
| // Create rules for testing. |
| PluralRules oddAndEven = PluralRules.createRules("odd__: n mod 2 is 1"); |
| |
| PluralFormat plFmt = new PluralFormat(oddAndEven); |
| plFmt.applyPattern("odd__{odd} other{even}"); |
| plFmt.setLocale(ULocale.ENGLISH); |
| |
| // Check that pattern gets deleted. |
| NumberFormat nrFmt = NumberFormat.getInstance(ULocale.ENGLISH); |
| assertEquals("pattern was not resetted by setLocale() call.", |
| nrFmt.format(5), |
| plFmt.format(5)); |
| |
| // Check that rules got updated. |
| plFmt.applyPattern("odd__{odd} other{even}"); |
| assertEquals("SetLocale should reset rules but did not.", "even", plFmt.format(1)); |
| |
| plFmt.applyPattern("one{one} other{not one}"); |
| for (int i = 0; i < 20; ++i) { |
| assertEquals("Wrong ruleset loaded by setLocale()", |
| ((i==1) ? "one" : "not one"), |
| plFmt.format(i)); |
| } |
| } |
| |
| @Test |
| public void TestParse() { |
| PluralFormat plFmt = new PluralFormat("other{test}"); |
| try { |
| plFmt.parse("test", new ParsePosition(0)); |
| errln("parse() should throw an UnsupportedOperationException but " + |
| "did not"); |
| } catch (UnsupportedOperationException e) { |
| } |
| |
| plFmt = new PluralFormat("other{test}"); |
| try { |
| plFmt.parseObject("test", new ParsePosition(0)); |
| errln("parse() should throw an UnsupportedOperationException but " + |
| "did not"); |
| } catch (UnsupportedOperationException e) { |
| } |
| } |
| |
| @Test |
| public void TestPattern() { |
| Object[] args = { "acme", null }; |
| |
| { |
| // ICU 4.8 PluralFormat does not trim() its pattern any more. |
| // None of the other *Format classes do. |
| String pat = " one {one ''widget} other {# widgets} "; |
| PluralFormat pf = new PluralFormat(pat); |
| assertEquals("should not trim() the pattern", pat, pf.toPattern()); |
| } |
| |
| MessageFormat pfmt = new MessageFormat("The disk ''{0}'' contains {1, plural, one {one ''''{1, number, #.0}'''' widget} other {# widgets}}."); |
| logln(""); |
| for (int i = 0; i < 3; ++i) { |
| args[1] = new Integer(i); |
| logln(pfmt.format(args)); |
| } |
| /* ICU 4.8 returns null instead of a choice/plural/select Format object |
| * (because it does not create an object for any "complex" argument). |
| PluralFormat pf = (PluralFormat)pfmt.getFormatsByArgumentIndex()[1]; |
| logln(pf.toPattern()); |
| */ |
| logln(pfmt.toPattern()); |
| MessageFormat pfmt2 = new MessageFormat(pfmt.toPattern()); |
| assertEquals("message formats are equal", pfmt, pfmt2); |
| } |
| |
| @Test |
| public void TestExtendedPluralFormat() { |
| String[] targets = { |
| "There are no widgets.", |
| "There is one widget.", |
| "There is a bling widget and one other widget.", |
| "There is a bling widget and 2 other widgets.", |
| "There is a bling widget and 3 other widgets.", |
| "Widgets, five (5-1=4) there be.", |
| "There is a bling widget and 5 other widgets.", |
| "There is a bling widget and 6 other widgets.", |
| }; |
| String pluralStyle = |
| "offset:1.0 " |
| + "=0 {There are no widgets.} " |
| + "=1.0 {There is one widget.} " |
| + "=5 {Widgets, five (5-1=#) there be.} " |
| + "one {There is a bling widget and one other widget.} " |
| + "other {There is a bling widget and # other widgets.}"; |
| PluralFormat pf = new PluralFormat(ULocale.ENGLISH, pluralStyle); |
| MessageFormat mf = new MessageFormat("{0,plural," + pluralStyle + "}", ULocale.ENGLISH); |
| Integer args[] = new Integer[1]; |
| for (int i = 0; i <= 7; ++i) { |
| String result = pf.format(i); |
| assertEquals("PluralFormat.format(value " + i + ")", targets[i], result); |
| args[0] = i; |
| result = mf.format(args); |
| assertEquals("MessageFormat.format(value " + i + ")", targets[i], result); |
| } |
| |
| // Try explicit values after keywords. |
| pf.applyPattern("other{zz}other{yy}one{xx}one{ww}=1{vv}=1{uu}"); |
| assertEquals("should find first matching *explicit* value", "vv", pf.format(1)); |
| } |
| |
| @Test |
| public void TestExtendedPluralFormatParsing() { |
| String[] failures = { |
| "offset:1..0 =0 {Foo}", |
| "offset:1.0 {Foo}", |
| "=0= {Foo}", |
| "=0 {Foo} =0.0 {Bar}", |
| " = {Foo}", |
| }; |
| for (String fmt : failures) { |
| try { |
| new PluralFormat(fmt); |
| fail("expected exception when parsing '" + fmt + "'"); |
| } catch (IllegalArgumentException e) { |
| // ok |
| } |
| } |
| } |
| |
| @Test |
| public void TestOrdinalFormat() { |
| String pattern = "one{#st file}two{#nd file}few{#rd file}other{#th file}"; |
| PluralFormat pf = new PluralFormat(ULocale.ENGLISH, PluralType.ORDINAL, pattern); |
| assertEquals("PluralFormat.format(321)", "321st file", pf.format(321)); |
| assertEquals("PluralFormat.format(22)", "22nd file", pf.format(22)); |
| assertEquals("PluralFormat.format(3)", "3rd file", pf.format(3)); |
| |
| // Code coverage: Use the other new-for-PluralType constructor as well. |
| pf = new PluralFormat(ULocale.ENGLISH, PluralType.ORDINAL); |
| pf.applyPattern(pattern); |
| assertEquals("PluralFormat.format(456)", "456th file", pf.format(456)); |
| assertEquals("PluralFormat.format(111)", "111th file", pf.format(111)); |
| |
| // Code coverage: Use Locale not ULocale. |
| pf = new PluralFormat(Locale.ENGLISH, PluralType.ORDINAL); |
| pf.applyPattern(pattern); |
| assertEquals("PluralFormat.format(456)", "456th file", pf.format(456)); |
| assertEquals("PluralFormat.format(111)", "111th file", pf.format(111)); |
| } |
| |
| @Test |
| public void TestDecimals() { |
| // Simple number replacement. |
| PluralFormat pf = new PluralFormat(ULocale.ENGLISH, "one{one meter}other{# meters}"); |
| assertEquals("simple format(1)", "one meter", pf.format(1)); |
| assertEquals("simple format(1.5)", "1.5 meters", pf.format(1.5)); |
| PluralFormat pf2 = new PluralFormat(ULocale.ENGLISH, |
| "offset:1 one{another meter}other{another # meters}"); |
| pf2.setNumberFormat(new DecimalFormat("0.0", new DecimalFormatSymbols(ULocale.ENGLISH))); |
| assertEquals("offset-decimals format(1)", "another 0.0 meters", pf2.format(1)); |
| assertEquals("offset-decimals format(2)", "another 1.0 meters", pf2.format(2)); |
| assertEquals("offset-decimals format(2.5)", "another 1.5 meters", pf2.format(2.5)); |
| } |
| |
| @Test |
| public void TestNegative() { |
| PluralFormat pluralFormat = new PluralFormat(ULocale.ENGLISH, "one{# foot}other{# feet}"); |
| String actual = pluralFormat.format(-3); |
| assertEquals(pluralFormat.toString(), "-3 feet", actual); |
| } |
| } |