17935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert/*
27935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *******************************************************************************
37935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * Copyright (C) 2007-2014, International Business Machines Corporation and
47935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * others. All Rights Reserved.
57935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert *******************************************************************************
67935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert */
77935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertpackage com.ibm.icu.dev.test.format;
87935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
97935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.io.ByteArrayInputStream;
107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.io.ByteArrayOutputStream;
117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.io.IOException;
127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.io.ObjectInputStream;
137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.io.ObjectOutputStream;
147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.io.Serializable;
157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.text.ParseException;
167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.ArrayList;
177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.Arrays;
187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.Collection;
197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.Collections;
207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.Comparator;
217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.EnumSet;
227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.HashMap;
237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.HashSet;
247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.LinkedHashSet;
257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.List;
267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.Locale;
277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.Map;
287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.Map.Entry;
297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.Set;
307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.TreeMap;
317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport java.util.TreeSet;
327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.dev.test.TestFmwk;
347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.dev.test.serializable.SerializableTest;
357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.dev.util.CollectionUtilities;
367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.dev.util.Relation;
377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.impl.Utility;
387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.text.NumberFormat;
397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.text.PluralRules;
407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.text.PluralRules.FixedDecimal;
417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.text.PluralRules.FixedDecimalRange;
427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.text.PluralRules.FixedDecimalSamples;
437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.text.PluralRules.KeywordStatus;
447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.text.PluralRules.PluralType;
457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.text.PluralRules.SampleType;
467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.text.UFieldPosition;
477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.util.Output;
487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertimport com.ibm.icu.util.ULocale;
497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert/**
517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * @author dougfelt (Doug Felt)
527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert * @author markdavis (Mark Davis) [for fractional support]
537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert */
547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubertpublic class PluralRulesTest extends TestFmwk {
557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    PluralRulesFactory factory = PluralRulesFactory.NORMAL;
577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static void main(String[] args) throws Exception {
597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        new PluralRulesTest().run(args);
607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void testOverUnderflow() {
637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        logln(String.valueOf(Long.MAX_VALUE + 1d));
647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (double[] testDouble : new double[][] {
657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { 1E18, 0, 0, 1E18 }, // check overflow
667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { 10000000000000.1d, 1, 1, 10000000000000d }, { -0.00001d, 1, 5, 0 }, { 1d, 0, 0, 1 },
677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { 1.1d, 1, 1, 1 }, { 12345d, 0, 0, 12345 }, { 12345.678912d, 678912, 6, 12345 },
687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { 12345.6789123d, 678912, 6, 12345 }, // we only go out 6 digits
697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { 1E18, 0, 0, 1E18 }, // check overflow
707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { 1E19, 0, 0, 1E18 }, // check overflow
717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }) {
727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            FixedDecimal fd = new FixedDecimal(testDouble[0]);
737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            assertEquals(testDouble[0] + "=doubleValue()", testDouble[0], fd.doubleValue());
747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            assertEquals(testDouble[0] + " decimalDigits", (int) testDouble[1], fd.decimalDigits);
757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            assertEquals(testDouble[0] + " visibleDecimalDigitCount", (int) testDouble[2], fd.visibleDecimalDigitCount);
767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            assertEquals(testDouble[0] + " decimalDigitsWithoutTrailingZeros", (int) testDouble[1],
777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    fd.decimalDigitsWithoutTrailingZeros);
787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            assertEquals(testDouble[0] + " visibleDecimalDigitCountWithoutTrailingZeros", (int) testDouble[2],
797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    fd.visibleDecimalDigitCountWithoutTrailingZeros);
807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            assertEquals(testDouble[0] + " integerValue", (long) testDouble[3], fd.integerValue);
817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (ULocale locale : new ULocale[] { ULocale.ENGLISH, new ULocale("cy"), new ULocale("ar") }) {
847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            PluralRules rules = factory.forLocale(locale);
857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            assertEquals(locale + " NaN", "other", rules.select(Double.NaN));
877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            assertEquals(locale + " ∞", "other", rules.select(Double.POSITIVE_INFINITY));
887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            assertEquals(locale + " -∞", "other", rules.select(Double.NEGATIVE_INFINITY));
897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void testSyntaxRestrictions() {
937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Object[][] shouldFail = {
947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a:n in 3..10,13..19" },
957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // = and != always work
977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a:n=1" },
987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a:n=1,3" },
997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a:n!=1" },
1007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a:n!=1,3" },
1017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // with spacing
1037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a: n = 1" },
1047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a: n = 1, 3" },
1057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a: n != 1" },
1067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a: n != 1, 3" },
1077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a: n ! = 1" },
1087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a: n ! = 1, 3" },
1097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a: n = 1 , 3" },
1107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a: n != 1 , 3" },
1117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a: n ! = 1 , 3" },
1127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a: n = 1 .. 3" },
1137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a: n != 1 .. 3" },
1147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a: n ! = 1 .. 3" },
1157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // more complicated
1177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a:n in 3 .. 10 , 13 .. 19" },
1187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // singles have special exceptions
1207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a: n is 1" },
1217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a: n is not 1" },
1227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a: n not is 1", ParseException.class }, // hacked to fail
1237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a: n in 1" },
1247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a: n not in 1" },
1257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // multiples also have special exceptions
1277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // TODO enable the following once there is an update to CLDR
1287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // {"a: n is 1,3", ParseException.class},
1297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a: n is not 1,3", ParseException.class }, // hacked to fail
1307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a: n not is 1,3", ParseException.class }, // hacked to fail
1317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a: n in 1,3" },
1327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a: n not in 1,3" },
1337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // disallow not with =
1357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a: n not= 1", ParseException.class }, // hacked to fail
1367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a: n not= 1,3", ParseException.class }, // hacked to fail
1377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // disallow double negatives
1397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a: n ! is not 1", ParseException.class },
1407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a: n ! is not 1", ParseException.class },
1417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a: n not not in 1", ParseException.class },
1427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a: n is not not 1", NumberFormatException.class },
1437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // disallow screwy cases
1457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { null, NullPointerException.class }, { "djkl;", ParseException.class },
1467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a: n = 1 .", ParseException.class }, { "a: n = 1 ..", ParseException.class },
1477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a: n = 1 2", ParseException.class }, { "a: n = 1 ,", ParseException.class },
1487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { "a:n in 3 .. 10 , 13 .. 19 ,", ParseException.class }, };
1497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (Object[] shouldFailTest : shouldFail) {
1507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String rules = (String) shouldFailTest[0];
1517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Class exception = shouldFailTest.length < 2 ? null : (Class) shouldFailTest[1];
1527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Class actualException = null;
1537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            try {
1547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                PluralRules.parseDescription(rules);
1557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } catch (Exception e) {
1567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                actualException = e.getClass();
1577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
1587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            assertEquals("Exception " + rules, exception, actualException);
1597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void testSamples() {
1637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String description = "one: n is 3 or f is 5 @integer  3,19, @decimal 3.50 ~ 3.53,   …; other:  @decimal 99.0~99.2, 999.0, …";
1647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        PluralRules test = PluralRules.createRules(description);
1657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        checkNewSamples(description, test, "one", PluralRules.SampleType.INTEGER, "@integer 3, 19", true,
1677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                new FixedDecimal(3));
1687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        checkNewSamples(description, test, "one", PluralRules.SampleType.DECIMAL, "@decimal 3.50~3.53, …", false,
1697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                new FixedDecimal(3.5, 2));
1707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        checkOldSamples(description, test, "one", SampleType.INTEGER, 3d, 19d);
1717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        checkOldSamples(description, test, "one", SampleType.DECIMAL, 3.5d, 3.51d, 3.52d, 3.53d);
1727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        checkNewSamples(description, test, "other", PluralRules.SampleType.INTEGER, "", true, null);
1747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        checkNewSamples(description, test, "other", PluralRules.SampleType.DECIMAL, "@decimal 99.0~99.2, 999.0, …",
1757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                false, new FixedDecimal(99d, 1));
1767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        checkOldSamples(description, test, "other", SampleType.INTEGER);
1777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        checkOldSamples(description, test, "other", SampleType.DECIMAL, 99d, 99.1, 99.2d, 999d);
1787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void checkOldSamples(String description, PluralRules rules, String keyword, SampleType sampleType,
1817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Double... expected) {
1827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Collection<Double> oldSamples = rules.getSamples(keyword, sampleType);
1837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (!assertEquals("getOldSamples; " + keyword + "; " + description, new HashSet(Arrays.asList(expected)),
1847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                oldSamples)) {
1857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            rules.getSamples(keyword, sampleType);
1867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
1887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
1897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void checkNewSamples(String description, PluralRules test, String keyword, SampleType sampleType,
1907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String samplesString, boolean isBounded, FixedDecimal firstInRange) {
1917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String title = description + ", " + sampleType;
1927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        FixedDecimalSamples samples = test.getDecimalSamples(keyword, sampleType);
1937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (samples != null) {
1947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            assertEquals("samples; " + title, samplesString, samples.toString());
1957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            assertEquals("bounded; " + title, isBounded, samples.bounded);
1967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            assertEquals("first; " + title, firstInRange, samples.samples.iterator().next().start);
1977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
1987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertEquals("limited: " + title, isBounded, test.isLimited(keyword, sampleType));
1997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
2007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final String[] parseTestData = { "a: n is 1", "a:1", "a: n mod 10 is 2", "a:2,12,22",
2027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "a: n is not 1", "a:0,2,3,4,5", "a: n mod 3 is not 1", "a:0,2,3,5,6,8,9", "a: n in 2..5", "a:2,3,4,5",
2037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "a: n within 2..5", "a:2,3,4,5", "a: n not in 2..5", "a:0,1,6,7,8", "a: n not within 2..5", "a:0,1,6,7,8",
2047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "a: n mod 10 in 2..5", "a:2,3,4,5,12,13,14,15,22,23,24,25", "a: n mod 10 within 2..5",
2057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "a:2,3,4,5,12,13,14,15,22,23,24,25", "a: n mod 10 is 2 and n is not 12", "a:2,22,32,42",
2067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "a: n mod 10 in 2..3 or n mod 10 is 5", "a:2,3,5,12,13,15,22,23,25",
2077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "a: n mod 10 within 2..3 or n mod 10 is 5", "a:2,3,5,12,13,15,22,23,25", "a: n is 1 or n is 4 or n is 23",
2087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "a:1,4,23", "a: n mod 2 is 1 and n is not 3 and n in 1..11", "a:1,5,7,9,11",
2097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "a: n mod 2 is 1 and n is not 3 and n within 1..11", "a:1,5,7,9,11",
2107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "a: n mod 2 is 1 or n mod 5 is 1 and n is not 6", "a:1,3,5,7,9,11,13,15,16",
2117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "a: n in 2..5; b: n in 5..8; c: n mod 2 is 1", "a:2,3,4,5;b:6,7,8;c:1,9,11",
2127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "a: n within 2..5; b: n within 5..8; c: n mod 2 is 1", "a:2,3,4,5;b:6,7,8;c:1,9,11",
2137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "a: n in 2,4..6; b: n within 7..9,11..12,20", "a:2,4,5,6;b:7,8,9,11,12,20",
2147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "a: n in 2..8,12 and n not in 4..6", "a:2,3,7,8,12", "a: n mod 10 in 2,3,5..7 and n is not 12",
2157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "a:2,3,5,6,7,13,15,16,17", "a: n in 2..6,3..7", "a:2,3,4,5,6,7", };
2167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private String[] getTargetStrings(String targets) {
2187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        List list = new ArrayList(50);
2197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String[] valSets = Utility.split(targets, ';');
2207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int i = 0; i < valSets.length; ++i) {
2217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String[] temp = Utility.split(valSets[i], ':');
2227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String key = temp[0].trim();
2237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String[] vals = Utility.split(temp[1], ',');
2247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (int j = 0; j < vals.length; ++j) {
2257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                String valString = vals[j].trim();
2267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                int val = Integer.parseInt(valString);
2277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                while (list.size() <= val) {
2287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    list.add(null);
2297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
2307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (list.get(val) != null) {
2317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    fail("test data error, key: " + list.get(val) + " already set for: " + val);
2327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
2337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                list.set(val, key);
2347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
2357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String[] result = (String[]) list.toArray(new String[list.size()]);
2387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int i = 0; i < result.length; ++i) {
2397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (result[i] == null) {
2407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                result[i] = "other";
2417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
2427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return result;
2447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
2457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private void checkTargets(PluralRules rules, String[] targets) {
2477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int i = 0; i < targets.length; ++i) {
2487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            assertEquals("value " + i, targets[i], rules.select(i));
2497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
2517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void testParseEmpty() throws ParseException {
2537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        PluralRules rules = PluralRules.parseDescription("a:n");
2547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertEquals("empty", "a", rules.select(0));
2557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
2567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void testParsing() {
2587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int i = 0; i < parseTestData.length; i += 2) {
2597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String pattern = parseTestData[i];
2607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String expected = parseTestData[i + 1];
2617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            logln("pattern[" + i + "] " + pattern);
2637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            try {
2647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                PluralRules rules = PluralRules.createRules(pattern);
2657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                String[] targets = getTargetStrings(expected);
2667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                checkTargets(rules, targets);
2677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } catch (Exception e) {
2687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                e.printStackTrace();
2697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                throw new RuntimeException(e.getMessage());
2707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
2717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
2727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
2737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static String[][] operandTestData = { { "a: n 3", "FAIL" },
2757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            { "a: n=1,2; b: n != 3..5; c:n!=5", "a:1,2; b:6,7; c:3,4" },
2767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            { "a: n=1,2; b: n!=3..5; c:n!=5", "a:1,2; b:6,7; c:3,4" },
2777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            { "a: t is 1", "a:1.1,1.1000,99.100; other:1.2,1.0" }, { "a: f is 1", "a:1.1; other:1.1000,99.100" },
2787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            { "a: i is 2; b:i is 3", "b: 3.5; a: 2.5" }, { "a: f is 0; b:f is 50", "a: 1.00; b: 1.50" },
2797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            { "a: v is 1; b:v is 2", "a: 1.0; b: 1.00" }, { "one: n is 1 AND v is 0", "one: 1 ; other: 1.00,1.0" }, // English
2807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                                                                                                                    // rules
2817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            { "one: v is 0 and i mod 10 is 1 or f mod 10 is 1", "one: 1, 1.1, 3.1; other: 1.0, 3.2, 5" }, // Last
2827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                                                                                                          // visible
2837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                                                                                                          // digit
2847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            { "one: j is 0", "one: 0; other: 0.0, 1.0, 3" }, // Last visible digit
2857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // one → n is 1; few → n in 2..4;
2867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    };
2877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void testOperands() {
2897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (String[] pair : operandTestData) {
2907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String pattern = pair[0].trim();
2917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String categoriesAndExpected = pair[1].trim();
2927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
2937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // logln("pattern[" + i + "] " + pattern);
2947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            boolean FAIL_EXPECTED = categoriesAndExpected.equalsIgnoreCase("fail");
2957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            try {
2967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                logln(pattern);
2977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                PluralRules rules = PluralRules.createRules(pattern);
2987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (FAIL_EXPECTED) {
2997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    assertNull("Should fail with 'null' return.", rules);
3007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else {
3017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    logln(rules == null ? "null rules" : rules.toString());
3027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    checkCategoriesAndExpected(pattern, categoriesAndExpected, rules);
3037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
3047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } catch (Exception e) {
3057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (!FAIL_EXPECTED) {
3067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    e.printStackTrace();
3077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    throw new RuntimeException(e.getMessage());
3087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
3097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
3107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
3127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void testUniqueRules() {
3147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        main: for (ULocale locale : factory.getAvailableULocales()) {
3157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            PluralRules rules = factory.forLocale(locale);
3167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Map<String, PluralRules> keywordToRule = new HashMap<String, PluralRules>();
3177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Collection<FixedDecimalSamples> samples = new LinkedHashSet<FixedDecimalSamples>();
3187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (String keyword : rules.getKeywords()) {
3207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                for (SampleType sampleType : SampleType.values()) {
3217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    FixedDecimalSamples samples2 = rules.getDecimalSamples(keyword, sampleType);
3227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if (samples2 != null) {
3237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        samples.add(samples2);
3247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
3257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
3267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (keyword.equals("other")) {
3277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    continue;
3287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
3297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                String rules2 = keyword + ":" + rules.getRules(keyword);
3307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                PluralRules singleRule = PluralRules.createRules(rules2);
3317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (singleRule == null) {
3327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    errln("Can't generate single rule for " + rules2);
3337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    PluralRules.createRules(rules2); // for debugging
3347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    continue main;
3357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
3367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                keywordToRule.put(keyword, singleRule);
3377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
3387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Map<FixedDecimal, String> collisionTest = new TreeMap();
3397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (FixedDecimalSamples sample3 : samples) {
3407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                Set<FixedDecimalRange> samples2 = sample3.getSamples();
3417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (samples2 == null) {
3427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    continue;
3437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
3447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                for (FixedDecimalRange sample : samples2) {
3457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    for (int i = 0; i < 1; ++i) {
3467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        FixedDecimal item = i == 0 ? sample.start : sample.end;
3477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        collisionTest.clear();
3487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        for (Entry<String, PluralRules> entry : keywordToRule.entrySet()) {
3497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            PluralRules rule = entry.getValue();
3507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            String foundKeyword = rule.select(item);
3517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            if (foundKeyword.equals("other")) {
3527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                                continue;
3537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            }
3547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            String old = collisionTest.get(item);
3557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            if (old != null) {
3567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                                errln(locale + "\tNon-unique rules: " + item + " => " + old + " & " + foundKeyword);
3577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                                rule.select(item);
3587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            } else {
3597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                                collisionTest.put(item, foundKeyword);
3607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            }
3617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        }
3627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
3637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
3647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
3657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
3677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private void checkCategoriesAndExpected(String title1, String categoriesAndExpected, PluralRules rules) {
3697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (String categoryAndExpected : categoriesAndExpected.split("\\s*;\\s*")) {
3707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String[] categoryFromExpected = categoryAndExpected.split("\\s*:\\s*");
3717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String expected = categoryFromExpected[0];
3727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (String value : categoryFromExpected[1].split("\\s*,\\s*")) {
3737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (value.startsWith("@") || value.equals("…") || value.equals("null")) {
3747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    continue;
3757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
3767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                String[] values = value.split("\\s*~\\s*");
3777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                checkValue(title1, rules, expected, values[0]);
3787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (values.length > 1) {
3797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    checkValue(title1, rules, expected, values[1]);
3807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
3817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
3827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
3847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
3857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void checkValue(String title1, PluralRules rules, String expected, String value) {
3867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        double number = Double.parseDouble(value);
3877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int decimalPos = value.indexOf('.') + 1;
3887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int countVisibleFractionDigits;
3897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int fractionaldigits;
3907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (decimalPos == 0) {
3917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            countVisibleFractionDigits = fractionaldigits = 0;
3927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } else {
3937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            countVisibleFractionDigits = value.length() - decimalPos;
3947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            fractionaldigits = Integer.parseInt(value.substring(decimalPos));
3957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
3967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String result = rules.select(number, countVisibleFractionDigits, fractionaldigits);
3977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        ULocale locale = null;
3987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertEquals(getAssertMessage(title1, locale, rules, expected) + "; value: " + value, expected, result);
3997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
4007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static String[][] equalityTestData = {
4027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // once we add fractions, we had to retract the "test all possibilities" for equality,
4037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // so we only have a limited set of equality tests now.
4047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            { "c: n%11!=5", "c: n mod 11 is not 5" }, { "c: n is not 7", "c: n != 7" }, { "a:n in 2;", "a: n = 2" },
4057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            { "b:n not in 5;", "b: n != 5" },
4067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // { "a: n is 5",
4087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // "a: n in 2..6 and n not in 2..4 and n is not 6" },
4097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // { "a: n in 2..3",
4107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // "a: n is 2 or n is 3",
4117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // "a: n is 3 and n in 2..5 or n is 2" },
4127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // { "a: n is 12; b:n mod 10 in 2..3",
4137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // "b: n mod 10 in 2..3 and n is not 12; a: n in 12..12",
4147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    // "b: n is 13; a: n is 12; b: n mod 10 is 2 or n mod 10 is 3" },
4157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    };
4167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static String[][] inequalityTestData = { { "a: n mod 8 is 3", "a: n mod 7 is 3" },
4187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            { "a: n mod 3 is 2 and n is not 5", "a: n mod 6 is 2 or n is 8 or n is 11" },
4197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // the following are currently inequal, but we may make them equal in the future.
4207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            { "a: n in 2..5", "a: n in 2..4,5" }, };
4217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private void compareEquality(String id, Object[] objects, boolean shouldBeEqual) {
4237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int i = 0; i < objects.length; ++i) {
4247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Object lhs = objects[i];
4257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int start = shouldBeEqual ? i : i + 1;
4267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (int j = start; j < objects.length; ++j) {
4277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                Object rhs = objects[j];
4287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (rhs == null || shouldBeEqual != lhs.equals(rhs)) {
4297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    String msg = shouldBeEqual ? "should be equal" : "should not be equal";
4307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    fail(id + " " + msg + " (" + i + ", " + j + "):\n    " + lhs + "\n    " + rhs);
4317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
4327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // assertEquals("obj " + i + " and " + j, lhs, rhs);
4337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
4347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
4357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
4367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private void compareEqualityTestSets(String[][] sets, boolean shouldBeEqual) {
4387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int i = 0; i < sets.length; ++i) {
4397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String[] patterns = sets[i];
4407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            PluralRules[] rules = new PluralRules[patterns.length];
4417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (int j = 0; j < patterns.length; ++j) {
4427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                rules[j] = PluralRules.createRules(patterns[j]);
4437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
4447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            compareEquality("test " + i, rules, shouldBeEqual);
4457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
4467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
4477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void testEquality() {
4497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        compareEqualityTestSets(equalityTestData, true);
4507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
4517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void testInequality() {
4537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        compareEqualityTestSets(inequalityTestData, false);
4547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
4557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void testBuiltInRules() {
4577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // spot check
4587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        PluralRules rules = factory.forLocale(ULocale.US);
4597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertEquals("us 0", PluralRules.KEYWORD_OTHER, rules.select(0));
4607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertEquals("us 1", PluralRules.KEYWORD_ONE, rules.select(1));
4617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertEquals("us 2", PluralRules.KEYWORD_OTHER, rules.select(2));
4627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        rules = factory.forLocale(ULocale.JAPAN);
4647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertEquals("ja 0", PluralRules.KEYWORD_OTHER, rules.select(0));
4657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertEquals("ja 1", PluralRules.KEYWORD_OTHER, rules.select(1));
4667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertEquals("ja 2", PluralRules.KEYWORD_OTHER, rules.select(2));
4677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        rules = factory.forLocale(ULocale.createCanonical("ru"));
4697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertEquals("ru 0", PluralRules.KEYWORD_MANY, rules.select(0));
4707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertEquals("ru 1", PluralRules.KEYWORD_ONE, rules.select(1));
4717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertEquals("ru 2", PluralRules.KEYWORD_FEW, rules.select(2));
4727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
4737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void testFunctionalEquivalent() {
4757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // spot check
4767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        ULocale unknown = ULocale.createCanonical("zz_ZZ");
4777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        ULocale un_equiv = PluralRules.getFunctionalEquivalent(unknown, null);
4787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertEquals("unknown locales have root", ULocale.ROOT, un_equiv);
4797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        ULocale jp_equiv = PluralRules.getFunctionalEquivalent(ULocale.JAPAN, null);
4817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        ULocale cn_equiv = PluralRules.getFunctionalEquivalent(ULocale.CHINA, null);
4827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertEquals("japan and china equivalent locales", jp_equiv, cn_equiv);
4837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        boolean[] available = new boolean[1];
4857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        ULocale russia = ULocale.createCanonical("ru_RU");
4867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        ULocale ru_ru_equiv = PluralRules.getFunctionalEquivalent(russia, available);
4877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertFalse("ru_RU not listed", available[0]);
4887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        ULocale russian = ULocale.createCanonical("ru");
4907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        ULocale ru_equiv = PluralRules.getFunctionalEquivalent(russian, available);
4917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertTrue("ru listed", available[0]);
4927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertEquals("ru and ru_RU equivalent locales", ru_ru_equiv, ru_equiv);
4937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
4947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
4957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void testAvailableULocales() {
4967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        ULocale[] locales = factory.getAvailableULocales();
4977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Set localeSet = new HashSet();
4987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        localeSet.addAll(Arrays.asList(locales));
4997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertEquals("locales are unique in list", locales.length, localeSet.size());
5017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
5027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /*
5047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Test the method public static PluralRules parseDescription(String description)
5057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
5067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void TestParseDescription() {
5077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        try {
5087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (PluralRules.DEFAULT != PluralRules.parseDescription("")) {
5097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                errln("PluralRules.parseDescription(String) was suppose "
5107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        + "to return PluralRules.DEFAULT when String is of " + "length 0.");
5117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
5127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } catch (ParseException e) {
5137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            errln("PluralRules.parseDescription(String) was not suppose " + "to return an exception.");
5147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
5167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /*
5187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Tests the method public static PluralRules createRules(String description)
5197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
5207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void TestCreateRules() {
5217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        try {
5227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (PluralRules.createRules(null) != null) {
5237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                errln("PluralRules.createRules(String) was suppose to "
5247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        + "return null for an invalid String descrtiption.");
5257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
5267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } catch (Exception e) {
5277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
5297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /*
5317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Tests the method public int hashCode()
5327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
5337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void TestHashCode() {
5347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // Bad test, breaks whenever PluralRules implementation changes.
5357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // PluralRules pr = PluralRules.DEFAULT;
5367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // if (106069776 != pr.hashCode()) {
5377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // errln("PluralRules.hashCode() was suppose to return 106069776 " + "when PluralRules.DEFAULT.");
5387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // }
5397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
5407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /*
5427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Tests the method public boolean equals(PluralRules rhs)
5437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
5447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void TestEquals() {
5457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        PluralRules pr = PluralRules.DEFAULT;
5467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (pr.equals((PluralRules) null)) {
5487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            errln("PluralRules.equals(PluralRules) was supposed to return false " + "when passing null.");
5497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
5517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private void assertRuleValue(String rule, double value) {
5537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertRuleKeyValue("a:" + rule, "a", value);
5547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
5557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private void assertRuleKeyValue(String rule, String key, double value) {
5577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        PluralRules pr = PluralRules.createRules(rule);
5587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertEquals(rule, value, pr.getUniqueKeywordValue(key));
5597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
5607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /*
5627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Tests getUniqueKeywordValue()
5637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
5647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void TestGetUniqueKeywordValue() {
5657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertRuleKeyValue("a: n is 1", "not_defined", PluralRules.NO_UNIQUE_VALUE); // key not defined
5667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertRuleValue("n within 2..2", 2);
5677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertRuleValue("n is 1", 1);
5687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertRuleValue("n in 2..2", 2);
5697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertRuleValue("n in 3..4", PluralRules.NO_UNIQUE_VALUE);
5707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertRuleValue("n within 3..4", PluralRules.NO_UNIQUE_VALUE);
5717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertRuleValue("n is 2 or n is 2", 2);
5727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertRuleValue("n is 2 and n is 2", 2);
5737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertRuleValue("n is 2 or n is 3", PluralRules.NO_UNIQUE_VALUE);
5747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertRuleValue("n is 2 and n is 3", PluralRules.NO_UNIQUE_VALUE);
5757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertRuleValue("n is 2 or n in 2..3", PluralRules.NO_UNIQUE_VALUE);
5767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertRuleValue("n is 2 and n in 2..3", 2);
5777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertRuleKeyValue("a: n is 1", "other", PluralRules.NO_UNIQUE_VALUE); // key matches default rule
5787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertRuleValue("n in 2,3", PluralRules.NO_UNIQUE_VALUE);
5797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertRuleValue("n in 2,3..6 and n not in 2..3,5..6", 4);
5807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
5817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
5827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
5837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * The version in PluralFormatUnitTest is not really a test, and it's in the wrong place anyway, so I'm putting a
5847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * variant of it here.
5857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
5867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void TestGetSamples() {
5877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Set<ULocale> uniqueRuleSet = new HashSet<ULocale>();
5887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (ULocale locale : factory.getAvailableULocales()) {
5897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            uniqueRuleSet.add(PluralRules.getFunctionalEquivalent(locale, null));
5907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
5917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (ULocale locale : uniqueRuleSet) {
5927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            PluralRules rules = factory.forLocale(locale);
5937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            logln("\nlocale: " + (locale == ULocale.ROOT ? "root" : locale.toString()) + ", rules: " + rules);
5947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Set<String> keywords = rules.getKeywords();
5957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (String keyword : keywords) {
5967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                Collection<Double> list = rules.getSamples(keyword);
5977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                logln("keyword: " + keyword + ", samples: " + list);
5987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // with fractions, the samples can be empty and thus the list null. In that case, however, there will be
5997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // FixedDecimal values.
6007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // So patch the test for that.
6017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (list.size() == 0) {
6027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // when the samples (meaning integer samples) are null, then then integerSamples must be, and the
6037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    // decimalSamples must not be
6047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    FixedDecimalSamples integerSamples = rules.getDecimalSamples(keyword, SampleType.INTEGER);
6057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    FixedDecimalSamples decimalSamples = rules.getDecimalSamples(keyword, SampleType.DECIMAL);
6067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    assertTrue(getAssertMessage("List is not null", locale, rules, keyword), integerSamples == null
6077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            && decimalSamples != null && decimalSamples.samples.size() != 0);
6087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else {
6097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if (!assertTrue(getAssertMessage("Test getSamples.isEmpty", locale, rules, keyword),
6107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            !list.isEmpty())) {
6117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        rules.getSamples(keyword);
6127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
6137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if (rules.toString().contains(": j")) {
6147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        // hack until we remove j
6157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    } else {
6167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        for (double value : list) {
6177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            assertEquals(getAssertMessage("Match keyword", locale, rules, keyword) + "; value '"
6187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                                    + value + "'", keyword, rules.select(value));
6197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        }
6207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
6217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
6227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
6237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            assertNull(locale + ", list is null", rules.getSamples("@#$%^&*"));
6257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            assertNull(locale + ", list is null", rules.getSamples("@#$%^&*", SampleType.DECIMAL));
6267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
6277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
6287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public String getAssertMessage(String message, ULocale locale, PluralRules rules, String keyword) {
6307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String ruleString = "";
6317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (keyword != null) {
6327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (keyword.equals("other")) {
6337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                for (String keyword2 : rules.getKeywords()) {
6347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    ruleString += " NOR " + rules.getRules(keyword2).split("@")[0];
6357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
6367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else {
6377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                String rule = rules.getRules(keyword);
6387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                ruleString = rule == null ? null : rule.split("@")[0];
6397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
6407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ruleString = "; rule: '" + keyword + ": " + ruleString + "'";
6417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // !keyword.equals("other") ? "'; keyword: '" + keyword + "'; rule: '" + rules.getRules(keyword) + "'"
6427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // : "'; keyword: '" + keyword + "'; rules: '" + rules.toString() + "'";
6437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
6447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return message + (locale == null ? "" : "; locale: '" + locale + "'") + ruleString;
6457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
6467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
6487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * Returns the empty set if the keyword is not defined, null if there are an unlimited number of values for the
6497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * keyword, or the set of values that trigger the keyword.
6507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
6517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void TestGetAllKeywordValues() {
6527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        // data is pairs of strings, the rule, and the expected values as arguments
6537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String[] data = {
6547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                "other: ; a: n mod 3 is 0",
6557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                "a: null",
6567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                "a: n in 2..5 and n within 5..8",
6577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                "a: 5",
6587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                "a: n in 2..5",
6597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                "a: 2,3,4,5; other: null",
6607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                "a: n not in 2..5",
6617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                "a: null; other: null",
6627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                "a: n within 2..5",
6637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                "a: 2,3,4,5; other: null",
6647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                "a: n not within 2..5",
6657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                "a: null; other: null",
6667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                "a: n in 2..5 or n within 6..8",
6677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                "a: 2,3,4,5,6,7,8", // ignore 'other' here on out, always null
6687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                "a: n in 2..5 and n within 6..8",
6697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                "a: null",
6707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // we no longer support 'degenerate' rules
6717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // "a: n within 2..5 and n within 6..8", "a:", // our sampling catches these
6727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // "a: n within 2..5 and n within 5..8", "a: 5", // ''
6737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // "a: n within 1..2 and n within 2..3 or n within 3..4 and n within 4..5", "a: 2,4",
6747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // "a: n mod 3 is 0 and n within 0..5", "a: 0,3",
6757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                "a: n within 1..2 and n within 2..3 or n within 3..4 and n within 4..5 or n within 5..6 and n within 6..7",
6767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                "a: 2,4,6", // but not this...
6777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                "a: n mod 3 is 0 and n within 1..2", "a: null", "a: n mod 3 is 0 and n within 0..6", "a: 0,3,6",
6787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                "a: n mod 3 is 0 and n in 3..12", "a: 3,6,9,12", "a: n in 2,4..6 and n is not 5", "a: 2,4,6", };
6797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int i = 0; i < data.length; i += 2) {
6807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String ruleDescription = data[i];
6817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String result = data[i + 1];
6827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
6837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            PluralRules p = PluralRules.createRules(ruleDescription);
6847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (p == null) { // for debugging
6857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                PluralRules.createRules(ruleDescription);
6867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
6877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (String ruleResult : result.split(";")) {
6887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                String[] ruleAndValues = ruleResult.split(":");
6897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                String keyword = ruleAndValues[0].trim();
6907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                String valueList = ruleAndValues.length < 2 ? null : ruleAndValues[1];
6917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (valueList != null) {
6927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    valueList = valueList.trim();
6937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
6947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                Collection<Double> values;
6957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (valueList == null || valueList.length() == 0) {
6967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    values = Collections.EMPTY_SET;
6977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else if ("null".equals(valueList)) {
6987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    values = null;
6997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                } else {
7007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    values = new TreeSet<Double>();
7017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    for (String value : valueList.split(",")) {
7027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        values.add(Double.parseDouble(value));
7037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
7047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
7057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
7067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                Collection<Double> results = p.getAllKeywordValues(keyword);
7077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                assertEquals(keyword + " in " + ruleDescription, values, results == null ? null : new HashSet(results));
7087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
7097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (results != null) {
7107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    try {
7117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        results.add(PluralRules.NO_UNIQUE_VALUE);
7127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        fail("returned set is modifiable");
7137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    } catch (UnsupportedOperationException e) {
7147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        // pass
7157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
7167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
7177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
7187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
7197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
7207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
7217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void TestOrdinal() {
7227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        PluralRules pr = factory.forLocale(ULocale.ENGLISH, PluralType.ORDINAL);
7237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertEquals("PluralRules(en-ordinal).select(2)", "two", pr.select(2));
7247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
7257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
7267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void TestBasicFraction() {
7277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        String[][] tests = { { "en", "one: j is 1" }, { "1", "0", "1", "one" }, { "1", "2", "1.00", "other" }, };
7287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        ULocale locale = null;
7297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        NumberFormat nf = null;
7307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        PluralRules pr = null;
7317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
7327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (String[] row : tests) {
7337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            switch (row.length) {
7347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            case 2:
7357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                locale = ULocale.forLanguageTag(row[0]);
7367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                nf = NumberFormat.getInstance(locale);
7377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                pr = PluralRules.createRules(row[1]);
7387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
7397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            case 4:
7407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                double n = Double.parseDouble(row[0]);
7417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                int minFracDigits = Integer.parseInt(row[1]);
7427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                nf.setMinimumFractionDigits(minFracDigits);
7437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                String expectedFormat = row[2];
7447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                String expectedKeyword = row[3];
7457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
7467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                UFieldPosition pos = new UFieldPosition();
7477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                String formatted = nf.format(1.0, new StringBuffer(), pos).toString();
7487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                int countVisibleFractionDigits = pos.getCountVisibleFractionDigits();
7497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                long fractionDigits = pos.getFractionDigits();
7507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                String keyword = pr.select(n, countVisibleFractionDigits, fractionDigits);
7517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                assertEquals("Formatted " + n + "\t" + minFracDigits, expectedFormat, formatted);
7527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                assertEquals("Keyword " + n + "\t" + minFracDigits, expectedKeyword, keyword);
7537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                break;
7547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            default:
7557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                throw new RuntimeException();
7567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
7577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
7587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
7597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
7607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void TestLimitedAndSamplesConsistency() {
7617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (ULocale locale : PluralRules.getAvailableULocales()) {
7627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ULocale loc2 = PluralRules.getFunctionalEquivalent(locale, null);
7637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (!loc2.equals(locale)) {
7647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                continue; // only need "unique" rules
7657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
7667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (PluralType type : PluralType.values()) {
7677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                PluralRules rules = PluralRules.forLocale(locale, type);
7687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                for (SampleType sampleType : SampleType.values()) {
7697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if (sampleType == SampleType.DECIMAL && type == PluralType.ORDINAL) {
7707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        logKnownIssue("10783", "Fix issues with isLimited vs computeLimited on ordinals");
7717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        continue;
7727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
7737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    for (String keyword : rules.getKeywords()) {
7747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        boolean isLimited = rules.isLimited(keyword, sampleType);
7757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        boolean computeLimited = rules.computeLimited(keyword, sampleType);
7767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        if (!keyword.equals("other")) {
7777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            assertEquals(getAssertMessage("computeLimited == isLimited", locale, rules, keyword),
7787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                                    computeLimited, isLimited);
7797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        }
7807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        Collection<Double> samples = rules.getSamples(keyword, sampleType);
7817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        assertNotNull(getAssertMessage("Samples must not be null", locale, rules, keyword), samples);
7827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        /* FixedDecimalSamples decimalSamples = */rules.getDecimalSamples(keyword, sampleType);
7837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        // assertNotNull(getAssertMessage("Decimal samples must be null if unlimited", locale, rules,
7847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        // keyword), decimalSamples);
7857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
7867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
7877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
7887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
7897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
7907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
7917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void TestKeywords() {
7927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Set<String> possibleKeywords = new LinkedHashSet(Arrays.asList("zero", "one", "two", "few", "many", "other"));
7937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Object[][][] tests = {
7947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // format is locale, explicits, then triples of keyword, status, unique value.
7957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { { "en", null }, { "one", KeywordStatus.UNIQUE, 1.0d }, { "other", KeywordStatus.UNBOUNDED, null } },
7967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                { { "pl", null }, { "one", KeywordStatus.UNIQUE, 1.0d }, { "few", KeywordStatus.UNBOUNDED, null },
7977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        { "many", KeywordStatus.UNBOUNDED, null },
7987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        { "other", KeywordStatus.SUPPRESSED, null, KeywordStatus.UNBOUNDED, null } // note that it is
7997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                                                                                                   // suppressed in
8007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                                                                                                   // INTEGER but not
8017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                                                                                                   // DECIMAL
8027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }, { { "en", new HashSet<Double>(Arrays.asList(1.0d)) }, // check that 1 is suppressed
8037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        { "one", KeywordStatus.SUPPRESSED, null }, { "other", KeywordStatus.UNBOUNDED, null } }, };
8047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Output<Double> uniqueValue = new Output<Double>();
8057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (Object[][] test : tests) {
8067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ULocale locale = new ULocale((String) test[0][0]);
8077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // NumberType numberType = (NumberType) test[1];
8087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Set<Double> explicits = (Set<Double>) test[0][1];
8097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            PluralRules pluralRules = factory.forLocale(locale);
8107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            LinkedHashSet<String> remaining = new LinkedHashSet(possibleKeywords);
8117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (int i = 1; i < test.length; ++i) {
8127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                Object[] row = test[i];
8137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                String keyword = (String) row[0];
8147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                KeywordStatus statusExpected = (KeywordStatus) row[1];
8157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                Double uniqueExpected = (Double) row[2];
8167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                remaining.remove(keyword);
8177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                KeywordStatus status = pluralRules.getKeywordStatus(keyword, 0, explicits, uniqueValue);
8187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                assertEquals(getAssertMessage("Unique Value", locale, pluralRules, keyword), uniqueExpected,
8197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        uniqueValue.value);
8207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                assertEquals(getAssertMessage("Keyword Status", locale, pluralRules, keyword), statusExpected, status);
8217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (row.length > 3) {
8227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    statusExpected = (KeywordStatus) row[3];
8237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    uniqueExpected = (Double) row[4];
8247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    status = pluralRules.getKeywordStatus(keyword, 0, explicits, uniqueValue, SampleType.DECIMAL);
8257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    assertEquals(getAssertMessage("Unique Value - decimal", locale, pluralRules, keyword),
8267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            uniqueExpected, uniqueValue.value);
8277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    assertEquals(getAssertMessage("Keyword Status - decimal", locale, pluralRules, keyword),
8287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            statusExpected, status);
8297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
8307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
8317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (String keyword : remaining) {
8327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                KeywordStatus status = pluralRules.getKeywordStatus(keyword, 0, null, uniqueValue);
8337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                assertEquals("Invalid keyword " + keyword, status, KeywordStatus.INVALID);
8347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                assertNull("Invalid keyword " + keyword, uniqueValue.value);
8357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
8367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
8377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
8387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
8397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    enum StandardPluralCategories {
8407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        zero, one, two, few, many, other;
8417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /**
8427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         *
8437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         */
8447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        private static final Set<StandardPluralCategories> ALL = Collections.unmodifiableSet(EnumSet
8457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                .allOf(StandardPluralCategories.class));
8467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
8477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        /**
8487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * Return a mutable set
8497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         *
8507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @param source
8517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         * @return
8527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert         */
8537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        static final EnumSet<StandardPluralCategories> getSet(Collection<String> source) {
8547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            EnumSet<StandardPluralCategories> result = EnumSet.noneOf(StandardPluralCategories.class);
8557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (String s : source) {
8567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                result.add(StandardPluralCategories.valueOf(s));
8577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
8587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return result;
8597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
8607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
8617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        static final Comparator<Set<StandardPluralCategories>> SHORTEST_FIRST = new Comparator<Set<StandardPluralCategories>>() {
8627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            public int compare(Set<StandardPluralCategories> arg0, Set<StandardPluralCategories> arg1) {
8637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                int diff = arg0.size() - arg1.size();
8647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (diff != 0) {
8657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    return diff;
8667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
8677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // otherwise first...
8687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // could be optimized, but we don't care here.
8697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                for (StandardPluralCategories value : ALL) {
8707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    if (arg0.contains(value)) {
8717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        if (!arg1.contains(value)) {
8727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                            return 1;
8737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        }
8747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    } else if (arg1.contains(value)) {
8757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                        return -1;
8767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    }
8777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
8787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
8797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                return 0;
8807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
8817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
8827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        };
8837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
8847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
8857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void TestLocales() {
8867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        if (false) {
8877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            generateLOCALE_SNAPSHOT();
8887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
8897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (String test : LOCALE_SNAPSHOT) {
8907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            test = test.trim();
8917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            String[] parts = test.split("\\s*;\\s*");
8927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (String localeString : parts[0].split("\\s*,\\s*")) {
8937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                ULocale locale = new ULocale(localeString);
8947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                if (factory.hasOverride(locale)) {
8957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    continue; // skip for now
8967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
8977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                PluralRules rules = factory.forLocale(locale);
8987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                for (int i = 1; i < parts.length; ++i) {
8997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    checkCategoriesAndExpected(localeString, parts[i], rules);
9007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
9017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
9027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
9037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
9047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
9057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private static final Comparator<PluralRules> PLURAL_RULE_COMPARATOR = new Comparator<PluralRules>() {
9067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public int compare(PluralRules o1, PluralRules o2) {
9077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return o1.compareTo(o2);
9087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
9097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    };
9107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
9117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private void generateLOCALE_SNAPSHOT() {
9127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Comparator c = new CollectionUtilities.CollectionComparator<Comparable>();
9137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Relation<Set<StandardPluralCategories>, PluralRules> setsToRules = Relation.of(
9147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                new TreeMap<Set<StandardPluralCategories>, Set<PluralRules>>(c), TreeSet.class, PLURAL_RULE_COMPARATOR);
9157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Relation<PluralRules, ULocale> data = Relation.of(
9167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                new TreeMap<PluralRules, Set<ULocale>>(PLURAL_RULE_COMPARATOR), TreeSet.class);
9177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (ULocale locale : PluralRules.getAvailableULocales()) {
9187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            PluralRules pr = PluralRules.forLocale(locale);
9197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            EnumSet<StandardPluralCategories> set = getCanonicalSet(pr.getKeywords());
9207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            setsToRules.put(set, pr);
9217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            data.put(pr, locale);
9227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
9237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (Entry<Set<StandardPluralCategories>, Set<PluralRules>> entry1 : setsToRules.keyValuesSet()) {
9247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Set<StandardPluralCategories> set = entry1.getKey();
9257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Set<PluralRules> rules = entry1.getValue();
9267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            System.out.println("\n        // " + set);
9277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            for (PluralRules rule : rules) {
9287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                Set<ULocale> locales = data.get(rule);
9297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                System.out.print("        \"" + CollectionUtilities.join(locales, ","));
9307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                for (StandardPluralCategories spc : set) {
9317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    String keyword = spc.toString();
9327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    FixedDecimalSamples samples = rule.getDecimalSamples(keyword, SampleType.INTEGER);
9337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    System.out.print("; " + spc + ": " + samples);
9347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                }
9357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                System.out.println("\",");
9367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
9377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
9387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
9397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
9407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
9417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param keywords
9427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return
9437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
9447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private EnumSet<StandardPluralCategories> getCanonicalSet(Set<String> keywords) {
9457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        EnumSet<StandardPluralCategories> result = EnumSet.noneOf(StandardPluralCategories.class);
9467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (String s : keywords) {
9477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            result.add(StandardPluralCategories.valueOf(s));
9487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
9497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return result;
9507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
9517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
9527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    static final String[] LOCALE_SNAPSHOT = {
9537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // [other]
9547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "bm,bo,dz,id,ig,ii,in,ja,jbo,jv,jw,kde,kea,km,ko,lkt,lo,ms,my,nqo,root,sah,ses,sg,th,to,vi,wo,yo,zh; other: @integer 0~15, 100, 1000, 10000, 100000, 1000000, …",
9557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
9567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // [one, other]
9577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "am,bn,fa,gu,hi,kn,mr,zu; one: @integer 0, 1; other: @integer 2~17, 100, 1000, 10000, 100000, 1000000, …",
9587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "ff,fr,hy,kab; one: @integer 0, 1; other: @integer 2~17, 100, 1000, 10000, 100000, 1000000, …",
9597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "ast,ca,de,en,et,fi,fy,gl,it,ji,nl,sv,sw,ur,yi; one: @integer 1; other: @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, …",
9607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "pt; one: @integer 1; other: @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, …",
9617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "si; one: @integer 0, 1; other: @integer 2~17, 100, 1000, 10000, 100000, 1000000, …",
9627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "ak,bh,guw,ln,mg,nso,pa,ti,wa; one: @integer 0, 1; other: @integer 2~17, 100, 1000, 10000, 100000, 1000000, …",
9637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "tzm; one: @integer 0, 1, 11~24; other: @integer 2~10, 100~106, 1000, 10000, 100000, 1000000, …",
9647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "af,asa,az,bem,bez,bg,brx,cgg,chr,ckb,dv,ee,el,eo,es,eu,fo,fur,gsw,ha,haw,hu,jgo,jmc,ka,kaj,kcg,kk,kkj,kl,ks,ksb,ku,ky,lb,lg,mas,mgo,ml,mn,nah,nb,nd,ne,nn,nnh,no,nr,ny,nyn,om,or,os,pap,ps,rm,rof,rwk,saq,seh,sn,so,sq,ss,ssy,st,syr,ta,te,teo,tig,tk,tn,tr,ts,ug,uz,ve,vo,vun,wae,xh,xog; one: @integer 1; other: @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, …",
9657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "pt_PT; one: @integer 1; other: @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, …",
9667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "da; one: @integer 1; other: @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, …",
9677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "is; one: @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, …; other: @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, …",
9687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "mk; one: @integer 1, 11, 21, 31, 41, 51, 61, 71, 101, 1001, …; other: @integer 0, 2~10, 12~17, 100, 1000, 10000, 100000, 1000000, …",
9697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "fil,tl; one: @integer 0~3, 5, 7, 8, 10~13, 15, 17, 18, 20, 21, 100, 1000, 10000, 100000, 1000000, …; other: @integer 4, 6, 9, 14, 16, 19, 24, 26, 104, 1004, …",
9707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
9717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // [zero, one, other]
9727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "lag; zero: @integer 0; one: @integer 1; other: @integer 2~17, 100, 1000, 10000, 100000, 1000000, …",
9737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "lv,prg; zero: @integer 0, 10~20, 30, 40, 50, 60, 100, 1000, 10000, 100000, 1000000, …; one: @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, …; other: @integer 2~9, 22~29, 102, 1002, …",
9747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "ksh; zero: @integer 0; one: @integer 1; other: @integer 2~17, 100, 1000, 10000, 100000, 1000000, …",
9757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
9767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // [one, two, other]
9777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "iu,kw,naq,se,sma,smi,smj,smn,sms; one: @integer 1; two: @integer 2; other: @integer 0, 3~17, 100, 1000, 10000, 100000, 1000000, …",
9787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
9797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // [one, few, other]
9807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "shi; one: @integer 0, 1; few: @integer 2~10; other: @integer 11~26, 100, 1000, 10000, 100000, 1000000, …",
9817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "mo,ro; one: @integer 1; few: @integer 0, 2~16, 101, 1001, …; other: @integer 20~35, 100, 1000, 10000, 100000, 1000000, …",
9827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "bs,hr,sh,sr; one: @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, …; few: @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, …; other: @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …",
9837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
9847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // [one, two, few, other]
9857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "gd; one: @integer 1, 11; two: @integer 2, 12; few: @integer 3~10, 13~19; other: @integer 0, 20~34, 100, 1000, 10000, 100000, 1000000, …",
9867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "sl; one: @integer 1, 101, 201, 301, 401, 501, 601, 701, 1001, …; two: @integer 2, 102, 202, 302, 402, 502, 602, 702, 1002, …; few: @integer 3, 4, 103, 104, 203, 204, 303, 304, 403, 404, 503, 504, 603, 604, 703, 704, 1003, …; other: @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …",
9877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
9887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // [one, two, many, other]
9897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "he,iw; one: @integer 1; two: @integer 2; many: @integer 20, 30, 40, 50, 60, 70, 80, 90, 100, 1000, 10000, 100000, 1000000, …; other: @integer 0, 3~17, 101, 1001, …",
9907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
9917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // [one, few, many, other]
9927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "cs,sk; one: @integer 1; few: @integer 2~4; many: null; other: @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …",
9937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "be; one: @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, …; few: @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, …; many: @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …; other: null",
9947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "lt; one: @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, …; few: @integer 2~9, 22~29, 102, 1002, …; many: null; other: @integer 0, 10~20, 30, 40, 50, 60, 100, 1000, 10000, 100000, 1000000, …",
9957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "mt; one: @integer 1; few: @integer 0, 2~10, 102~107, 1002, …; many: @integer 11~19, 111~117, 1011, …; other: @integer 20~35, 100, 1000, 10000, 100000, 1000000, …",
9967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "pl; one: @integer 1; few: @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, …; many: @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …; other: null",
9977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "ru,uk; one: @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, …; few: @integer 2~4, 22~24, 32~34, 42~44, 52~54, 62, 102, 1002, …; many: @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, …; other: null",
9987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
9997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // [one, two, few, many, other]
10007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "br; one: @integer 1, 21, 31, 41, 51, 61, 81, 101, 1001, …; two: @integer 2, 22, 32, 42, 52, 62, 82, 102, 1002, …; few: @integer 3, 4, 9, 23, 24, 29, 33, 34, 39, 43, 44, 49, 103, 1003, …; many: @integer 1000000, …; other: @integer 0, 5~8, 10~20, 100, 1000, 10000, 100000, …",
10017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "ga; one: @integer 1; two: @integer 2; few: @integer 3~6; many: @integer 7~10; other: @integer 0, 11~25, 100, 1000, 10000, 100000, 1000000, …",
10027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "gv; one: @integer 1, 11, 21, 31, 41, 51, 61, 71, 101, 1001, …; two: @integer 2, 12, 22, 32, 42, 52, 62, 72, 102, 1002, …; few: @integer 0, 20, 40, 60, 80, 100, 120, 140, 1000, 10000, 100000, 1000000, …; many: null; other: @integer 3~10, 13~19, 23, 103, 1003, …",
10037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
10047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            // [zero, one, two, few, many, other]
10057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "ar; zero: @integer 0; one: @integer 1; two: @integer 2; few: @integer 3~10, 103~110, 1003, …; many: @integer 11~26, 111, 1011, …; other: @integer 100~102, 200~202, 300~302, 400~402, 500~502, 600, 1000, 10000, 100000, 1000000, …",
10067935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            "cy; zero: @integer 0; one: @integer 1; two: @integer 2; few: @integer 3; many: @integer 6; other: @integer 4, 5, 7~20, 100, 1000, 10000, 100000, 1000000, …", };
10077935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
10087935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private <T extends Serializable> T serializeAndDeserialize(T original, Output<Integer> size) {
10097935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        try {
10107935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ByteArrayOutputStream baos = new ByteArrayOutputStream();
10117935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ObjectOutputStream ostream = new ObjectOutputStream(baos);
10127935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ostream.writeObject(original);
10137935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ostream.flush();
10147935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            byte bytes[] = baos.toByteArray();
10157935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            size.value = bytes.length;
10167935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ObjectInputStream istream = new ObjectInputStream(new ByteArrayInputStream(bytes));
10177935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            T reconstituted = (T) istream.readObject();
10187935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return reconstituted;
10197935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } catch (IOException e) {
10207935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new RuntimeException(e);
10217935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } catch (ClassNotFoundException e) {
10227935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            throw new RuntimeException(e);
10237935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
10247935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
10257935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
10267935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void TestSerialization() {
10277935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        Output<Integer> size = new Output<Integer>();
10287935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        int max = 0;
10297935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (ULocale locale : PluralRules.getAvailableULocales()) {
10307935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            PluralRules item = PluralRules.forLocale(locale);
10317935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            PluralRules item2 = serializeAndDeserialize(item, size);
10327935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            logln(locale + "\tsize:\t" + size.value);
10337935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            max = Math.max(max, size.value);
10347935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (!assertEquals(locale + "\tPlural rules before and after serialization", item, item2)) {
10357935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                // for debugging
10367935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                PluralRules item3 = serializeAndDeserialize(item, size);
10377935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                item.equals(item3);
10387935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
10397935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
10407935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        logln("max \tsize:\t" + max);
10417935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
10427935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
10437935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public static class FixedDecimalHandler implements SerializableTest.Handler {
10447935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public Object[] getTestObjects() {
10457935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            FixedDecimal items[] = { new FixedDecimal(3d), new FixedDecimal(3d, 2), new FixedDecimal(3.1d, 1),
10467935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                    new FixedDecimal(3.1d, 2), };
10477935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return items;
10487935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
10497935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
10507935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        public boolean hasSameBehavior(Object a, Object b) {
10517935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            FixedDecimal a1 = (FixedDecimal) a;
10527935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            FixedDecimal b1 = (FixedDecimal) b;
10537935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            return a1.equals(b1);
10547935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
10557935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
10567935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
10577935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void TestSerial() {
10587935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        PluralRules s = PluralRules.forLocale(ULocale.ENGLISH);
10597935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        checkStreamingEquality(s);
10607935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
10617935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
10627935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void checkStreamingEquality(PluralRules s) {
10637935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        try {
10647935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
10657935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteOut);
10667935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            objectOutputStream.writeObject(s);
10677935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            objectOutputStream.close();
10687935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            byte[] contents = byteOut.toByteArray();
10697935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            logln(s.getClass() + ": " + showBytes(contents));
10707935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ByteArrayInputStream byteIn = new ByteArrayInputStream(contents);
10717935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            ObjectInputStream objectInputStream = new ObjectInputStream(byteIn);
10727935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            Object obj = objectInputStream.readObject();
10737935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            assertEquals("Streamed Object equals ", s, obj);
10747935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        } catch (Exception e) {
10757935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            assertNull("TestSerial", e);
10767935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
10777935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
10787935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
10797935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    /**
10807935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @param contents
10817935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     * @return
10827935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert     */
10837935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    private String showBytes(byte[] contents) {
10847935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        StringBuilder b = new StringBuilder('[');
10857935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        for (int i = 0; i < contents.length; ++i) {
10867935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            int item = contents[i] & 0xFF;
10877935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            if (item >= 0x20 && item <= 0x7F) {
10887935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                b.append((char) item);
10897935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            } else {
10907935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert                b.append('(').append(Utility.hex(item, 2)).append(')');
10917935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert            }
10927935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        }
10937935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        return b.append(']').toString();
10947935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
10957935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
10967935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    public void testJavaLocaleFactory() {
10977935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        PluralRules rulesU0 = PluralRules.forLocale(ULocale.FRANCE);
10987935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        PluralRules rulesJ0 = PluralRules.forLocale(Locale.FRANCE);
10997935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertEquals("forLocale()", rulesU0, rulesJ0);
11007935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert
11017935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        PluralRules rulesU1 = PluralRules.forLocale(ULocale.FRANCE, PluralType.ORDINAL);
11027935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        PluralRules rulesJ1 = PluralRules.forLocale(Locale.FRANCE, PluralType.ORDINAL);
11037935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert        assertEquals("forLocale() with type", rulesU1, rulesJ1);
11047935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert    }
11057935b1839a081ed19ae0d33029ad3c09632a2caaFredrik Roubert}
1106