1 // 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html#License 3 /* 4 ******************************************************************************* 5 * Copyright (C) 2007-2015, International Business Machines Corporation and 6 * others. All Rights Reserved. 7 ******************************************************************************* 8 */ 9 package com.ibm.icu.dev.test.format; 10 11 import java.io.ByteArrayInputStream; 12 import java.io.ByteArrayOutputStream; 13 import java.io.IOException; 14 import java.io.ObjectInputStream; 15 import java.io.ObjectOutputStream; 16 import java.io.Serializable; 17 import java.text.ParseException; 18 import java.util.ArrayList; 19 import java.util.Arrays; 20 import java.util.Collection; 21 import java.util.Collections; 22 import java.util.Comparator; 23 import java.util.EnumSet; 24 import java.util.HashMap; 25 import java.util.HashSet; 26 import java.util.LinkedHashSet; 27 import java.util.List; 28 import java.util.Locale; 29 import java.util.Map; 30 import java.util.Map.Entry; 31 import java.util.Set; 32 import java.util.TreeMap; 33 import java.util.TreeSet; 34 35 import org.junit.Test; 36 import org.junit.runner.RunWith; 37 import org.junit.runners.JUnit4; 38 39 import com.ibm.icu.dev.test.TestFmwk; 40 import com.ibm.icu.dev.test.serializable.SerializableTestUtility; 41 import com.ibm.icu.dev.util.CollectionUtilities; 42 import com.ibm.icu.impl.Relation; 43 import com.ibm.icu.impl.Utility; 44 import com.ibm.icu.text.NumberFormat; 45 import com.ibm.icu.text.PluralRules; 46 import com.ibm.icu.text.PluralRules.FixedDecimal; 47 import com.ibm.icu.text.PluralRules.FixedDecimalRange; 48 import com.ibm.icu.text.PluralRules.FixedDecimalSamples; 49 import com.ibm.icu.text.PluralRules.KeywordStatus; 50 import com.ibm.icu.text.PluralRules.PluralType; 51 import com.ibm.icu.text.PluralRules.SampleType; 52 import com.ibm.icu.text.UFieldPosition; 53 import com.ibm.icu.util.Output; 54 import com.ibm.icu.util.ULocale; 55 56 /** 57 * @author dougfelt (Doug Felt) 58 * @author markdavis (Mark Davis) [for fractional support] 59 */ 60 @RunWith(JUnit4.class) 61 public class PluralRulesTest extends TestFmwk { 62 63 PluralRulesFactory factory = PluralRulesFactory.NORMAL; 64 65 @Test 66 public void testOverUnderflow() { 67 logln(String.valueOf(Long.MAX_VALUE + 1d)); 68 for (double[] testDouble : new double[][] { 69 { 1E18, 0, 0, 1E18 }, // check overflow 70 { 10000000000000.1d, 1, 1, 10000000000000d }, { -0.00001d, 1, 5, 0 }, { 1d, 0, 0, 1 }, 71 { 1.1d, 1, 1, 1 }, { 12345d, 0, 0, 12345 }, { 12345.678912d, 678912, 6, 12345 }, 72 { 12345.6789123d, 678912, 6, 12345 }, // we only go out 6 digits 73 { 1E18, 0, 0, 1E18 }, // check overflow 74 { 1E19, 0, 0, 1E18 }, // check overflow 75 }) { 76 FixedDecimal fd = new FixedDecimal(testDouble[0]); 77 assertEquals(testDouble[0] + "=doubleValue()", testDouble[0], fd.doubleValue()); 78 assertEquals(testDouble[0] + " decimalDigits", (int) testDouble[1], fd.getDecimalDigits()); 79 assertEquals(testDouble[0] + " visibleDecimalDigitCount", (int) testDouble[2], fd.getVisibleDecimalDigitCount()); 80 assertEquals(testDouble[0] + " decimalDigitsWithoutTrailingZeros", (int) testDouble[1], 81 fd.getDecimalDigitsWithoutTrailingZeros()); 82 assertEquals(testDouble[0] + " visibleDecimalDigitCountWithoutTrailingZeros", (int) testDouble[2], 83 fd.getVisibleDecimalDigitCountWithoutTrailingZeros()); 84 assertEquals(testDouble[0] + " integerValue", (long) testDouble[3], fd.getIntegerValue()); 85 } 86 87 for (ULocale locale : new ULocale[] { ULocale.ENGLISH, new ULocale("cy"), new ULocale("ar") }) { 88 PluralRules rules = factory.forLocale(locale); 89 90 assertEquals(locale + " NaN", "other", rules.select(Double.NaN)); 91 assertEquals(locale + " ", "other", rules.select(Double.POSITIVE_INFINITY)); 92 assertEquals(locale + " -", "other", rules.select(Double.NEGATIVE_INFINITY)); 93 } 94 } 95 96 @Test 97 public void testSyntaxRestrictions() { 98 Object[][] shouldFail = { 99 { "a:n in 3..10,13..19" }, 100 101 // = and != always work 102 { "a:n=1" }, 103 { "a:n=1,3" }, 104 { "a:n!=1" }, 105 { "a:n!=1,3" }, 106 107 // with spacing 108 { "a: n = 1" }, 109 { "a: n = 1, 3" }, 110 { "a: n != 1" }, 111 { "a: n != 1, 3" }, 112 { "a: n ! = 1" }, 113 { "a: n ! = 1, 3" }, 114 { "a: n = 1 , 3" }, 115 { "a: n != 1 , 3" }, 116 { "a: n ! = 1 , 3" }, 117 { "a: n = 1 .. 3" }, 118 { "a: n != 1 .. 3" }, 119 { "a: n ! = 1 .. 3" }, 120 121 // more complicated 122 { "a:n in 3 .. 10 , 13 .. 19" }, 123 124 // singles have special exceptions 125 { "a: n is 1" }, 126 { "a: n is not 1" }, 127 { "a: n not is 1", ParseException.class }, // hacked to fail 128 { "a: n in 1" }, 129 { "a: n not in 1" }, 130 131 // multiples also have special exceptions 132 // TODO enable the following once there is an update to CLDR 133 // {"a: n is 1,3", ParseException.class}, 134 { "a: n is not 1,3", ParseException.class }, // hacked to fail 135 { "a: n not is 1,3", ParseException.class }, // hacked to fail 136 { "a: n in 1,3" }, 137 { "a: n not in 1,3" }, 138 139 // disallow not with = 140 { "a: n not= 1", ParseException.class }, // hacked to fail 141 { "a: n not= 1,3", ParseException.class }, // hacked to fail 142 143 // disallow double negatives 144 { "a: n ! is not 1", ParseException.class }, 145 { "a: n ! is not 1", ParseException.class }, 146 { "a: n not not in 1", ParseException.class }, 147 { "a: n is not not 1", NumberFormatException.class }, 148 149 // disallow screwy cases 150 { null, NullPointerException.class }, { "djkl;", ParseException.class }, 151 { "a: n = 1 .", ParseException.class }, { "a: n = 1 ..", ParseException.class }, 152 { "a: n = 1 2", ParseException.class }, { "a: n = 1 ,", ParseException.class }, 153 { "a:n in 3 .. 10 , 13 .. 19 ,", ParseException.class }, }; 154 for (Object[] shouldFailTest : shouldFail) { 155 String rules = (String) shouldFailTest[0]; 156 Class exception = shouldFailTest.length < 2 ? null : (Class) shouldFailTest[1]; 157 Class actualException = null; 158 try { 159 PluralRules.parseDescription(rules); 160 } catch (Exception e) { 161 actualException = e.getClass(); 162 } 163 assertEquals("Exception " + rules, exception, actualException); 164 } 165 } 166 167 @Test 168 public void testSamples() { 169 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, "; 170 PluralRules test = PluralRules.createRules(description); 171 172 checkNewSamples(description, test, "one", PluralRules.SampleType.INTEGER, "@integer 3, 19", true, 173 new FixedDecimal(3)); 174 checkNewSamples(description, test, "one", PluralRules.SampleType.DECIMAL, "@decimal 3.50~3.53, ", false, 175 new FixedDecimal(3.5, 2)); 176 checkOldSamples(description, test, "one", SampleType.INTEGER, 3d, 19d); 177 checkOldSamples(description, test, "one", SampleType.DECIMAL, 3.5d, 3.51d, 3.52d, 3.53d); 178 179 checkNewSamples(description, test, "other", PluralRules.SampleType.INTEGER, "", true, null); 180 checkNewSamples(description, test, "other", PluralRules.SampleType.DECIMAL, "@decimal 99.0~99.2, 999.0, ", 181 false, new FixedDecimal(99d, 1)); 182 checkOldSamples(description, test, "other", SampleType.INTEGER); 183 checkOldSamples(description, test, "other", SampleType.DECIMAL, 99d, 99.1, 99.2d, 999d); 184 } 185 186 public void checkOldSamples(String description, PluralRules rules, String keyword, SampleType sampleType, 187 Double... expected) { 188 Collection<Double> oldSamples = rules.getSamples(keyword, sampleType); 189 if (!assertEquals("getOldSamples; " + keyword + "; " + description, new HashSet(Arrays.asList(expected)), 190 oldSamples)) { 191 rules.getSamples(keyword, sampleType); 192 } 193 } 194 195 public void checkNewSamples(String description, PluralRules test, String keyword, SampleType sampleType, 196 String samplesString, boolean isBounded, FixedDecimal firstInRange) { 197 String title = description + ", " + sampleType; 198 FixedDecimalSamples samples = test.getDecimalSamples(keyword, sampleType); 199 if (samples != null) { 200 assertEquals("samples; " + title, samplesString, samples.toString()); 201 assertEquals("bounded; " + title, isBounded, samples.bounded); 202 assertEquals("first; " + title, firstInRange, samples.samples.iterator().next().start); 203 } 204 assertEquals("limited: " + title, isBounded, test.isLimited(keyword, sampleType)); 205 } 206 207 private static final String[] parseTestData = { "a: n is 1", "a:1", "a: n mod 10 is 2", "a:2,12,22", 208 "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", 209 "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", 210 "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", 211 "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", 212 "a: n mod 10 in 2..3 or n mod 10 is 5", "a:2,3,5,12,13,15,22,23,25", 213 "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", 214 "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", 215 "a: n mod 2 is 1 and n is not 3 and n within 1..11", "a:1,5,7,9,11", 216 "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", 217 "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", 218 "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", 219 "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", 220 "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", 221 "a:2,3,5,6,7,13,15,16,17", "a: n in 2..6,3..7", "a:2,3,4,5,6,7", }; 222 223 private String[] getTargetStrings(String targets) { 224 List list = new ArrayList(50); 225 String[] valSets = Utility.split(targets, ';'); 226 for (int i = 0; i < valSets.length; ++i) { 227 String[] temp = Utility.split(valSets[i], ':'); 228 String key = temp[0].trim(); 229 String[] vals = Utility.split(temp[1], ','); 230 for (int j = 0; j < vals.length; ++j) { 231 String valString = vals[j].trim(); 232 int val = Integer.parseInt(valString); 233 while (list.size() <= val) { 234 list.add(null); 235 } 236 if (list.get(val) != null) { 237 fail("test data error, key: " + list.get(val) + " already set for: " + val); 238 } 239 list.set(val, key); 240 } 241 } 242 243 String[] result = (String[]) list.toArray(new String[list.size()]); 244 for (int i = 0; i < result.length; ++i) { 245 if (result[i] == null) { 246 result[i] = "other"; 247 } 248 } 249 return result; 250 } 251 252 private void checkTargets(PluralRules rules, String[] targets) { 253 for (int i = 0; i < targets.length; ++i) { 254 assertEquals("value " + i, targets[i], rules.select(i)); 255 } 256 } 257 258 @Test 259 public void testParseEmpty() throws ParseException { 260 PluralRules rules = PluralRules.parseDescription("a:n"); 261 assertEquals("empty", "a", rules.select(0)); 262 } 263 264 @Test 265 public void testParsing() { 266 for (int i = 0; i < parseTestData.length; i += 2) { 267 String pattern = parseTestData[i]; 268 String expected = parseTestData[i + 1]; 269 270 logln("pattern[" + i + "] " + pattern); 271 try { 272 PluralRules rules = PluralRules.createRules(pattern); 273 String[] targets = getTargetStrings(expected); 274 checkTargets(rules, targets); 275 } catch (Exception e) { 276 e.printStackTrace(); 277 throw new RuntimeException(e.getMessage()); 278 } 279 } 280 } 281 282 private static String[][] operandTestData = { { "a: n 3", "FAIL" }, 283 { "a: n=1,2; b: n != 3..5; c:n!=5", "a:1,2; b:6,7; c:3,4" }, 284 { "a: n=1,2; b: n!=3..5; c:n!=5", "a:1,2; b:6,7; c:3,4" }, 285 { "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" }, 286 { "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" }, 287 { "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 288 // rules 289 { "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 290 // visible 291 // digit 292 { "one: j is 0", "one: 0; other: 0.0, 1.0, 3" }, // Last visible digit 293 // one n is 1; few n in 2..4; 294 }; 295 296 @Test 297 public void testOperands() { 298 for (String[] pair : operandTestData) { 299 String pattern = pair[0].trim(); 300 String categoriesAndExpected = pair[1].trim(); 301 302 // logln("pattern[" + i + "] " + pattern); 303 boolean FAIL_EXPECTED = categoriesAndExpected.equalsIgnoreCase("fail"); 304 try { 305 logln(pattern); 306 PluralRules rules = PluralRules.createRules(pattern); 307 if (FAIL_EXPECTED) { 308 assertNull("Should fail with 'null' return.", rules); 309 } else { 310 logln(rules == null ? "null rules" : rules.toString()); 311 checkCategoriesAndExpected(pattern, categoriesAndExpected, rules); 312 } 313 } catch (Exception e) { 314 if (!FAIL_EXPECTED) { 315 e.printStackTrace(); 316 throw new RuntimeException(e.getMessage()); 317 } 318 } 319 } 320 } 321 322 @Test 323 public void testUniqueRules() { 324 main: for (ULocale locale : factory.getAvailableULocales()) { 325 PluralRules rules = factory.forLocale(locale); 326 Map<String, PluralRules> keywordToRule = new HashMap<String, PluralRules>(); 327 Collection<FixedDecimalSamples> samples = new LinkedHashSet<FixedDecimalSamples>(); 328 329 for (String keyword : rules.getKeywords()) { 330 for (SampleType sampleType : SampleType.values()) { 331 FixedDecimalSamples samples2 = rules.getDecimalSamples(keyword, sampleType); 332 if (samples2 != null) { 333 samples.add(samples2); 334 } 335 } 336 if (keyword.equals("other")) { 337 continue; 338 } 339 String rules2 = keyword + ":" + rules.getRules(keyword); 340 PluralRules singleRule = PluralRules.createRules(rules2); 341 if (singleRule == null) { 342 errln("Can't generate single rule for " + rules2); 343 PluralRules.createRules(rules2); // for debugging 344 continue main; 345 } 346 keywordToRule.put(keyword, singleRule); 347 } 348 Map<FixedDecimal, String> collisionTest = new TreeMap(); 349 for (FixedDecimalSamples sample3 : samples) { 350 Set<FixedDecimalRange> samples2 = sample3.getSamples(); 351 if (samples2 == null) { 352 continue; 353 } 354 for (FixedDecimalRange sample : samples2) { 355 for (int i = 0; i < 1; ++i) { 356 FixedDecimal item = i == 0 ? sample.start : sample.end; 357 collisionTest.clear(); 358 for (Entry<String, PluralRules> entry : keywordToRule.entrySet()) { 359 PluralRules rule = entry.getValue(); 360 String foundKeyword = rule.select(item); 361 if (foundKeyword.equals("other")) { 362 continue; 363 } 364 String old = collisionTest.get(item); 365 if (old != null) { 366 errln(locale + "\tNon-unique rules: " + item + " => " + old + " & " + foundKeyword); 367 rule.select(item); 368 } else { 369 collisionTest.put(item, foundKeyword); 370 } 371 } 372 } 373 } 374 } 375 } 376 } 377 378 private void checkCategoriesAndExpected(String title1, String categoriesAndExpected, PluralRules rules) { 379 for (String categoryAndExpected : categoriesAndExpected.split("\\s*;\\s*")) { 380 String[] categoryFromExpected = categoryAndExpected.split("\\s*:\\s*"); 381 String expected = categoryFromExpected[0]; 382 for (String value : categoryFromExpected[1].split("\\s*,\\s*")) { 383 if (value.startsWith("@") || value.equals("") || value.equals("null")) { 384 continue; 385 } 386 String[] values = value.split("\\s*~\\s*"); 387 checkValue(title1, rules, expected, values[0]); 388 if (values.length > 1) { 389 checkValue(title1, rules, expected, values[1]); 390 } 391 } 392 } 393 } 394 395 public void checkValue(String title1, PluralRules rules, String expected, String value) { 396 double number = Double.parseDouble(value); 397 int decimalPos = value.indexOf('.') + 1; 398 int countVisibleFractionDigits; 399 int fractionaldigits; 400 if (decimalPos == 0) { 401 countVisibleFractionDigits = fractionaldigits = 0; 402 } else { 403 countVisibleFractionDigits = value.length() - decimalPos; 404 fractionaldigits = Integer.parseInt(value.substring(decimalPos)); 405 } 406 String result = rules.select(number, countVisibleFractionDigits, fractionaldigits); 407 ULocale locale = null; 408 assertEquals(getAssertMessage(title1, locale, rules, expected) + "; value: " + value, expected, result); 409 } 410 411 private static String[][] equalityTestData = { 412 // once we add fractions, we had to retract the "test all possibilities" for equality, 413 // so we only have a limited set of equality tests now. 414 { "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" }, 415 { "b:n not in 5;", "b: n != 5" }, 416 417 // { "a: n is 5", 418 // "a: n in 2..6 and n not in 2..4 and n is not 6" }, 419 // { "a: n in 2..3", 420 // "a: n is 2 or n is 3", 421 // "a: n is 3 and n in 2..5 or n is 2" }, 422 // { "a: n is 12; b:n mod 10 in 2..3", 423 // "b: n mod 10 in 2..3 and n is not 12; a: n in 12..12", 424 // "b: n is 13; a: n is 12; b: n mod 10 is 2 or n mod 10 is 3" }, 425 }; 426 427 private static String[][] inequalityTestData = { { "a: n mod 8 is 3", "a: n mod 7 is 3" }, 428 { "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" }, 429 // the following are currently inequal, but we may make them equal in the future. 430 { "a: n in 2..5", "a: n in 2..4,5" }, }; 431 432 private void compareEquality(String id, Object[] objects, boolean shouldBeEqual) { 433 for (int i = 0; i < objects.length; ++i) { 434 Object lhs = objects[i]; 435 int start = shouldBeEqual ? i : i + 1; 436 for (int j = start; j < objects.length; ++j) { 437 Object rhs = objects[j]; 438 if (rhs == null || shouldBeEqual != lhs.equals(rhs)) { 439 String msg = shouldBeEqual ? "should be equal" : "should not be equal"; 440 fail(id + " " + msg + " (" + i + ", " + j + "):\n " + lhs + "\n " + rhs); 441 } 442 // assertEquals("obj " + i + " and " + j, lhs, rhs); 443 } 444 } 445 } 446 447 private void compareEqualityTestSets(String[][] sets, boolean shouldBeEqual) { 448 for (int i = 0; i < sets.length; ++i) { 449 String[] patterns = sets[i]; 450 PluralRules[] rules = new PluralRules[patterns.length]; 451 for (int j = 0; j < patterns.length; ++j) { 452 rules[j] = PluralRules.createRules(patterns[j]); 453 } 454 compareEquality("test " + i, rules, shouldBeEqual); 455 } 456 } 457 458 @Test 459 public void testEquality() { 460 compareEqualityTestSets(equalityTestData, true); 461 } 462 463 @Test 464 public void testInequality() { 465 compareEqualityTestSets(inequalityTestData, false); 466 } 467 468 @Test 469 public void testBuiltInRules() { 470 // spot check 471 PluralRules rules = factory.forLocale(ULocale.US); 472 assertEquals("us 0", PluralRules.KEYWORD_OTHER, rules.select(0)); 473 assertEquals("us 1", PluralRules.KEYWORD_ONE, rules.select(1)); 474 assertEquals("us 2", PluralRules.KEYWORD_OTHER, rules.select(2)); 475 476 rules = factory.forLocale(ULocale.JAPAN); 477 assertEquals("ja 0", PluralRules.KEYWORD_OTHER, rules.select(0)); 478 assertEquals("ja 1", PluralRules.KEYWORD_OTHER, rules.select(1)); 479 assertEquals("ja 2", PluralRules.KEYWORD_OTHER, rules.select(2)); 480 481 rules = factory.forLocale(ULocale.createCanonical("ru")); 482 assertEquals("ru 0", PluralRules.KEYWORD_MANY, rules.select(0)); 483 assertEquals("ru 1", PluralRules.KEYWORD_ONE, rules.select(1)); 484 assertEquals("ru 2", PluralRules.KEYWORD_FEW, rules.select(2)); 485 } 486 487 @Test 488 public void testFunctionalEquivalent() { 489 // spot check 490 ULocale unknown = ULocale.createCanonical("zz_ZZ"); 491 ULocale un_equiv = PluralRules.getFunctionalEquivalent(unknown, null); 492 assertEquals("unknown locales have root", ULocale.ROOT, un_equiv); 493 494 ULocale jp_equiv = PluralRules.getFunctionalEquivalent(ULocale.JAPAN, null); 495 ULocale cn_equiv = PluralRules.getFunctionalEquivalent(ULocale.CHINA, null); 496 assertEquals("japan and china equivalent locales", jp_equiv, cn_equiv); 497 498 boolean[] available = new boolean[1]; 499 ULocale russia = ULocale.createCanonical("ru_RU"); 500 ULocale ru_ru_equiv = PluralRules.getFunctionalEquivalent(russia, available); 501 assertFalse("ru_RU not listed", available[0]); 502 503 ULocale russian = ULocale.createCanonical("ru"); 504 ULocale ru_equiv = PluralRules.getFunctionalEquivalent(russian, available); 505 assertTrue("ru listed", available[0]); 506 assertEquals("ru and ru_RU equivalent locales", ru_ru_equiv, ru_equiv); 507 } 508 509 @Test 510 public void testAvailableULocales() { 511 ULocale[] locales = factory.getAvailableULocales(); 512 Set localeSet = new HashSet(); 513 localeSet.addAll(Arrays.asList(locales)); 514 515 assertEquals("locales are unique in list", locales.length, localeSet.size()); 516 } 517 518 /* 519 * Test the method public static PluralRules parseDescription(String description) 520 */ 521 @Test 522 public void TestParseDescription() { 523 try { 524 if (PluralRules.DEFAULT != PluralRules.parseDescription("")) { 525 errln("PluralRules.parseDescription(String) was suppose " 526 + "to return PluralRules.DEFAULT when String is of " + "length 0."); 527 } 528 } catch (ParseException e) { 529 errln("PluralRules.parseDescription(String) was not suppose " + "to return an exception."); 530 } 531 } 532 533 /* 534 * Tests the method public static PluralRules createRules(String description) 535 */ 536 @Test 537 public void TestCreateRules() { 538 try { 539 if (PluralRules.createRules(null) != null) { 540 errln("PluralRules.createRules(String) was suppose to " 541 + "return null for an invalid String descrtiption."); 542 } 543 } catch (Exception e) { 544 } 545 } 546 547 /* 548 * Tests the method public int hashCode() 549 */ 550 @Test 551 public void TestHashCode() { 552 // Bad test, breaks whenever PluralRules implementation changes. 553 // PluralRules pr = PluralRules.DEFAULT; 554 // if (106069776 != pr.hashCode()) { 555 // errln("PluralRules.hashCode() was suppose to return 106069776 " + "when PluralRules.DEFAULT."); 556 // } 557 } 558 559 /* 560 * Tests the method public boolean equals(PluralRules rhs) 561 */ 562 @Test 563 public void TestEquals() { 564 PluralRules pr = PluralRules.DEFAULT; 565 566 if (pr.equals((PluralRules) null)) { 567 errln("PluralRules.equals(PluralRules) was supposed to return false " + "when passing null."); 568 } 569 } 570 571 private void assertRuleValue(String rule, double value) { 572 assertRuleKeyValue("a:" + rule, "a", value); 573 } 574 575 private void assertRuleKeyValue(String rule, String key, double value) { 576 PluralRules pr = PluralRules.createRules(rule); 577 assertEquals(rule, value, pr.getUniqueKeywordValue(key)); 578 } 579 580 /* 581 * Tests getUniqueKeywordValue() 582 */ 583 @Test 584 public void TestGetUniqueKeywordValue() { 585 assertRuleKeyValue("a: n is 1", "not_defined", PluralRules.NO_UNIQUE_VALUE); // key not defined 586 assertRuleValue("n within 2..2", 2); 587 assertRuleValue("n is 1", 1); 588 assertRuleValue("n in 2..2", 2); 589 assertRuleValue("n in 3..4", PluralRules.NO_UNIQUE_VALUE); 590 assertRuleValue("n within 3..4", PluralRules.NO_UNIQUE_VALUE); 591 assertRuleValue("n is 2 or n is 2", 2); 592 assertRuleValue("n is 2 and n is 2", 2); 593 assertRuleValue("n is 2 or n is 3", PluralRules.NO_UNIQUE_VALUE); 594 assertRuleValue("n is 2 and n is 3", PluralRules.NO_UNIQUE_VALUE); 595 assertRuleValue("n is 2 or n in 2..3", PluralRules.NO_UNIQUE_VALUE); 596 assertRuleValue("n is 2 and n in 2..3", 2); 597 assertRuleKeyValue("a: n is 1", "other", PluralRules.NO_UNIQUE_VALUE); // key matches default rule 598 assertRuleValue("n in 2,3", PluralRules.NO_UNIQUE_VALUE); 599 assertRuleValue("n in 2,3..6 and n not in 2..3,5..6", 4); 600 } 601 602 /** 603 * The version in PluralFormatUnitTest is not really a test, and it's in the wrong place anyway, so I'm putting a 604 * variant of it here. 605 */ 606 @Test 607 public void TestGetSamples() { 608 Set<ULocale> uniqueRuleSet = new HashSet<ULocale>(); 609 for (ULocale locale : factory.getAvailableULocales()) { 610 uniqueRuleSet.add(PluralRules.getFunctionalEquivalent(locale, null)); 611 } 612 for (ULocale locale : uniqueRuleSet) { 613 PluralRules rules = factory.forLocale(locale); 614 logln("\nlocale: " + (locale == ULocale.ROOT ? "root" : locale.toString()) + ", rules: " + rules); 615 Set<String> keywords = rules.getKeywords(); 616 for (String keyword : keywords) { 617 Collection<Double> list = rules.getSamples(keyword); 618 logln("keyword: " + keyword + ", samples: " + list); 619 // with fractions, the samples can be empty and thus the list null. In that case, however, there will be 620 // FixedDecimal values. 621 // So patch the test for that. 622 if (list.size() == 0) { 623 // when the samples (meaning integer samples) are null, then then integerSamples must be, and the 624 // decimalSamples must not be 625 FixedDecimalSamples integerSamples = rules.getDecimalSamples(keyword, SampleType.INTEGER); 626 FixedDecimalSamples decimalSamples = rules.getDecimalSamples(keyword, SampleType.DECIMAL); 627 assertTrue(getAssertMessage("List is not null", locale, rules, keyword), integerSamples == null 628 && decimalSamples != null && decimalSamples.samples.size() != 0); 629 } else { 630 if (!assertTrue(getAssertMessage("Test getSamples.isEmpty", locale, rules, keyword), 631 !list.isEmpty())) { 632 rules.getSamples(keyword); 633 } 634 if (rules.toString().contains(": j")) { 635 // hack until we remove j 636 } else { 637 for (double value : list) { 638 assertEquals(getAssertMessage("Match keyword", locale, rules, keyword) + "; value '" 639 + value + "'", keyword, rules.select(value)); 640 } 641 } 642 } 643 } 644 645 assertNull(locale + ", list is null", rules.getSamples("@#$%^&*")); 646 assertNull(locale + ", list is null", rules.getSamples("@#$%^&*", SampleType.DECIMAL)); 647 } 648 } 649 650 public String getAssertMessage(String message, ULocale locale, PluralRules rules, String keyword) { 651 String ruleString = ""; 652 if (keyword != null) { 653 if (keyword.equals("other")) { 654 for (String keyword2 : rules.getKeywords()) { 655 ruleString += " NOR " + rules.getRules(keyword2).split("@")[0]; 656 } 657 } else { 658 String rule = rules.getRules(keyword); 659 ruleString = rule == null ? null : rule.split("@")[0]; 660 } 661 ruleString = "; rule: '" + keyword + ": " + ruleString + "'"; 662 // !keyword.equals("other") ? "'; keyword: '" + keyword + "'; rule: '" + rules.getRules(keyword) + "'" 663 // : "'; keyword: '" + keyword + "'; rules: '" + rules.toString() + "'"; 664 } 665 return message + (locale == null ? "" : "; locale: '" + locale + "'") + ruleString; 666 } 667 668 /** 669 * Returns the empty set if the keyword is not defined, null if there are an unlimited number of values for the 670 * keyword, or the set of values that trigger the keyword. 671 */ 672 @Test 673 public void TestGetAllKeywordValues() { 674 // data is pairs of strings, the rule, and the expected values as arguments 675 String[] data = { 676 "other: ; a: n mod 3 is 0", 677 "a: null", 678 "a: n in 2..5 and n within 5..8", 679 "a: 5", 680 "a: n in 2..5", 681 "a: 2,3,4,5; other: null", 682 "a: n not in 2..5", 683 "a: null; other: null", 684 "a: n within 2..5", 685 "a: 2,3,4,5; other: null", 686 "a: n not within 2..5", 687 "a: null; other: null", 688 "a: n in 2..5 or n within 6..8", 689 "a: 2,3,4,5,6,7,8", // ignore 'other' here on out, always null 690 "a: n in 2..5 and n within 6..8", 691 "a: null", 692 // we no longer support 'degenerate' rules 693 // "a: n within 2..5 and n within 6..8", "a:", // our sampling catches these 694 // "a: n within 2..5 and n within 5..8", "a: 5", // '' 695 // "a: n within 1..2 and n within 2..3 or n within 3..4 and n within 4..5", "a: 2,4", 696 // "a: n mod 3 is 0 and n within 0..5", "a: 0,3", 697 "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", 698 "a: 2,4,6", // but not this... 699 "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", 700 "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", }; 701 for (int i = 0; i < data.length; i += 2) { 702 String ruleDescription = data[i]; 703 String result = data[i + 1]; 704 705 PluralRules p = PluralRules.createRules(ruleDescription); 706 if (p == null) { // for debugging 707 PluralRules.createRules(ruleDescription); 708 } 709 for (String ruleResult : result.split(";")) { 710 String[] ruleAndValues = ruleResult.split(":"); 711 String keyword = ruleAndValues[0].trim(); 712 String valueList = ruleAndValues.length < 2 ? null : ruleAndValues[1]; 713 if (valueList != null) { 714 valueList = valueList.trim(); 715 } 716 Collection<Double> values; 717 if (valueList == null || valueList.length() == 0) { 718 values = Collections.EMPTY_SET; 719 } else if ("null".equals(valueList)) { 720 values = null; 721 } else { 722 values = new TreeSet<Double>(); 723 for (String value : valueList.split(",")) { 724 values.add(Double.parseDouble(value)); 725 } 726 } 727 728 Collection<Double> results = p.getAllKeywordValues(keyword); 729 assertEquals(keyword + " in " + ruleDescription, values, results == null ? null : new HashSet(results)); 730 731 if (results != null) { 732 try { 733 results.add(PluralRules.NO_UNIQUE_VALUE); 734 fail("returned set is modifiable"); 735 } catch (UnsupportedOperationException e) { 736 // pass 737 } 738 } 739 } 740 } 741 } 742 743 @Test 744 public void TestOrdinal() { 745 PluralRules pr = factory.forLocale(ULocale.ENGLISH, PluralType.ORDINAL); 746 assertEquals("PluralRules(en-ordinal).select(2)", "two", pr.select(2)); 747 } 748 749 @Test 750 public void TestBasicFraction() { 751 String[][] tests = { { "en", "one: j is 1" }, { "1", "0", "1", "one" }, { "1", "2", "1.00", "other" }, }; 752 ULocale locale = null; 753 NumberFormat nf = null; 754 PluralRules pr = null; 755 756 for (String[] row : tests) { 757 switch (row.length) { 758 case 2: 759 locale = ULocale.forLanguageTag(row[0]); 760 nf = NumberFormat.getInstance(locale); 761 pr = PluralRules.createRules(row[1]); 762 break; 763 case 4: 764 double n = Double.parseDouble(row[0]); 765 int minFracDigits = Integer.parseInt(row[1]); 766 nf.setMinimumFractionDigits(minFracDigits); 767 String expectedFormat = row[2]; 768 String expectedKeyword = row[3]; 769 770 UFieldPosition pos = new UFieldPosition(); 771 String formatted = nf.format(1.0, new StringBuffer(), pos).toString(); 772 int countVisibleFractionDigits = pos.getCountVisibleFractionDigits(); 773 long fractionDigits = pos.getFractionDigits(); 774 String keyword = pr.select(n, countVisibleFractionDigits, fractionDigits); 775 assertEquals("Formatted " + n + "\t" + minFracDigits, expectedFormat, formatted); 776 assertEquals("Keyword " + n + "\t" + minFracDigits, expectedKeyword, keyword); 777 break; 778 default: 779 throw new RuntimeException(); 780 } 781 } 782 } 783 784 @Test 785 public void TestLimitedAndSamplesConsistency() { 786 for (ULocale locale : PluralRules.getAvailableULocales()) { 787 ULocale loc2 = PluralRules.getFunctionalEquivalent(locale, null); 788 if (!loc2.equals(locale)) { 789 continue; // only need "unique" rules 790 } 791 for (PluralType type : PluralType.values()) { 792 PluralRules rules = PluralRules.forLocale(locale, type); 793 for (SampleType sampleType : SampleType.values()) { 794 if (type == PluralType.ORDINAL) { 795 logKnownIssue("10783", "Fix issues with isLimited vs computeLimited on ordinals"); 796 continue; 797 } 798 for (String keyword : rules.getKeywords()) { 799 boolean isLimited = rules.isLimited(keyword, sampleType); 800 boolean computeLimited = rules.computeLimited(keyword, sampleType); 801 if (!keyword.equals("other")) { 802 assertEquals(getAssertMessage("computeLimited == isLimited", locale, rules, keyword), 803 computeLimited, isLimited); 804 } 805 Collection<Double> samples = rules.getSamples(keyword, sampleType); 806 assertNotNull(getAssertMessage("Samples must not be null", locale, rules, keyword), samples); 807 /* FixedDecimalSamples decimalSamples = */rules.getDecimalSamples(keyword, sampleType); 808 // assertNotNull(getAssertMessage("Decimal samples must be null if unlimited", locale, rules, 809 // keyword), decimalSamples); 810 } 811 } 812 } 813 } 814 } 815 816 @Test 817 public void TestKeywords() { 818 Set<String> possibleKeywords = new LinkedHashSet(Arrays.asList("zero", "one", "two", "few", "many", "other")); 819 Object[][][] tests = { 820 // format is locale, explicits, then triples of keyword, status, unique value. 821 { { "en", null }, { "one", KeywordStatus.UNIQUE, 1.0d }, { "other", KeywordStatus.UNBOUNDED, null } }, 822 { { "pl", null }, { "one", KeywordStatus.UNIQUE, 1.0d }, { "few", KeywordStatus.UNBOUNDED, null }, 823 { "many", KeywordStatus.UNBOUNDED, null }, 824 { "other", KeywordStatus.SUPPRESSED, null, KeywordStatus.UNBOUNDED, null } // note that it is 825 // suppressed in 826 // INTEGER but not 827 // DECIMAL 828 }, { { "en", new HashSet<Double>(Arrays.asList(1.0d)) }, // check that 1 is suppressed 829 { "one", KeywordStatus.SUPPRESSED, null }, { "other", KeywordStatus.UNBOUNDED, null } }, }; 830 Output<Double> uniqueValue = new Output<Double>(); 831 for (Object[][] test : tests) { 832 ULocale locale = new ULocale((String) test[0][0]); 833 // NumberType numberType = (NumberType) test[1]; 834 Set<Double> explicits = (Set<Double>) test[0][1]; 835 PluralRules pluralRules = factory.forLocale(locale); 836 LinkedHashSet<String> remaining = new LinkedHashSet(possibleKeywords); 837 for (int i = 1; i < test.length; ++i) { 838 Object[] row = test[i]; 839 String keyword = (String) row[0]; 840 KeywordStatus statusExpected = (KeywordStatus) row[1]; 841 Double uniqueExpected = (Double) row[2]; 842 remaining.remove(keyword); 843 KeywordStatus status = pluralRules.getKeywordStatus(keyword, 0, explicits, uniqueValue); 844 assertEquals(getAssertMessage("Unique Value", locale, pluralRules, keyword), uniqueExpected, 845 uniqueValue.value); 846 assertEquals(getAssertMessage("Keyword Status", locale, pluralRules, keyword), statusExpected, status); 847 if (row.length > 3) { 848 statusExpected = (KeywordStatus) row[3]; 849 uniqueExpected = (Double) row[4]; 850 status = pluralRules.getKeywordStatus(keyword, 0, explicits, uniqueValue, SampleType.DECIMAL); 851 assertEquals(getAssertMessage("Unique Value - decimal", locale, pluralRules, keyword), 852 uniqueExpected, uniqueValue.value); 853 assertEquals(getAssertMessage("Keyword Status - decimal", locale, pluralRules, keyword), 854 statusExpected, status); 855 } 856 } 857 for (String keyword : remaining) { 858 KeywordStatus status = pluralRules.getKeywordStatus(keyword, 0, null, uniqueValue); 859 assertEquals("Invalid keyword " + keyword, status, KeywordStatus.INVALID); 860 assertNull("Invalid keyword " + keyword, uniqueValue.value); 861 } 862 } 863 } 864 865 enum StandardPluralCategories { 866 zero, one, two, few, many, other; 867 /** 868 * 869 */ 870 private static final Set<StandardPluralCategories> ALL = Collections.unmodifiableSet(EnumSet 871 .allOf(StandardPluralCategories.class)); 872 873 /** 874 * Return a mutable set 875 * 876 * @param source 877 * @return 878 */ 879 static final EnumSet<StandardPluralCategories> getSet(Collection<String> source) { 880 EnumSet<StandardPluralCategories> result = EnumSet.noneOf(StandardPluralCategories.class); 881 for (String s : source) { 882 result.add(StandardPluralCategories.valueOf(s)); 883 } 884 return result; 885 } 886 887 static final Comparator<Set<StandardPluralCategories>> SHORTEST_FIRST = new Comparator<Set<StandardPluralCategories>>() { 888 @Override 889 public int compare(Set<StandardPluralCategories> arg0, Set<StandardPluralCategories> arg1) { 890 int diff = arg0.size() - arg1.size(); 891 if (diff != 0) { 892 return diff; 893 } 894 // otherwise first... 895 // could be optimized, but we don't care here. 896 for (StandardPluralCategories value : ALL) { 897 if (arg0.contains(value)) { 898 if (!arg1.contains(value)) { 899 return 1; 900 } 901 } else if (arg1.contains(value)) { 902 return -1; 903 } 904 905 } 906 return 0; 907 } 908 909 }; 910 } 911 912 @Test 913 public void TestLocales() { 914 if (false) { 915 generateLOCALE_SNAPSHOT(); 916 } 917 for (String test : LOCALE_SNAPSHOT) { 918 test = test.trim(); 919 String[] parts = test.split("\\s*;\\s*"); 920 for (String localeString : parts[0].split("\\s*,\\s*")) { 921 ULocale locale = new ULocale(localeString); 922 if (factory.hasOverride(locale)) { 923 continue; // skip for now 924 } 925 PluralRules rules = factory.forLocale(locale); 926 for (int i = 1; i < parts.length; ++i) { 927 checkCategoriesAndExpected(localeString, parts[i], rules); 928 } 929 } 930 } 931 } 932 933 private static final Comparator<PluralRules> PLURAL_RULE_COMPARATOR = new Comparator<PluralRules>() { 934 @Override 935 public int compare(PluralRules o1, PluralRules o2) { 936 return o1.compareTo(o2); 937 } 938 }; 939 940 private void generateLOCALE_SNAPSHOT() { 941 Comparator c = new CollectionUtilities.CollectionComparator<Comparable>(); 942 Relation<Set<StandardPluralCategories>, PluralRules> setsToRules = Relation.of( 943 new TreeMap<Set<StandardPluralCategories>, Set<PluralRules>>(c), TreeSet.class, PLURAL_RULE_COMPARATOR); 944 Relation<PluralRules, ULocale> data = Relation.of( 945 new TreeMap<PluralRules, Set<ULocale>>(PLURAL_RULE_COMPARATOR), TreeSet.class); 946 for (ULocale locale : PluralRules.getAvailableULocales()) { 947 PluralRules pr = PluralRules.forLocale(locale); 948 EnumSet<StandardPluralCategories> set = getCanonicalSet(pr.getKeywords()); 949 setsToRules.put(set, pr); 950 data.put(pr, locale); 951 } 952 for (Entry<Set<StandardPluralCategories>, Set<PluralRules>> entry1 : setsToRules.keyValuesSet()) { 953 Set<StandardPluralCategories> set = entry1.getKey(); 954 Set<PluralRules> rules = entry1.getValue(); 955 System.out.println("\n // " + set); 956 for (PluralRules rule : rules) { 957 Set<ULocale> locales = data.get(rule); 958 System.out.print(" \"" + CollectionUtilities.join(locales, ",")); 959 for (StandardPluralCategories spc : set) { 960 String keyword = spc.toString(); 961 FixedDecimalSamples samples = rule.getDecimalSamples(keyword, SampleType.INTEGER); 962 System.out.print("; " + spc + ": " + samples); 963 } 964 System.out.println("\","); 965 } 966 } 967 } 968 969 /** 970 * @param keywords 971 * @return 972 */ 973 private EnumSet<StandardPluralCategories> getCanonicalSet(Set<String> keywords) { 974 EnumSet<StandardPluralCategories> result = EnumSet.noneOf(StandardPluralCategories.class); 975 for (String s : keywords) { 976 result.add(StandardPluralCategories.valueOf(s)); 977 } 978 return result; 979 } 980 981 static final String[] LOCALE_SNAPSHOT = { 982 // [other] 983 "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, ", 984 985 // [one, other] 986 "am,bn,fa,gu,hi,kn,mr,zu; one: @integer 0, 1; other: @integer 2~17, 100, 1000, 10000, 100000, 1000000, ", 987 "ff,fr,hy,kab; one: @integer 0, 1; other: @integer 2~17, 100, 1000, 10000, 100000, 1000000, ", 988 "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, ", 989 "pt; one: @integer 1; other: @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, ", 990 "si; one: @integer 0, 1; other: @integer 2~17, 100, 1000, 10000, 100000, 1000000, ", 991 "ak,bh,guw,ln,mg,nso,pa,ti,wa; one: @integer 0, 1; other: @integer 2~17, 100, 1000, 10000, 100000, 1000000, ", 992 "tzm; one: @integer 0, 1, 11~24; other: @integer 2~10, 100~106, 1000, 10000, 100000, 1000000, ", 993 "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, ", 994 "pt_PT; one: @integer 1; other: @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, ", 995 "da; one: @integer 1; other: @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, ", 996 "is; one: @integer 1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, ; other: @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, ", 997 "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, ", 998 "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, ", 999 1000 // [zero, one, other] 1001 "lag; zero: @integer 0; one: @integer 1; other: @integer 2~17, 100, 1000, 10000, 100000, 1000000, ", 1002 "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, ", 1003 "ksh; zero: @integer 0; one: @integer 1; other: @integer 2~17, 100, 1000, 10000, 100000, 1000000, ", 1004 1005 // [one, two, other] 1006 "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, ", 1007 1008 // [one, few, other] 1009 "shi; one: @integer 0, 1; few: @integer 2~10; other: @integer 11~26, 100, 1000, 10000, 100000, 1000000, ", 1010 "mo,ro; one: @integer 1; few: @integer 0, 2~16, 101, 1001, ; other: @integer 20~35, 100, 1000, 10000, 100000, 1000000, ", 1011 "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, ", 1012 1013 // [one, two, few, other] 1014 "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, ", 1015 "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, ", 1016 1017 // [one, two, many, other] 1018 "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, ", 1019 1020 // [one, few, many, other] 1021 "cs,sk; one: @integer 1; few: @integer 2~4; many: null; other: @integer 0, 5~19, 100, 1000, 10000, 100000, 1000000, ", 1022 "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", 1023 "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, ", 1024 "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, ", 1025 "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", 1026 "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", 1027 1028 // [one, two, few, many, other] 1029 "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, ", 1030 "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, ", 1031 "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, ", 1032 1033 // [zero, one, two, few, many, other] 1034 "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, ", 1035 "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, ", }; 1036 1037 private <T extends Serializable> T serializeAndDeserialize(T original, Output<Integer> size) { 1038 try { 1039 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 1040 ObjectOutputStream ostream = new ObjectOutputStream(baos); 1041 ostream.writeObject(original); 1042 ostream.flush(); 1043 byte bytes[] = baos.toByteArray(); 1044 size.value = bytes.length; 1045 ObjectInputStream istream = new ObjectInputStream(new ByteArrayInputStream(bytes)); 1046 T reconstituted = (T) istream.readObject(); 1047 return reconstituted; 1048 } catch (IOException e) { 1049 throw new RuntimeException(e); 1050 } catch (ClassNotFoundException e) { 1051 throw new RuntimeException(e); 1052 } 1053 } 1054 1055 @Test 1056 public void TestSerialization() { 1057 Output<Integer> size = new Output<Integer>(); 1058 int max = 0; 1059 for (ULocale locale : PluralRules.getAvailableULocales()) { 1060 PluralRules item = PluralRules.forLocale(locale); 1061 PluralRules item2 = serializeAndDeserialize(item, size); 1062 logln(locale + "\tsize:\t" + size.value); 1063 max = Math.max(max, size.value); 1064 if (!assertEquals(locale + "\tPlural rules before and after serialization", item, item2)) { 1065 // for debugging 1066 PluralRules item3 = serializeAndDeserialize(item, size); 1067 item.equals(item3); 1068 } 1069 } 1070 logln("max \tsize:\t" + max); 1071 } 1072 1073 public static class FixedDecimalHandler implements SerializableTestUtility.Handler { 1074 @Override 1075 public Object[] getTestObjects() { 1076 FixedDecimal items[] = { new FixedDecimal(3d), new FixedDecimal(3d, 2), new FixedDecimal(3.1d, 1), 1077 new FixedDecimal(3.1d, 2), }; 1078 return items; 1079 } 1080 1081 @Override 1082 public boolean hasSameBehavior(Object a, Object b) { 1083 FixedDecimal a1 = (FixedDecimal) a; 1084 FixedDecimal b1 = (FixedDecimal) b; 1085 return a1.equals(b1); 1086 } 1087 } 1088 1089 @Test 1090 public void TestSerial() { 1091 PluralRules s = PluralRules.forLocale(ULocale.ENGLISH); 1092 checkStreamingEquality(s); 1093 } 1094 1095 public void checkStreamingEquality(PluralRules s) { 1096 try { 1097 ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); 1098 ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteOut); 1099 objectOutputStream.writeObject(s); 1100 objectOutputStream.close(); 1101 byte[] contents = byteOut.toByteArray(); 1102 logln(s.getClass() + ": " + showBytes(contents)); 1103 ByteArrayInputStream byteIn = new ByteArrayInputStream(contents); 1104 ObjectInputStream objectInputStream = new ObjectInputStream(byteIn); 1105 Object obj = objectInputStream.readObject(); 1106 assertEquals("Streamed Object equals ", s, obj); 1107 } catch (Exception e) { 1108 assertNull("TestSerial", e); 1109 } 1110 } 1111 1112 /** 1113 * @param contents 1114 * @return 1115 */ 1116 private String showBytes(byte[] contents) { 1117 StringBuilder b = new StringBuilder('['); 1118 for (int i = 0; i < contents.length; ++i) { 1119 int item = contents[i] & 0xFF; 1120 if (item >= 0x20 && item <= 0x7F) { 1121 b.append((char) item); 1122 } else { 1123 b.append('(').append(Utility.hex(item, 2)).append(')'); 1124 } 1125 } 1126 return b.append(']').toString(); 1127 } 1128 1129 @Test 1130 public void testJavaLocaleFactory() { 1131 PluralRules rulesU0 = PluralRules.forLocale(ULocale.FRANCE); 1132 PluralRules rulesJ0 = PluralRules.forLocale(Locale.FRANCE); 1133 assertEquals("forLocale()", rulesU0, rulesJ0); 1134 1135 PluralRules rulesU1 = PluralRules.forLocale(ULocale.FRANCE, PluralType.ORDINAL); 1136 PluralRules rulesJ1 = PluralRules.forLocale(Locale.FRANCE, PluralType.ORDINAL); 1137 assertEquals("forLocale() with type", rulesU1, rulesJ1); 1138 } 1139 } 1140