ICU-8474 updates to fix new rules, separate out test generation class, factory class, change toString() to roundtrip, etc.

X-SVN-Rev: 33499
This commit is contained in:
Mark Davis 2013-04-08 14:11:49 +00:00
parent d8a9e311a3
commit 74c81065f6
7 changed files with 1593 additions and 166 deletions

View File

@ -20,6 +20,7 @@ import com.ibm.icu.impl.SimpleCache;
import com.ibm.icu.text.DateIntervalInfo.PatternInfo;
import com.ibm.icu.util.Calendar;
import com.ibm.icu.util.DateInterval;
import com.ibm.icu.util.Output;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.ULocale.Category;
@ -603,7 +604,43 @@ public class DateIntervalFormat extends UFormat {
return format(fFromCalendar, fToCalendar, appendTo, fieldPosition);
}
/**
* @internal
* @deprecated This API is ICU internal only.
*/
public String getPatterns(Calendar fromCalendar,
Calendar toCalendar,
Output<String> part2) {
// First, find the largest different calendar field.
int field;
if ( fromCalendar.get(Calendar.ERA) != toCalendar.get(Calendar.ERA) ) {
field = Calendar.ERA;
} else if ( fromCalendar.get(Calendar.YEAR) !=
toCalendar.get(Calendar.YEAR) ) {
field = Calendar.YEAR;
} else if ( fromCalendar.get(Calendar.MONTH) !=
toCalendar.get(Calendar.MONTH) ) {
field = Calendar.MONTH;
} else if ( fromCalendar.get(Calendar.DATE) !=
toCalendar.get(Calendar.DATE) ) {
field = Calendar.DATE;
} else if ( fromCalendar.get(Calendar.AM_PM) !=
toCalendar.get(Calendar.AM_PM) ) {
field = Calendar.AM_PM;
} else if ( fromCalendar.get(Calendar.HOUR) !=
toCalendar.get(Calendar.HOUR) ) {
field = Calendar.HOUR;
} else if ( fromCalendar.get(Calendar.MINUTE) !=
toCalendar.get(Calendar.MINUTE) ) {
field = Calendar.MINUTE;
} else {
return null;
}
PatternInfo intervalPattern = fIntervalPatterns.get(
DateIntervalInfo.CALENDAR_FIELD_TO_PATTERN_LETTER[field]);
part2.value = intervalPattern.getSecondPart();
return intervalPattern.getFirstPart();
}
/**
* Format 2 Calendars to produce a string.
*

View File

@ -10,16 +10,19 @@ package com.ibm.icu.text;
import java.io.Serializable;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import com.ibm.icu.impl.PatternProps;
import com.ibm.icu.impl.PluralRulesLoader;
@ -30,56 +33,48 @@ import com.ibm.icu.util.ULocale;
/**
* <p>
* Defines rules for mapping non-negative numeric values onto a small set of keywords.
*
* </p>
* <p>
* Rules are constructed from a text description, consisting of a series of keywords and conditions. The {@link #select}
* method examines each condition in order and returns the keyword for the first condition that matches the number. If
* none match, {@link #KEYWORD_OTHER} is returned.
* </p>
*
* <p>
* A PluralRules object is immutable. It contains caches for sample values, but those are synchronized.
*
* <p>
* PluralRules is Serializable so that it can be used in formatters, which are serializable.
*
* </p>
* <p>
* For more information, details, and tips for writing rules, see the <a
* href="http://www.unicode.org/draft/reports/tr35/tr35.html#Language_Plural_Rules">LDML spec, C.11 Language Plural
* Rules</a>
* </p>
*
* <p>
* Examples:
* </p>
*
* <pre>
* &quot;one: n is 1; few: n in 2..4&quot;
* </pre>
*
* </p>
* <p>
* This defines two rules, for 'one' and 'few'. The condition for 'one' is "n is 1" which means that the number must be
* equal to 1 for this condition to pass. The condition for 'few' is "n in 2..4" which means that the number must be
* between 2 and 4 inclusive - and be an integer - for this condition to pass. All other numbers are assigned the
* keyword "other" by the default rule.
* </p>
* <p>
*
* <pre>
* &quot;zero: n is 0; one: n is 1; zero: n mod 100 in 1..19&quot;
* </pre>
*
* <p>
* This illustrates that the same keyword can be defined multiple times. Each rule is examined in order, and the first
* keyword whose condition passes is the one returned. Also notes that a modulus is applied to n in the last rule. Thus
* its condition holds for 119, 219, 319...
* </p>
* <p>
*
* <pre>
* &quot;one: n is 1; few: n mod 10 in 2..4 and n mod 100 not in 12..14&quot;
* </pre>
*
* </p>
* <p>
* This illustrates conjunction and negation. The condition for 'few' has two parts, both of which must be met:
* "n mod 10 in 2..4" and "n mod 100 not in 12..14". The first part applies a modulus to n before the test as in the
@ -88,7 +83,7 @@ import com.ibm.icu.util.ULocale;
* </p>
* <p>
* Syntax:
*
* </p>
* <pre>
* rules = rule (';' rule)*
* rule = keyword ':' condition
@ -101,53 +96,59 @@ import com.ibm.icu.util.ULocale;
* within_relation = expr ('not')? 'within' range_list
* expr = ('n' | 'i' | 'f' | 'v') ('mod' value)?
* range_list = (range | value) (',' range_list)*
* value = digit+
* value = digit+ ('.' digit+)?
* digit = 0|1|2|3|4|5|6|7|8|9
* range = value'..'value
* </pre>
*
* </p>
* <p>
* The i, f, and v values are defined as follows.
* The i, f, and v values are defined as follows:
* </p>
* <ul>
* <li>v to be the number of visible fraction digits.</li>
* <li>f to be the visible fractional digits.</li>
* <li>i to be the integer digits.</li>
* <li>f to be the visible fractional digits, as an integer.</li>
* <li>v to be the number of visible fraction digits.</li>
* <li>j is defined to only match integers. That is j is 3 fails if v != 0 (eg for 3.1 or 3.0).</li>
* </ul>
* <p>
* Examples are in the following table:
* </p>
* <table border='1'>
* <table border='1' style="border-collapse:collapse">
* <tbody>
* <tr>
* <td>n</td>
* <td>f</td>
* <td>v</td>
* <th>n</th>
* <th>i</th>
* <th>f</th>
* <th>v</th>
* </tr>
* <tr>
* <td>1.0</td>
* <td>0</td>
* <td>1</td>
* <td align="right">0</td>
* <td>1</td>
* </tr>
* <tr>
* <td>1.00</td>
* <td>0</td>
* <td>1</td>
* <td align="right">0</td>
* <td>2</td>
* </tr>
* <tr>
* <td>1.3</td>
* <td>3</td>
* <td>1</td>
* <td align="right">3</td>
* <td>1</td>
* </tr>
* <tr>
* <td>1.03</td>
* <td>3</td>
* <td>1</td>
* <td align="right">3</td>
* <td>2</td>
* </tr>
* <tr>
* <td>1.23</td>
* <td>23</td>
* <td>1</td>
* <td align="right">23</td>
* <td>2</td>
* </tr>
* </tbody>
@ -164,6 +165,16 @@ import com.ibm.icu.util.ULocale;
* @stable ICU 3.8
*/
public class PluralRules implements Serializable {
/**
* @internal
* @deprecated This API is ICU internal only.
*/
public static final String CATEGORY_SEPARATOR = "; ";
/**
* @internal
* @deprecated This API is ICU internal only.
*/
public static final String KEYWORD_RULE_SEPARATOR = ": ";
private static final long serialVersionUID = 1;
@ -173,6 +184,9 @@ public class PluralRules implements Serializable {
private transient int hashCode;
private transient Map<String, List<Double>> _keySamplesMap;
private transient Map<String, Boolean> _keyLimitedMap;
private transient Map<String, Set<NumberInfo>> _keyFractionSamplesMap;
private transient Set<NumberInfo> _fractionSamples;
// Standard keywords.
@ -262,6 +276,11 @@ public class PluralRules implements Serializable {
public int updateRepeatLimit(int limit) {
return limit;
}
public void getMentionedValues(Set<NumberInfo> toAddTo) {
toAddTo.add(new NumberInfo(0));
toAddTo.add(new NumberInfo(9999.9999));
}
};
/*
@ -283,12 +302,19 @@ public class PluralRules implements Serializable {
}
public String toString() {
return "(" + KEYWORD_OTHER + ")";
return "";
}
public int updateRepeatLimit(int limit) {
return limit;
}
public void getMentionedValues(Set<NumberInfo> toAddTo) {
}
public String getConstraint() {
return null;
}
};
/**
@ -332,35 +358,116 @@ public class PluralRules implements Serializable {
}
}
private static class NumberInfo {
private static final String OPERAND_LIST = "nifv";
public NumberInfo(double number, int countVisibleFractionDigits, int fractionalDigits) {
source = number;
intValue = (long)number;
this.fractionalDigits = fractionalDigits;
this.countVisibleFractionDigits = countVisibleFractionDigits;
private enum Operand {
n,
i,
f,
v,
j;
}
public NumberInfo(double number) {
this(number, 0, 0);
}
final double source;
final double intValue;
final double fractionalDigits;
final double countVisibleFractionDigits;
public double get(int operand) {
/**
* @deprecated This API is ICU internal only.
* @internal
*/
public static class NumberInfo implements Comparable<NumberInfo> {
public final double source;
public final int fractionalDigits;
public final int visibleFractionDigitCount;
public final double intValue;
public NumberInfo(double n, int v, int f) {
source = n;
visibleFractionDigitCount = v;
fractionalDigits = f;
intValue = (long)n;
}
// Ugly, but for samples we don't care.
public NumberInfo(double n, int v) {
this(n,v,getFractionalDigits(n, v));
}
// Ugly, but for samples we don't care.
public static int decimals(double n) {
String temp = String.valueOf(n);
return temp.endsWith(".0") ? 0 : temp.length() - temp.indexOf('.') - 1;
}
// Ugly, but for samples we don't care.
public NumberInfo (String n) {
this(Double.parseDouble(n), getVisibleFractionCount(n));
}
private static int getFractionalDigits(double n, int v) {
if (v == 0) {
return 0;
} else {
int base = (int) Math.pow(10, v);
long scaled = Math.round(n * base);
return (int) (scaled % base);
}
}
private static int getVisibleFractionCount(String value) {
int decimalPos = value.trim().indexOf('.') + 1;
if (decimalPos == 0) {
return 0;
} else {
return value.length() - decimalPos - 1;
}
}
public NumberInfo(double n) {
this(n, decimals(n));
}
public NumberInfo(long n) {
this(n,0);
}
public double get(Operand operand) {
switch(operand) {
default: return source;
case 1: return intValue;
case 2: return fractionalDigits;
case 3: return countVisibleFractionDigits;
case i: return intValue;
case f: return fractionalDigits;
case v: return visibleFractionDigitCount;
}
}
public static int getOperand(String t) {
return OPERAND_LIST.indexOf(t);
public static Operand getOperand(String t) {
return Operand.valueOf(t);
}
/**
* We're not going to care about NaN.
*/
public int compareTo(NumberInfo other) {
if (source != other.source) {
return source < other.source ? -1 : 1;
}
if (visibleFractionDigitCount != other.visibleFractionDigitCount) {
return visibleFractionDigitCount < other.visibleFractionDigitCount ? -1 : 1;
}
return fractionalDigits - other.fractionalDigits;
}
@Override
public boolean equals(Object arg0) {
NumberInfo other = (NumberInfo)arg0;
return source == other.source && visibleFractionDigitCount == other.visibleFractionDigitCount && fractionalDigits == other.fractionalDigits;
}
@Override
public int hashCode() {
// TODO Auto-generated method stub
return fractionalDigits + 37 * (visibleFractionDigitCount + (int)(37 * source));
}
@Override
public String toString() {
return String.format("%." + visibleFractionDigitCount + "f", source);
}
}
/*
* A constraint on a number.
*/
@ -386,6 +493,12 @@ public class PluralRules implements Serializable {
* @return the new limit
*/
int updateRepeatLimit(int limit);
/**
* Gets samples of significant numbers
*/
void getMentionedValues(Set<NumberInfo> toAddTo);
}
/*
@ -403,6 +516,13 @@ public class PluralRules implements Serializable {
/* Returns the larger of limit and this rule's limit. */
int updateRepeatLimit(int limit);
/**
* Gets samples of significant numbers
*/
void getMentionedValues(Set<NumberInfo> toAddTo);
public String getConstraint();
}
/*
@ -421,6 +541,15 @@ public class PluralRules implements Serializable {
/* Return true if the values for this keyword are limited. */
boolean isLimited(String keyword);
/**
* Get mentioned samples
*/
Set<NumberInfo> getMentionedValues(Set<NumberInfo> toAddTo);
/**
* keyword: rules mapping
*/
String getRules(String keyword);
}
/*
@ -465,16 +594,18 @@ public class PluralRules implements Serializable {
int mod = 0;
boolean inRange = true;
boolean integersOnly = true;
long lowBound = Long.MAX_VALUE;
long highBound = Long.MIN_VALUE;
long[] vals = null;
double lowBound = Long.MAX_VALUE;
double highBound = Long.MIN_VALUE;
double[] vals = null;
boolean isRange = false;
int x = 0;
String t = tokens[x++];
int operand = NumberInfo.getOperand(t);
if (operand < 0) {
Operand operand;
try {
operand = NumberInfo.getOperand(t);
} catch (Exception e) {
throw unexpected(t, condition);
}
if (x < tokens.length) {
@ -507,11 +638,11 @@ public class PluralRules implements Serializable {
if (isRange) {
String[] range_list = Utility.splitString(t, ",");
vals = new long[range_list.length * 2];
vals = new double[range_list.length * 2];
for (int k1 = 0, k2 = 0; k1 < range_list.length; ++k1, k2 += 2) {
String range = range_list[k1];
String[] pair = Utility.splitString(range, "..");
long low, high;
double low, high;
if (pair.length == 2) {
low = Long.parseLong(pair[0]);
high = Long.parseLong(pair[1]);
@ -638,25 +769,65 @@ public class PluralRules implements Serializable {
private final int mod;
private final boolean inRange;
private final boolean integersOnly;
private final long lowerBound;
private final long upperBound;
private final long[] range_list;
private final int operand;
private final double lowerBound;
private final double upperBound;
private final double[] range_list;
private final Operand operand;
RangeConstraint(int mod, boolean inRange, int operand, boolean integersOnly,
long lowerBound, long upperBound, long[] range_list) {
RangeConstraint(int mod, boolean inRange, Operand operand, boolean integersOnly,
double lowBound, double highBound, double[] vals) {
this.mod = mod;
this.inRange = inRange;
this.integersOnly = integersOnly;
this.lowerBound = lowerBound;
this.upperBound = upperBound;
this.range_list = range_list;
this.lowerBound = lowBound;
this.upperBound = highBound;
this.range_list = vals;
this.operand = operand;
}
public void getMentionedValues(Set<NumberInfo> toAddTo) {
addRanges(toAddTo, mod);
if (mod != 0) {
addRanges(toAddTo, mod*2);
addRanges(toAddTo, mod*3);
}
}
private void addRanges(Set<NumberInfo> toAddTo, int offset) {
toAddTo.add(new NumberInfo(lowerBound + offset));
if (upperBound != lowerBound) {
toAddTo.add(new NumberInfo(upperBound + offset));
}
if (range_list != null) {
// we will just add one value from the middle
for (double value : range_list) {
if (value == lowerBound || value == upperBound) {
continue;
}
toAddTo.add(new NumberInfo(value + offset));
break;
}
}
if (!integersOnly) {
double average = (lowerBound + upperBound) / 2.0d;
toAddTo.add(new NumberInfo(average + offset));
if (range_list != null) {
// we will just add one value from the middle
for (double value : range_list) {
if (value == lowerBound || value == upperBound) {
continue;
}
toAddTo.add(new NumberInfo(value + 0.33 + offset));
break;
}
}
}
}
public boolean isFulfilled(NumberInfo number) {
double n = number.get(operand);
if (integersOnly && (n - (long)n) != 0.0) {
if ((integersOnly && (n - (long)n) != 0.0
|| operand == Operand.j && number.visibleFractionDigitCount != 0)) {
return !inRange;
}
if (mod != 0) {
@ -682,50 +853,42 @@ public class PluralRules implements Serializable {
}
public String toString() {
class ListBuilder {
StringBuilder sb = new StringBuilder("[");
ListBuilder add(String s) {
return add(s, null);
}
ListBuilder add(String s, Object o) {
if (sb.length() > 1) {
sb.append(", ");
}
sb.append(s);
if (o != null) {
sb.append(": ").append(o.toString());
}
return this;
}
public String toString() {
String s = sb.append(']').toString();
sb = null;
return s;
}
}
ListBuilder lb = new ListBuilder();
lb.add(NumberInfo.OPERAND_LIST.substring(operand, operand+1));
if (mod > 1) {
lb.add("mod", mod);
}
if (inRange) {
lb.add("in");
} else {
lb.add("except");
}
if (integersOnly) {
lb.add("ints");
}
if (lowerBound == upperBound) {
lb.add(String.valueOf(lowerBound));
} else {
lb.add(String.valueOf(lowerBound) + "-" + String.valueOf(upperBound));
StringBuilder result = new StringBuilder();
result.append(operand);
if (mod != 0) {
result.append(" mod ").append(mod);
}
boolean isList = lowerBound != upperBound;
result.append(
!isList ? (inRange ? " is " : " is not ")
: integersOnly ? (inRange ? " in " : " not in ")
: (inRange ? " within " : " not within ")
);
if (range_list != null) {
lb.add(Arrays.toString(range_list));
for (int i = 0; i < range_list.length; i += 2) {
addRange(result, range_list[i], range_list[i+1], i != 0);
}
return lb.toString();
} else {
addRange(result, lowerBound, upperBound, false);
}
return result.toString();
}
}
private static void addRange(StringBuilder result, double lb, double ub, boolean addSeparator) {
if (addSeparator) {
result.append(",");
}
if (lb == ub) {
result.append(format(lb));
} else {
result.append(format(lb) + ".." + format(ub));
}
}
private static String format(double lb) {
long lbi = (long) lb;
return lb == lbi ? String.valueOf(lbi) : String.valueOf(lb);
}
/* Convenience base class for and/or constraints. */
@ -734,21 +897,15 @@ public class PluralRules implements Serializable {
private static final long serialVersionUID = 1;
protected final Constraint a;
protected final Constraint b;
private final String conjunction;
protected BinaryConstraint(Constraint a, Constraint b, String c) {
protected BinaryConstraint(Constraint a, Constraint b) {
this.a = a;
this.b = b;
this.conjunction = c;
}
public int updateRepeatLimit(int limit) {
return a.updateRepeatLimit(b.updateRepeatLimit(limit));
}
public String toString() {
return a.toString() + conjunction + b.toString();
}
}
/* A constraint representing the logical and of two constraints. */
@ -756,7 +913,7 @@ public class PluralRules implements Serializable {
private static final long serialVersionUID = 7766999779862263523L;
AndConstraint(Constraint a, Constraint b) {
super(a, b, " && ");
super(a, b);
}
public boolean isFulfilled(NumberInfo n) {
@ -768,6 +925,15 @@ public class PluralRules implements Serializable {
// satisfy both-- we still consider this 'unlimited'
return a.isLimited() || b.isLimited();
}
public void getMentionedValues(Set<NumberInfo> toAddTo) {
a.getMentionedValues(toAddTo);
b.getMentionedValues(toAddTo);
}
public String toString() {
return a.toString() + " and " + b.toString();
}
}
/* A constraint representing the logical or of two constraints. */
@ -775,7 +941,7 @@ public class PluralRules implements Serializable {
private static final long serialVersionUID = 1405488568664762222L;
OrConstraint(Constraint a, Constraint b) {
super(a, b, " || ");
super(a, b);
}
public boolean isFulfilled(NumberInfo n) {
@ -785,6 +951,14 @@ public class PluralRules implements Serializable {
public boolean isLimited() {
return a.isLimited() && b.isLimited();
}
public void getMentionedValues(Set<NumberInfo> toAddTo) {
a.getMentionedValues(toAddTo);
b.getMentionedValues(toAddTo);
}
public String toString() {
return a.toString() + " or " + b.toString();
}
}
/*
@ -828,7 +1002,18 @@ public class PluralRules implements Serializable {
}
public String toString() {
return keyword + ": " + constraint;
return keyword + ": " + constraint.toString();
}
public String getConstraint() {
return constraint.toString();
}
/**
* Gets samples of significant numbers
*/
public void getMentionedValues(Set<NumberInfo> toAddTo) {
constraint.getMentionedValues(toAddTo);
}
}
@ -913,13 +1098,130 @@ public class PluralRules implements Serializable {
}
public String toString() {
String s = rule.toString();
StringBuilder builder = new StringBuilder();
Map<String, String> ordered = new TreeMap<String, String>(KEYWORD_COMPARATOR);
for (RuleChain current = this; current != null; current = current.next) {
String keyword = current.rule.getKeyword();
String constraint = current.rule.getConstraint();
ordered.put(keyword, constraint);
}
for (Entry<String, String> entry : ordered.entrySet()) {
if (builder.length() != 0) {
builder.append(CATEGORY_SEPARATOR);
}
builder.append(entry.getKey()).append(KEYWORD_RULE_SEPARATOR).append(entry.getValue());
}
return builder.toString();
}
/* (non-Javadoc)
* @see com.ibm.icu.text.PluralRules.RuleList#getMentionedSamples(java.util.Set)
*/
public Set<NumberInfo> getMentionedValues(Set<NumberInfo> toAddTo) {
rule.getMentionedValues(toAddTo);
if (next != null) {
s = next.toString() + "; " + s;
next.getMentionedValues(toAddTo);
} else {
// once done, manufacture values for the OTHER case
int otherCount = 3;
NumberInfo last = null;
Set<NumberInfo> others = new LinkedHashSet<NumberInfo>();
for (NumberInfo s : toAddTo) {
double trial;
if (last == null) {
trial = s.source-0.5;
} else {
double diff = s.source - last.source;
if (diff > 1.0d) {
trial = Math.floor(s.source);
if (trial == s.source) {
--trial;
}
return s;
} else {
trial = (s.source + last.source) / 2;
}
}
if (trial >= 0) {
addConditional(toAddTo, others, trial);
}
last = s;
}
double trial = last == null ? 0 : last.source;
double fraction = 0;
while (otherCount > 0) {
if (addConditional(toAddTo, others, trial = trial * 2 + 1 + fraction)) {
--otherCount;
}
fraction += 0.125;
}
toAddTo.addAll(others);
}
toAddTo.add(new NumberInfo(0)); // always there
toAddTo.add(new NumberInfo(0,1)); // always there
toAddTo.add(new NumberInfo(0.1,1)); // always there
toAddTo.add(new NumberInfo(1)); // always there
toAddTo.add(new NumberInfo(1,2)); // always there
toAddTo.add(new NumberInfo(1.01,2)); // always there
toAddTo.add(new NumberInfo(2,2)); // always there
toAddTo.add(new NumberInfo(2.01,2)); // always there
toAddTo.add(new NumberInfo(2.10,2)); // always there
return toAddTo;
}
private boolean addConditional(Set<NumberInfo> toAddTo, Set<NumberInfo> others, double trial) {
boolean added;
NumberInfo toAdd = new NumberInfo(trial);
if (!toAddTo.contains(toAdd) && !others.contains(toAdd)) {
others.add(toAdd);
added = true;
} else {
added = false;
}
return added;
}
public String getRules(String keyword) {
for (RuleChain current = this; current != null; current = current.next) {
if (current.rule.getKeyword().equals(keyword)) {
return current.rule.getConstraint();
}
}
return null;
}
}
enum StandardPluralCategories {
zero,
one,
two,
few,
many,
other;
static StandardPluralCategories forString(String s) {
StandardPluralCategories a;
try {
a = valueOf(s);
} catch (Exception e) {
return null;
}
return a;
}
}
/**
* @deprecated This API is ICU internal only.
* @internal
*/
public static final Comparator<String> KEYWORD_COMPARATOR = new Comparator<String> () {
public int compare(String arg0, String arg1) {
StandardPluralCategories a = StandardPluralCategories.forString(arg0);
StandardPluralCategories b = StandardPluralCategories.forString(arg1);
return a == null
? (b == null ? arg0.compareTo(arg1) : -1)
: (b == null ? 1 : a.compareTo(b));
}
};
// -------------------------------------------------------------------------
// Static class methods.
@ -985,7 +1287,9 @@ public class PluralRules implements Serializable {
*/
private PluralRules(RuleList rules) {
this.rules = rules;
this.keywords = Collections.unmodifiableSet(rules.getKeywords());
TreeSet<String> temp = new TreeSet<String>(KEYWORD_COMPARATOR);
temp.addAll(rules.getKeywords());
this.keywords = Collections.unmodifiableSet(new LinkedHashSet<String>(temp));
}
/**
@ -1007,11 +1311,25 @@ public class PluralRules implements Serializable {
* @param number The number for which the rule has to be determined.
* @return The keyword of the selected rule.
* @internal
* @deprecated This API is ICU internal only.
*/
public String select(double number, int countVisibleFractionDigits, int fractionaldigits) {
return rules.select(new NumberInfo(number, countVisibleFractionDigits, fractionaldigits));
}
/**
* Given a number, returns the keyword of the first rule that applies to
* the number.
*
* @param number The number for which the rule has to be determined.
* @return The keyword of the selected rule.
* @internal
* @deprecated This API is ICU internal only.
*/
public String select(NumberInfo sample) {
return rules.select(new NumberInfo(sample.source, sample.visibleFractionDigitCount, sample.fractionalDigits));
}
/**
* Returns a set of all rule keywords used in this <code>PluralRules</code>
* object. The rule "other" is always present by default.
@ -1085,6 +1403,36 @@ public class PluralRules implements Serializable {
return getKeySamplesMap().get(keyword);
}
/**
* Returns a list of values for which select() would return that keyword,
* or null if the keyword is not defined. The returned collection is unmodifiable.
* The returned list is not complete, and there might be additional values that
* would return the keyword.
*
* @param keyword the keyword to test
* @return a list of values matching the keyword.
* @internal
* @deprecated This API is ICU internal only.
*/
public Collection<NumberInfo> getFractionSamples(String keyword) {
if (!keywords.contains(keyword)) {
return null;
}
initKeyMaps();
return _keyFractionSamplesMap.get(keyword);
}
/**
* Returns a list of values that includes at least one value for each keyword.
*
* @return a list of values
* @internal
*/
public Collection<NumberInfo> getFractionSamples() {
initKeyMaps();
return _fractionSamples;
}
private Map<String, Boolean> getKeyLimitedMap() {
initKeyMaps();
return _keyLimitedMap;
@ -1114,6 +1462,7 @@ public class PluralRules implements Serializable {
int keywordsRemaining = keywords.size();
int limit = Math.max(5, getRepeatLimit() * MAX_SAMPLES) * 2;
for (int i = 0; keywordsRemaining > 0 && i < limit; ++i) {
double val = i / 2.0;
String keyword = select(val);
@ -1133,13 +1482,26 @@ public class PluralRules implements Serializable {
}
}
// collect explicit samples
Map<String, Set<NumberInfo>> sampleFractionMap = new HashMap<String, Set<NumberInfo>>();
Set<NumberInfo> mentioned = rules.getMentionedValues(new TreeSet<NumberInfo>());
for (NumberInfo s : mentioned) {
String keyword = select(s.source, s.visibleFractionDigitCount, s.fractionalDigits);
Set<NumberInfo> list = sampleFractionMap.get(keyword);
if (list == null) {
list = new LinkedHashSet<NumberInfo>(); // will be sorted because the iteration is
sampleFractionMap.put(keyword, list);
}
list.add(s);
}
if (keywordsRemaining > 0) {
for (String k : keywords) {
if (!sampleMap.containsKey(k)) {
sampleMap.put(k, Collections.<Double>emptyList());
if (--keywordsRemaining == 0) {
break;
}
if (!sampleFractionMap.containsKey(k)) {
sampleFractionMap.put(k, Collections.<NumberInfo>emptySet());
}
}
}
@ -1148,7 +1510,12 @@ public class PluralRules implements Serializable {
for (Entry<String, List<Double>> entry : sampleMap.entrySet()) {
sampleMap.put(entry.getKey(), Collections.unmodifiableList(entry.getValue()));
}
for (Entry<String, Set<NumberInfo>> entry : sampleFractionMap.entrySet()) {
sampleFractionMap.put(entry.getKey(), Collections.unmodifiableSet(entry.getValue()));
}
_keySamplesMap = sampleMap;
_keyFractionSamplesMap = sampleFractionMap;
_fractionSamples = Collections.unmodifiableSet(mentioned);
}
}
@ -1188,11 +1555,10 @@ public class PluralRules implements Serializable {
* @stable ICU 3.8
*/
public String toString() {
return "keywords: " + keywords +
" limit: " + getRepeatLimit() +
" rules: " + rules.toString();
return rules.toString();
}
/**
* {@inheritDoc}
* @stable ICU 3.8
@ -1366,4 +1732,12 @@ public class PluralRules implements Serializable {
return originalSize == 1 ? KeywordStatus.UNIQUE : KeywordStatus.BOUNDED;
}
/**
* @internal
* @deprecated This API is ICU internal only.
*/
public String getRules(String keyword) {
return rules.getRules(keyword);
}
}

View File

@ -0,0 +1,149 @@
/*
*******************************************************************************
* Copyright (C) 2013, Google Inc, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.dev.test.format;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.ibm.icu.dev.util.Relation;
import com.ibm.icu.text.PluralRules;
import com.ibm.icu.text.PluralRules.NumberInfo;
import com.ibm.icu.text.PluralRules.PluralType;
import com.ibm.icu.util.ULocale;
/**
* @author markdavis
*
*/
public abstract class PluralRulesFactory {
abstract boolean hasOverride(ULocale locale);
abstract PluralRules forLocale(ULocale locale, PluralType ordinal);
PluralRules forLocale(ULocale locale) {
return forLocale(locale, PluralType.CARDINAL);
}
abstract ULocale[] getAvailableULocales();
abstract ULocale getFunctionalEquivalent(ULocale locale, boolean[] isAvailable);
static final PluralRulesFactory NORMAL = new PluralRulesFactoryVanilla();
static final PluralRulesFactory ALTERNATE = new PluralRulesFactoryWithOverrides();
private PluralRulesFactory() {}
static class PluralRulesFactoryVanilla extends PluralRulesFactory {
@Override
boolean hasOverride(ULocale locale) {
return false;
}
@Override
PluralRules forLocale(ULocale locale, PluralType ordinal) {
return PluralRules.forLocale(locale, ordinal);
}
@Override
ULocale[] getAvailableULocales() {
return PluralRules.getAvailableULocales();
}
@Override
ULocale getFunctionalEquivalent(ULocale locale, boolean[] isAvailable) {
return PluralRules.getFunctionalEquivalent(locale, isAvailable);
}
}
static class PluralRulesFactoryWithOverrides extends PluralRulesFactory {
static Map<ULocale,PluralRules> OVERRIDES = new HashMap<ULocale,PluralRules>();
static Relation<ULocale,NumberInfo> EXTRA_SAMPLES = Relation.of(new HashMap<ULocale,Set<NumberInfo>>(), HashSet.class);
static {
String[][] overrides = {
{"en,ca,de,et,fi,gl,it,nl,pt,sv,sw,ta,te,ur", "one: j is 1"},
{"cs,sk", "one: j is 1; few: j in 2..4; many: v is not 0"},
//{"el", "one: j is 1 or i is 0 and f is 1"},
{"da,is", "one: j is 1 or f is 1"},
{"fil", "one: j in 0..1"},
{"he", "one: j is 1; two: j is 2", "10,20"},
{"hi", "one: n within 0..1"},
{"hr", "one: j mod 10 is 1 and j mod 100 is not 11; few: j mod 10 in 2..4 and j mod 100 not in 12..14; many: j mod 10 is 0 or j mod 10 in 5..9 or j mod 100 in 11..14"},
{"lv", "zero: n mod 10 is 0" +
" or n mod 10 in 11..19" +
" or v in 1..6 and f is not 0 and f mod 10 is 0" +
" or v in 1..6 and f mod 10 in 11..19;" +
"one: n mod 10 is 1 and n mod 100 is not 11" +
" or v in 1..6 and f mod 10 is 1 and f mod 100 is not 11" +
" or v not in 0..6 and f mod 10 is 1"},
{"pl", "one: j is 1; few: j mod 10 in 2..4 and j mod 100 not in 12..14; many: j is not 1 and j mod 10 in 0..1 or j mod 10 in 5..9 or j mod 100 in 12..14"},
{"sl", "one: j mod 100 is 1; two: j mod 100 is 2; few: j mod 100 in 3..4 or v is not 0"},
{"sr", "one: j mod 10 is 1 and j mod 100 is not 11" +
" or v in 1..6 and f mod 10 is 1 and f mod 100 is not 11" +
" or v not in 0..6 and f mod 10 is 1;" +
" few: j mod 10 in 2..4 and j mod 100 not in 12..14" +
" or v in 1..6 and f mod 10 in 2..4 and f mod 100 not in 12..14" +
" or v not in 0..6 and f mod 10 in 2..4;" +
" many: j mod 10 is 0 or j mod 10 in 5..9 or j mod 100 in 11..14" +
" or v in 1..6 and f mod 10 in 5..9" +
" or v in 1..6 and f mod 100 in 11..14" +
" or v not in 0..6 and f mod 10 in 5..9"},
{"ro", "one: j is 1; few: n is 0 or n is not 1 and n mod 100 in 1..19"},
{"ru,uk", "one: j mod 10 is 1 and j mod 100 is not 11;" +
" few: j mod 10 in 2..4 and j mod 100 not in 12..14;" +
" many: j mod 10 is 0 or j mod 10 in 5..9 or j mod 100 in 11..14"},
};
for (String[] pair : overrides) {
for (String locale : pair[0].split("\\s*,\\s*")) {
ULocale uLocale = new ULocale(locale);
if (OVERRIDES.containsKey(uLocale)) {
throw new IllegalArgumentException("Duplicate locale: " + uLocale);
}
OVERRIDES.put(uLocale, PluralRules.createRules(pair[1]));
if (pair.length==3) {
for (String item : pair[2].split("\\s*,\\s*")) {
EXTRA_SAMPLES.put(uLocale, new PluralRules.NumberInfo(item));
}
}
}
}
}
@Override
boolean hasOverride(ULocale locale) {
return OVERRIDES.containsKey(locale);
}
@Override
PluralRules forLocale(ULocale locale, PluralType ordinal) {
PluralRules override = ordinal != PluralType.CARDINAL ? null : OVERRIDES.get(locale);
return override != null ? override: PluralRules.forLocale(locale, ordinal);
}
@Override
ULocale[] getAvailableULocales() {
return PluralRules.getAvailableULocales(); // TODO fix if we add more locales
}
static final Map<String,ULocale> rulesToULocale = new HashMap();
@Override
ULocale getFunctionalEquivalent(ULocale locale, boolean[] isAvailable) {
if (rulesToULocale.isEmpty()) {
for (ULocale locale2 : getAvailableULocales()) {
String rules = forLocale(locale2).toString();
ULocale old = rulesToULocale.get(rules);
if (old == null) {
rulesToULocale.put(rules, locale2);
}
}
}
String rules = forLocale(locale).toString();
ULocale result = rulesToULocale.get(rules);
return result == null ? ULocale.ROOT : result;
}
};
}

View File

@ -11,23 +11,36 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import com.ibm.icu.dev.test.TestFmwk;
import com.ibm.icu.impl.Utility;
import com.ibm.icu.text.PluralRules;
import com.ibm.icu.text.PluralRules.KeywordStatus;
import com.ibm.icu.text.PluralRules.PluralType;
import com.ibm.icu.text.PluralRules.NumberInfo;
import com.ibm.icu.util.Output;
import com.ibm.icu.util.ULocale;
/**
* @author dougfelt (Doug Felt)
* @author markdavis (Mark Davis) [for fractional support]
*/
public class PluralRulesTest extends TestFmwk {
static boolean USE_ALT = System.getProperty("alt_plurals") != null;
PluralRulesFactory factory = USE_ALT ? PluralRulesFactory.ALTERNATE : PluralRulesFactory.NORMAL;
public static void main(String[] args) throws Exception {
new PluralRulesTest().run(args);
}
@ -126,8 +139,8 @@ public class PluralRulesTest extends TestFmwk {
"one: 1 ; other: 1.00,1.0"}, // English rules
{"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 visible digit
{"one: n is 1 and v is 0; few: n in 2..4 and v is 0; many: v is not 0",
"one: 1; few: 2, 3, 4; many: 0.5, 1.0, 2.0, 2.1, 3.0, 4.999, 5.3; other:0,5,1001"}, // Last visible digit
{"one: j is 0",
"one: 0; other: 0.0, 1.0, 3"}, // Last visible digit
// one n is 1; few n in 2..4;
};
@ -140,6 +153,56 @@ public class PluralRulesTest extends TestFmwk {
try {
PluralRules rules = PluralRules.createRules(pattern);
logln(rules.toString());
checkCategoriesAndExpected(pattern, categoriesAndExpected, rules);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
}
}
}
public void testUniqueRules() {
main:
for (ULocale locale : factory.getAvailableULocales()) {
PluralRules rules = factory.forLocale(locale);
Collection<NumberInfo> samples = rules.getFractionSamples();
Map<String,PluralRules> keywordToRule = new HashMap<String,PluralRules>();
for (String keyword : rules.getKeywords()) {
if (keyword.equals("other")) {
continue;
}
String rules2 = keyword + ":" + rules.getRules(keyword);
PluralRules singleRule = PluralRules.createRules(rules2);
if (singleRule == null) {
errln("Can't generate single rule for " + rules2);
PluralRules.createRules(rules2); // for debugging
continue main;
}
keywordToRule.put(keyword, singleRule);
}
Map<NumberInfo, String> collisionTest = new TreeMap();
for (NumberInfo sample : samples) {
collisionTest.clear();
for (Entry<String, PluralRules> entry: keywordToRule.entrySet()) {
String keyword = entry.getKey();
PluralRules rule = entry.getValue();
String foundKeyword = rule.select(sample);
if (foundKeyword.equals("other")) {
continue;
}
String old = collisionTest.get(sample);
if (old != null) {
errln(locale + "\tNon-unique rules: " + sample + " => " + old + " & " + foundKeyword);
rule.select(sample);
} else {
collisionTest.put(sample, foundKeyword);
}
}
}
}
}
private void checkCategoriesAndExpected(String title, String categoriesAndExpected, PluralRules rules) {
for (String categoryAndExpected : categoriesAndExpected.split("\\s*;\\s*")) {
String[] categoryFromExpected = categoryAndExpected.split("\\s*:\\s*");
String expected = categoryFromExpected[0];
@ -155,12 +218,7 @@ public class PluralRulesTest extends TestFmwk {
fractionaldigits = Integer.parseInt(value.substring(decimalPos));
}
String result = rules.select(number, countVisibleFractionDigits, fractionaldigits);
assertEquals("testing <" + pair[0] + "> with <" + value + ">", expected, result);
}
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage());
assertEquals("testing <" + title + "> with <" + value + ">", expected, result);
}
}
}
@ -221,17 +279,17 @@ public class PluralRulesTest extends TestFmwk {
public void testBuiltInRules() {
// spot check
PluralRules rules = PluralRules.forLocale(ULocale.US);
PluralRules rules = factory.forLocale(ULocale.US);
assertEquals("us 0", PluralRules.KEYWORD_OTHER, rules.select(0));
assertEquals("us 1", PluralRules.KEYWORD_ONE, rules.select(1));
assertEquals("us 2", PluralRules.KEYWORD_OTHER, rules.select(2));
rules = PluralRules.forLocale(ULocale.JAPAN);
rules = factory.forLocale(ULocale.JAPAN);
assertEquals("ja 0", PluralRules.KEYWORD_OTHER, rules.select(0));
assertEquals("ja 1", PluralRules.KEYWORD_OTHER, rules.select(1));
assertEquals("ja 2", PluralRules.KEYWORD_OTHER, rules.select(2));
rules = PluralRules.forLocale(ULocale.createCanonical("ru"));
rules = factory.forLocale(ULocale.createCanonical("ru"));
assertEquals("ru 0", PluralRules.KEYWORD_MANY, rules.select(0));
assertEquals("ru 1", PluralRules.KEYWORD_ONE, rules.select(1));
assertEquals("ru 2", PluralRules.KEYWORD_FEW, rules.select(2));
@ -259,7 +317,7 @@ public class PluralRulesTest extends TestFmwk {
}
public void testAvailableULocales() {
ULocale[] locales = PluralRules.getAvailableULocales();
ULocale[] locales = factory.getAvailableULocales();
Set localeSet = new HashSet();
localeSet.addAll(Arrays.asList(locales));
@ -351,11 +409,11 @@ public class PluralRulesTest extends TestFmwk {
*/
public void TestGetSamples() {
Set<ULocale> uniqueRuleSet = new HashSet<ULocale>();
for (ULocale locale : PluralRules.getAvailableULocales()) {
for (ULocale locale : factory.getAvailableULocales()) {
uniqueRuleSet.add(PluralRules.getFunctionalEquivalent(locale, null));
}
for (ULocale locale : uniqueRuleSet) {
PluralRules rules = PluralRules.forLocale(locale);
PluralRules rules = factory.forLocale(locale);
logln("\nlocale: " + (locale == ULocale.ROOT ? "root" : locale.toString()) + ", rules: " + rules);
Set<String> keywords = rules.getKeywords();
for (String keyword : keywords) {
@ -442,7 +500,7 @@ public class PluralRulesTest extends TestFmwk {
}
public void TestOrdinal() {
PluralRules pr = PluralRules.forLocale(ULocale.ENGLISH, PluralType.ORDINAL);
PluralRules pr = factory.forLocale(ULocale.ENGLISH, PluralType.ORDINAL);
assertEquals("PluralRules(en-ordinal).select(2)", "two", pr.select(2));
}
@ -467,7 +525,7 @@ public class PluralRulesTest extends TestFmwk {
ULocale locale = new ULocale((String) test[0]);
// NumberType numberType = (NumberType) test[1];
Set<Double> explicits = (Set<Double>) test[1];
PluralRules pluralRules = PluralRules.forLocale(locale);
PluralRules pluralRules = factory.forLocale(locale);
LinkedHashSet<String> remaining = new LinkedHashSet(possibleKeywords);
for (int i = 2; i < test.length; i += 3) {
String keyword = (String) test[i];
@ -485,4 +543,116 @@ public class PluralRulesTest extends TestFmwk {
}
}
}
enum StandardPluralCategories {
zero,
one,
two,
few,
many,
other;
/**
*
*/
private static final Set<StandardPluralCategories> ALL = Collections.unmodifiableSet(EnumSet.allOf(StandardPluralCategories.class));
/**
* Return a mutable set
* @param source
* @return
*/
static final EnumSet<StandardPluralCategories> getSet(Collection<String> source) {
EnumSet<StandardPluralCategories> result = EnumSet.noneOf(StandardPluralCategories.class);
for (String s : source) {
result.add(StandardPluralCategories.valueOf(s));
}
return result;
}
static final Comparator<Set<StandardPluralCategories>> SHORTEST_FIRST = new Comparator<Set<StandardPluralCategories>>() {
public int compare(Set<StandardPluralCategories> arg0, Set<StandardPluralCategories> arg1) {
int diff = arg0.size() - arg1.size();
if (diff != 0) {
return diff;
}
// otherwise first...
// could be optimized, but we don't care here.
for (StandardPluralCategories value : ALL) {
if (arg0.contains(value)) {
if (!arg1.contains(value)) {
return 1;
}
} else if (arg1.contains(value)) {
return -1;
}
}
return 0;
}
};
}
public void TestLocales() {
for (String test : LOCALE_SNAPSHOT) {
test = test.trim();
String[] parts = test.split("\\s*;\\s*");
for (String localeString : parts[0].split("\\s*,\\s*")) {
ULocale locale = new ULocale(localeString);
if (factory.hasOverride(locale)) {
continue; // skip for now
}
PluralRules rules = factory.forLocale(locale);
for (int i = 1; i < parts.length; ++i) {
checkCategoriesAndExpected(localeString, parts[i], rules);
}
}
}
}
static final String[] LOCALE_SNAPSHOT = {
// [other]
"az,bm,bo,dz,fa,hu,id,ig,ii,ja,jv,ka,kde,kea,km,kn,ko,lo,ms,my,sah,ses,sg,th,to,tr,vi,wo,yo,zh; other: 0, 0.0, 0.1, 1, 1.0, 3, 7",
// [one, other]
"af,asa,ast,bem,bez,bg,bn,brx,ca,cgg,chr,ckb,da,de,dv,ee,el,en,eo,es,et,eu,fi,fo,fur,fy,gl,gsw,gu,ha,haw,hy,is,it,jgo,jmc,kaj,kcg,kk,kkj,kl,ks,ksb,ku,ky,lb,lg,mas,mgo,ml,mn,mr,nah,nb,nd,ne,nl,nn,nnh,no,nr,ny,nyn,om,or,os,pa,pap,ps,pt,rm,rof,rwk,saq,seh,sn,so,sq,ss,ssy,st,sv,sw,syr,ta,te,teo,tig,tk,tn,ts,ur,ve,vo,vun,wae,xh,xog,zu; one: 1, 1.0; other: 0, 0.0, 0.1, 0.5, 3, 7",
"ak,am,bh,fil,guw,hi,ln,mg,nso,ti,tl,wa; one: 0, 0.0, 1, 1.0; other: 0.1, 0.5, 3, 7",
"ff,fr,kab; one: 0, 0.0, 0.1, 0.5, 1, 1.0, 1.5; other: 2, 5",
"gv; one: 0, 0.0, 1, 1.0, 11, 12, 20, 21, 22, 31, 32, 40, 60; other: 0.1, 15.5, 39.5, 59",
"mk; one: 1, 1.0, 21, 31; other: 0, 0.0, 0.1, 10.5, 11, 26, 30",
"tzm; one: 0, 0.0, 1, 1.0, 11, 98, 99; other: 0.1, 0.5, 10",
// [one, few, other]
"cs,sk; one: 1, 1.0; few: 2, 3, 4; other: 0, 0.0, 0.1, 0.5, 5",
"lt; one: 1, 1.0, 21, 31; few: 22, 29, 32, 39, 65; other: 0, 0.0, 0.1, 11, 12, 19, 110, 111, 119, 211, 219, 311, 318.5, 319",
"mo,ro; one: 1, 1.0; few: 0, 0.0, 101, 118, 119, 201, 219, 301, 318, 319; other: 0.1, 160",
"shi; one: 0, 0.0, 0.1, 0.5, 1, 1.0; few: 2, 9, 10; other: 1.5, 5.5",
// [one, two, other]
"iu,kw,naq,se,sma,smi,smj,smn,sms; one: 1, 1.0; two: 2; other: 0, 0.0, 0.1, 0.5, 1.5, 5",
// [zero, one, other]
"ksh; zero: 0, 0.0; one: 1, 1.0; other: 0.1, 0.5, 3, 7",
"lag; zero: 0, 0.0; one: 0.1, 0.5, 1, 1.0, 1.5; other: 2, 5",
"lv; zero: 0, 0.0; one: 1, 1.0, 21, 31, 161; other: 0.1, 11, 110, 111, 211, 310, 311",
// [one, few, many, other]
"be,bs,hr,ru,sh,sr,uk; one: 1, 1.0, 21, 31; few: 22, 24, 32, 34; many: 0, 0.0, 10, 11, 12, 14, 15, 19, 20, 25, 29, 30, 35, 39, 110, 111, 112, 114, 211, 212, 214, 311, 312, 314; other: 0.1, 9.5, 15.5",
"mt; one: 1, 1.0; few: 0, 0.0, 102, 109, 110, 202, 210, 302, 310; many: 111, 119, 211, 219, 311, 319; other: 0.1, 55.5, 101",
"pl; one: 1, 1.0; few: 22, 24, 32, 34; many: 0, 0.0, 10, 11, 12, 14, 15, 19, 20, 21, 25, 29, 30, 31, 35, 39, 112, 114, 212, 214, 312, 314; other: 0.1, 5.5, 9.5, 15.5",
// [one, two, many, other]
"he; one: 1, 1.0; two: 2; many: 10, 20, 30; other: 0, 0.0, 0.1, 5.5, 19, 29",
// [one, two, few, other]
"gd; one: 1, 1.0, 11; two: 2, 12; few: 3, 10, 13, 19; other: 0, 0.0, 0.1, 5.5, 11.5, 12.5",
"sl; one: 1, 1.0, 101, 201, 301; two: 102, 202, 302; few: 103, 104, 203, 204, 303, 304; other: 0, 0.0, 0.1, 103.5, 152.5, 203.5",
// [one, two, few, many, other]
"br; one: 1, 1.0, 21, 31; two: 22, 32; few: 23, 24, 29, 33, 34, 39, 369, 389; many: 1000000, 2000000, 3000000; other: 0, 0.0, 0.1, 11, 12, 13, 14, 19, 110, 111, 112, 119, 170, 171, 172, 179, 190, 191, 192, 199, 210, 211, 212, 219, 270, 271, 272, 279, 290, 291, 292, 299, 310, 311, 312, 319, 334.5, 370, 371, 372, 379, 390, 391, 392, 399",
"ga; one: 1, 1.0; two: 2; few: 3, 6; many: 7, 8, 9, 10; other: 0, 0.0, 0.1, 6.5",
// [zero, one, two, few, many, other]
"ar; zero: 0, 0.0; one: 1, 1.0; two: 2; few: 103, 109, 110, 203, 210, 303, 310; many: 111, 150, 199, 211, 298, 299, 311, 399; other: 0.1",
"cy; zero: 0, 0.0; one: 1, 1.0; two: 2; few: 3; many: 6; other: 0.1, 2.5, 3.5, 5",
};
}

View File

@ -0,0 +1,549 @@
/*
*******************************************************************************
* Copyright (C) 2013, Google Inc. and International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.dev.test.format;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Map.Entry;
import com.ibm.icu.dev.test.format.PluralRulesTest.StandardPluralCategories;
import com.ibm.icu.dev.util.CollectionUtilities;
import com.ibm.icu.dev.util.Relation;
import com.ibm.icu.impl.Row;
import com.ibm.icu.text.PluralRules;
import com.ibm.icu.text.PluralRules.NumberInfo;
import com.ibm.icu.util.ULocale;
/**
* @author markdavis
*/
public class WritePluralRulesData {
public static void main(String[] args) throws Exception {
if (args.length == 0) {
args = new String[] {"rules"};
}
for (String arg : args) {
if (arg.equalsIgnoreCase("samples")) {
generateSamples();
} else if (arg.equalsIgnoreCase("rules")) {
showRules();
} else if (arg.equalsIgnoreCase("oldSnap")) {
generateLOCALE_SNAPSHOT(PluralRulesFactory.NORMAL);
} else if (arg.equalsIgnoreCase("newSnap")) {
generateLOCALE_SNAPSHOT(PluralRulesFactory.ALTERNATE);
} else {
throw new IllegalArgumentException();
}
}
}
public static void generateLOCALE_SNAPSHOT(PluralRulesFactory pluralRulesFactory) {
StringBuilder builder = new StringBuilder();
Map<Set<StandardPluralCategories>, Relation<String, ULocale>> keywordsToData = new TreeMap(StandardPluralCategories.SHORTEST_FIRST);
for (ULocale locale : pluralRulesFactory.getAvailableULocales()) {
builder.setLength(0);
PluralRules rules = pluralRulesFactory.forLocale(locale);
boolean firstKeyword = true;
EnumSet<StandardPluralCategories> keywords = StandardPluralCategories.getSet(rules.getKeywords());
Relation<String, ULocale> samplesToLocales = keywordsToData.get(keywords);
if (samplesToLocales == null) {
keywordsToData.put(keywords, samplesToLocales = Relation.of(
new LinkedHashMap<String,Set<ULocale>>(), LinkedHashSet.class));
}
//System.out.println(locale);
for (StandardPluralCategories keyword : keywords) {
if (firstKeyword) {
firstKeyword = false;
} else {
builder.append(";\t");
}
Collection<NumberInfo> samples = rules.getFractionSamples(keyword.toString());
if (samples.size() == 0) {
throw new IllegalArgumentException();
}
builder.append(keyword).append(": ");
boolean first = true;
for (NumberInfo n : samples) {
if (first) {
first = false;
} else {
builder.append(", ");
}
builder.append(n);
// for (double j : samples) {
// double sample = i + j/100;
// }
}
}
samplesToLocales.put(builder.toString(), locale);
}
System.out.println(" static final String[] LOCALE_SNAPSHOT = {");
for (Entry<Set<StandardPluralCategories>, Relation<String, ULocale>> keywordsAndData : keywordsToData.entrySet()) {
System.out.println("\n // " + keywordsAndData.getKey());
for (Entry<String, Set<ULocale>> samplesAndLocales : keywordsAndData.getValue().keyValuesSet()) {
Set<ULocale> locales = samplesAndLocales.getValue();
// check functional equivalence
boolean[] isAvailable = new boolean[1];
for (ULocale locale : locales) {
ULocale base = pluralRulesFactory.getFunctionalEquivalent(locale, isAvailable);
if (!locales.contains(base) && !base.toString().isEmpty()) {
System.out.println("**" + locales + " doesn't contain " + base);
}
}
System.out.println(
" \"" + CollectionUtilities.join(locales, ",")
+ ";\t" + samplesAndLocales.getKey() + "\",");
}
}
System.out.println(" };");
}
private static class OldNewData extends Row.R4<String, String, String, String> {
public OldNewData(String oldRules, String oldSamples, String newRules, String newSamples) {
super(oldRules, oldSamples, newRules, newSamples);
}
}
static final String[] FOCUS_LOCALES = ("af,am,ar,az,bg,bn,ca,cs,cy,da,de,el,en,es,et,eu,fa,fi,fil,fr,gl,gu," +
"hi,hr,hu,hy,id,is,it,he,ja,ka,kk,km,kn,ko,ky,lo,lt,lv,mk,ml,mn,mr,ms,my,ne,nl,nb," +
"pa,pl,ps,pt,ro,ru,si,sk,sl,sq,sr,sv,sw,ta,te,th,tr,uk,ur,uz,vi,zh,zu").split("\\s*,\\s*");
public static void showRules() {
if (true) {
// for debugging
PluralRules rules = PluralRulesFactory.ALTERNATE.forLocale(new ULocale("lv"));
rules.select(2.0d, 2, 0);
}
// System.out.println(new TreeSet(Arrays.asList(locales)));
Relation<Map<String,OldNewData>, String> rulesToLocale = Relation.of(
new TreeMap<Map<String,OldNewData>, Set<String>>(
new CollectionUtilities.MapComparator<String,OldNewData>()), TreeSet.class);
for (String localeString : FOCUS_LOCALES) {
ULocale locale = new ULocale(localeString);
PluralRules oldRules = PluralRules.forLocale(locale);
PluralRules newRules = PluralRulesFactory.ALTERNATE.hasOverride(locale) ? PluralRulesFactory.ALTERNATE.forLocale(locale) : null;
Set<String> keywords = oldRules.getKeywords();
if (newRules != null) {
TreeSet<String> temp = new TreeSet<String>(PluralRules.KEYWORD_COMPARATOR);
temp.addAll(keywords);
temp.addAll(newRules.getKeywords());
keywords = temp;
}
Map<String,OldNewData> temp = new LinkedHashMap();
for (String keyword : keywords) {
Collection<NumberInfo> oldFractionSamples = oldRules.getFractionSamples(keyword);
Collection<NumberInfo> newFractionSamples = newRules == null ? null : newRules.getFractionSamples(keyword);
// add extra samples if we have some, or if the rules differ
if (newRules != null) {
oldFractionSamples = oldFractionSamples == null ? new TreeSet()
: new TreeSet(oldFractionSamples);
newFractionSamples = newFractionSamples == null ? new TreeSet()
: new TreeSet(newFractionSamples);
// if (extraSamples != null) {
// for (NumberPlus sample : extraSamples) {
// if (oldRules.select(sample.source, sample.visibleFractionDigitCount, sample.fractionalDigits).equals(keyword)) {
// oldFractionSamples.add(sample);
// }
// if (newRules != null && newRules.select(sample.source, sample.visibleFractionDigitCount, sample.fractionalDigits).equals(keyword)) {
// newFractionSamples.add(sample);
// }
// }
// }
// if the rules differ, then add samples from each to the other
if (newRules != null) {
for (NumberInfo sample : oldRules.getFractionSamples()) {
if (newRules.select(sample.source, sample.visibleFractionDigitCount, sample.fractionalDigits).equals(keyword)) {
newFractionSamples.add(sample);
}
}
for (NumberInfo sample : newRules.getFractionSamples()) {
if (oldRules.select(sample.source, sample.visibleFractionDigitCount, sample.fractionalDigits).equals(keyword)) {
oldFractionSamples.add(sample);
}
}
}
}
String oldRulesString = oldRules.getRules(keyword);
if (oldRulesString == null) {
oldRulesString = "";
}
String newRulesString = newRules == null ? "" : newRules.getRules(keyword);
if (newRulesString == null) {
newRulesString = "";
}
temp.put(keyword, new OldNewData(
oldRulesString,
oldFractionSamples == null ? "" : "'" + CollectionUtilities.join(oldFractionSamples, ", "),
newRulesString,
newFractionSamples == null ? "" : "'" + CollectionUtilities.join(newFractionSamples, ", ")
));
}
rulesToLocale.put(temp, locale.toString());
}
System.out.println("Locales\tPC\tOld Rules\tOld Samples\tNew Rules\tNew Samples");
for (Entry<Map<String, OldNewData>, Set<String>> entry : rulesToLocale.keyValuesSet()) {
String localeList = CollectionUtilities.join(entry.getValue(), " ");
for (Entry<String, OldNewData> keywordRulesSamples : entry.getKey().entrySet()) {
System.out.println(
localeList // locale
+ "\t" + keywordRulesSamples.getKey() // keyword
+ "\t" + keywordRulesSamples.getValue().get0() // rules
+ "\t" + keywordRulesSamples.getValue().get1() // samples
+ "\t" + keywordRulesSamples.getValue().get2() // rules
+ "\t" + keywordRulesSamples.getValue().get3() // samples
);
localeList = "";
}
}
if (false) {
System.out.println("\n\nOld Rules for Locales");
for (String localeString : FOCUS_LOCALES) {
ULocale locale = new ULocale(localeString);
PluralRules oldRules = PluralRules.forLocale(locale);
System.out.println("{\"" + locale.toString() + "\", \"" + oldRules.toString() + "\"},");
}
}
}
static String[][] SAMPLE_PATTERNS = {
{"af", "one", "{0} dag"},
{"af", "other", "{0} dae"},
{"am", "one", "{0} ቀን"},
{"am", "other", "{0} ቀናት"}, // fixed to 'other'
{"ar", "few", "{0} ساعات"},
{"ar", "many", "{0} ساعة"},
{"ar", "one", "ساعة"},
{"ar", "other", "{0} ساعة"},
{"ar", "two", "ساعتان"},
{"ar", "zero", "{0} ساعة"},
{"bg", "one", "{0} ден"},
{"bg", "other", "{0} дена"},
{"bn", "one", "{0} টি আপেল"},
{"bn", "other", "আমার অনেকগুলি আপেল আছে"},
{"br", "few", "{0} deiz"},
{"br", "many", "{0} a zeizioù"},
{"br", "one", "{0} deiz"},
{"br", "other", "{0} deiz"},
{"br", "two", "{0} zeiz"},
{"ca", "one", "{0} dia"},
{"ca", "other", "{0} dies"},
{"cs", "few", "{0} dny"},
{"cs", "one", "{0} den"},
{"cs", "other", "{0} dní"},
{"cs", "many", "{0} dne"}, // added from spreadsheet
{"cy", "zero", "{0} cadair (f) {0} peint (m)"},
{"cy", "one", "{0} gadair (f) {0} peint (m)"},
{"cy", "two", "{0} gadair (f) {0} beint (m)"},
{"cy", "few", "{0} cadair (f) {0} pheint (m)"},
{"cy", "many", "{0} chadair (f) {0} pheint (m)"},
{"cy", "other", "{0} cadair (f) {0} peint (m)"},
{"da", "one", "{0} dag"},
{"da", "other", "{0} dage"},
{"de", "one", "{0} Tag"},
{"de", "other", "{0} Tage"},
{"dz", "other", "ཉིནམ་ {0} "},
{"el", "one", "{0} ημέρα"},
{"el", "other", "{0} ημέρες"},
{"es", "one", "{0} día"},
{"es", "other", "{0} días"},
{"et", "one", "{0} ööpäev"},
{"et", "other", "{0} ööpäeva"},
{"eu", "one", "Nire {0} lagunarekin nago"},
{"eu", "other", "Nire {0} lagunekin nago"},
{"fa", "other", "{0} روز"},
{"fi", "one", "{0} päivä"},
{"fi", "other", "{0} päivää"},
{"fil", "one", "sa {0} araw"},
{"fil", "other", "sa {0} (na) araw"},
{"fr", "one", "{0} jour"},
{"fr", "other", "{0} jours"},
{"gl", "one", "{0} día"},
{"gl", "other", "{0} días"},
{"gu", "one", "{0} અઠવાડિયું"},
{"gu", "other", "{0} અઠવાડિયા"},
{"he", "many", "{0} ימים"},
{"he", "one", " יום {0}"},
{"he", "other", "{0} ימים"},
{"he", "two", "יומיים"},
{"hi", "one", "{0} घंटा"},
{"hi", "other", "{0} घंटे"},
{"hr", "few", "za {0} mjeseca"},
{"hr", "many", "za {0} mjeseci"},
{"hr", "one", "za {0} mjesec"},
{"hr", "other", "za sljedeći broj mjeseci: {0}"},
{"hu", "other", "{0} nap"},
{"hy", "few", "{0} օր"},
{"hy", "many", "{0} օր"},
{"hy", "one", "{0} օր"},
{"hy", "other", "{0} օր"},
{"hy", "two", "{0} օր"},
{"hy", "zero", "{0} օր"},
{"id", "other", "{0} hari"},
{"is", "one", "{0} dagur"},
{"is", "other", "{0} dagar"},
{"it", "one", "{0} giorno"},
{"it", "other", "{0} giorni"},
{"ja", "other", "{0}日"},
{"km", "other", "{0} ថ្ងៃ"},
{"kn", "other", "{0} ದಿನಗಳು"},
{"ko", "other", "{0}일"},
{"lo", "other", "{0} ມື້"},
{"lt", "few", "{0} dienos"},
{"lt", "one", "{0} diena"},
{"lt", "other", "{0} dienų"},
{"lv", "one", "{0} diennakts"},
{"lv", "other", "{0} diennaktis"},
{"lv", "zero", "{0} diennakšu"},
{"ml", "one", "{0} വ്യക്തി"},
{"ml", "other", "{0} വ്യക്തികൾ"},
{"mr", "one", "{0} घर"},
{"mr", "other", "{0} घरे"},
{"ms", "other", "{0} hari"},
{"nb", "one", "{0} dag"},
{"nb", "other", "{0} dager"},
{"ne", "one", "तपाईंसँग {0} निमन्त्रणा छ"},
{"ne", "other", "तपाईँसँग {0} निमन्त्रणाहरू छन्"},
// {"ne", "", "{0} दिन बाँकी छ ।"},
// {"ne", "", "{0} दिन बाँकी छ ।"},
// {"ne", "", "{0} दिन बाँकी छ ।"},
// {"ne", "", "{0} जनाहरू पाहुना बाँकी छ ।"},
{"nl", "one", "{0} dag"},
{"nl", "other", "{0} dagen"},
{"pl", "few", "{0} miesiące"},
{"pl", "many", "{0} miesięcy"},
{"pl", "one", "{0} miesiąc"},
{"pl", "other", "{0} miesiąca"},
{"pt", "one", "{0} dia"},
{"pt", "other", "{0} dias"},
{"pt_PT", "one", "{0} dia"},
{"pt_PT", "other", "{0} dias"},
{"ro", "few", "{0} zile"},
{"ro", "one", "{0} zi"},
{"ro", "other", "{0} de zile"},
{"ru", "few", "{0} года"},
{"ru", "many", "{0} лет"},
{"ru", "one", "{0} год"},
{"ru", "other", "{0} года"},
{"si", "other", "දින {0}ක්"},
{"sk", "few", "{0} dni"},
{"sk", "one", "{0} deň"},
{"sk", "other", "{0} dní"},
{"sk", "many", "{0} dňa"}, // added from spreadsheet
{"sl", "few", "{0} ure"},
{"sl", "one", "{0} ura"},
{"sl", "other", "{0} ur"},
{"sl", "two", "{0} uri"},
{"sr", "few", "{0} сата"},
{"sr", "many", "{0} сати"},
{"sr", "one", "{0} сат"},
{"sr", "other", "{0} сати"},
{"sv", "one", "om {0} dag"},
{"sv", "other", "om {0} dagar"},
{"sw", "one", "siku {0} iliyopita"},
{"sw", "other", "siku {0} zilizopita"},
{"ta", "one", "{0} நாள்"},
{"ta", "other", "{0} நாட்கள்"},
{"te", "one", "{0} రోజు"},
{"te", "other", "{0} రోజులు"},
{"th", "other", "{0} วัน"},
{"tr", "other", "{0} gün"},
{"uk", "few", "{0} дні"},
{"uk", "many", "{0} днів"},
{"uk", "one", "{0} день"},
{"uk", "other", "{0} дня"},
{"ur", "one", "{0} گھنٹہ"},
{"ur", "other", "{0} گھنٹے"},
{"vi", "other", "{0} ngày"},
{"zh", "other", "{0} 天"},
{"zh_Hant", "other", "{0} 日"},
{"en", "one", "{0} day"}, // added from spreadsheet
{"en", "other", "{0} days"}, // added from spreadsheet
{"zu", "one", "{0} usuku"}, // added from spreadsheet
{"zu", "other", "{0} izinsuku"}, // added from spreadsheet
};
static final Set<String> NEW_LOCALES = new HashSet(Arrays.asList("az,ka,kk,ky,mk,mn,my,pa,ps,sq,uz".split("\\s*,\\s*")));
static class SamplePatterns {
final Map<String,String> keywordToPattern = new TreeMap(PluralRules.KEYWORD_COMPARATOR);
final Map<String,String> keywordToErrors = new HashMap();
public void put(String keyword, String sample) {
if (keywordToPattern.containsKey(keyword)) {
throw new IllegalArgumentException("Duplicate keyword <" + keyword + ">");
} else {
keywordToPattern.put(keyword, sample);
}
}
public void checkErrors(Set<String> set) {
final Map<String,String> skeletonToKeyword = new HashMap();
for (String keyword : set) {
String error = "";
String sample = keywordToPattern.get(keyword);
String skeleton = sample.replace(" ", "").replace("{0}", "");
String oldSkeletonKeyword = skeletonToKeyword.get(skeleton);
if (oldSkeletonKeyword != null) {
if (!error.isEmpty()) {
error += ", ";
}
error += "Duplicate keyword skeleton <" + keyword + ", " + skeleton + ">, same as for: <" + oldSkeletonKeyword + ">";
} else {
skeletonToKeyword.put(skeleton, keyword);
}
if (error.isEmpty()) {
keywordToErrors.put(keyword, "");
} else {
keywordToErrors.put(keyword, "\tERROR: " + error);
}
}
}
}
static void generateSamples() {
Map<ULocale, SamplePatterns> localeToSamplePatterns = new LinkedHashMap();
for (String[] row : SAMPLE_PATTERNS) {
ULocale locale = new ULocale(row[0]);
String keyword = row[1];
String sample = row[2];
SamplePatterns samplePatterns = localeToSamplePatterns.get(locale);
if (samplePatterns == null) {
localeToSamplePatterns.put(locale, samplePatterns = new SamplePatterns());
}
samplePatterns.put(keyword, sample);
}
LinkedHashSet<ULocale> skippedLocales = new LinkedHashSet<ULocale>();
System.out.println("Locale\tPC\tPattern\tSample\tErrors");
for (String localeString : FOCUS_LOCALES) {
ULocale locale = new ULocale(localeString);
PluralRules newRules = PluralRulesFactory.ALTERNATE.forLocale(locale);
SamplePatterns samplePatterns = localeToSamplePatterns.get(locale);
if (samplePatterns == null && NEW_LOCALES.contains(localeString)) {
skippedLocales.add(locale);
continue;
}
// check for errors. Changes state so that we get an error map
samplePatterns.checkErrors(newRules.getKeywords());
// now print.
for (String keyword : newRules.getKeywords()) {
String pattern = null;
String error = null;
Collection<NumberInfo> samples = newRules.getFractionSamples(keyword);
NumberInfo first = samples.iterator().next();
String sample = "??? " + first.toString();
if (samplePatterns == null) {
pattern = "???";
error = "\tERROR: Locale data missing";
} else {
pattern = samplePatterns.keywordToPattern.get(keyword);
error = samplePatterns.keywordToErrors.get(keyword);
if (pattern == null) {
pattern = "???";
error = "\tERROR: Needed for new rules";
} else {
sample = pattern.replace("{0}", first.toString());
}
}
System.out.println(locale + "\t" + keyword
+ "\t" + pattern
+ "\t" + sample
+ error
);
}
}
System.out.println("SKIP:\t\t\t" + skippedLocales);
}
static String[][] OLDRULES = {
{"af", "one: n is 1"},
{"am", "one: n in 0..1"},
{"ar", "zero: n is 0; one: n is 1; two: n is 2; few: n mod 100 in 3..10; many: n mod 100 in 11..99"},
{"az", "other: null"},
{"bg", "one: n is 1"},
{"bn", "one: n is 1"},
{"ca", "one: n is 1"},
{"cs", "one: n is 1; few: n in 2..4"},
{"cy", "zero: n is 0; one: n is 1; two: n is 2; few: n is 3; many: n is 6"},
{"da", "one: n is 1"},
{"de", "one: n is 1"},
{"el", "one: n is 1"},
{"en", "one: n is 1"},
{"es", "one: n is 1"},
{"et", "one: n is 1"},
{"eu", "one: n is 1"},
{"fa", "other: null"},
{"fi", "one: n is 1"},
{"fil", "one: n in 0..1"},
{"fr", "one: n within 0..2 and n is not 2"},
{"gl", "one: n is 1"},
{"gu", "one: n is 1"},
{"hi", "one: n in 0..1"},
{"hr", "one: n mod 10 is 1 and n mod 100 is not 11; few: n mod 10 in 2..4 and n mod 100 not in 12..14; many: n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14"},
{"hu", "other: null"},
{"hy", "one: n is 1"},
{"id", "other: null"},
{"is", "one: n is 1"},
{"it", "one: n is 1"},
{"he", "one: n is 1; two: n is 2; many: n is not 0 and n mod 10 is 0"},
{"ja", "other: null"},
{"ka", "other: null"},
{"kk", "one: n is 1"},
{"km", "other: null"},
{"kn", "other: null"},
{"ko", "other: null"},
{"ky", "one: n is 1"},
{"lo", "other: null"},
{"lt", "one: n mod 10 is 1 and n mod 100 not in 11..19; few: n mod 10 in 2..9 and n mod 100 not in 11..19"},
{"lv", "zero: n is 0; one: n mod 10 is 1 and n mod 100 is not 11"},
{"mk", "one: n mod 10 is 1 and n is not 11"},
{"ml", "one: n is 1"},
{"mn", "one: n is 1"},
{"mr", "one: n is 1"},
{"ms", "other: null"},
{"my", "other: null"},
{"ne", "one: n is 1"},
{"nl", "one: n is 1"},
{"nb", "one: n is 1"},
{"pa", "one: n is 1"},
{"pl", "one: n is 1; few: n mod 10 in 2..4 and n mod 100 not in 12..14; many: n is not 1 and n mod 10 in 0..1 or n mod 10 in 5..9 or n mod 100 in 12..14"},
{"ps", "one: n is 1"},
{"pt", "one: n is 1"},
{"ro", "one: n is 1; few: n is 0 or n is not 1 and n mod 100 in 1..19"},
{"ru", "one: n mod 10 is 1 and n mod 100 is not 11; few: n mod 10 in 2..4 and n mod 100 not in 12..14; many: n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14"},
{"si", "other: null"},
{"sk", "one: n is 1; few: n in 2..4"},
{"sl", "one: n mod 100 is 1; two: n mod 100 is 2; few: n mod 100 in 3..4"},
{"sq", "one: n is 1"},
{"sr", "one: n mod 10 is 1 and n mod 100 is not 11; few: n mod 10 in 2..4 and n mod 100 not in 12..14; many: n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14"},
{"sv", "one: n is 1"},
{"sw", "one: n is 1"},
{"ta", "one: n is 1"},
{"te", "one: n is 1"},
{"th", "other: null"},
{"tr", "other: null"},
{"uk", "one: n mod 10 is 1 and n mod 100 is not 11; few: n mod 10 in 2..4 and n mod 100 not in 12..14; many: n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14"},
{"ur", "one: n is 1"},
{"uz", "other: null"},
{"vi", "other: null"},
{"zh", "other: null"},
{"zu", "one: n is 1"},
};
}

View File

@ -1,6 +1,6 @@
/*
*******************************************************************************
* Copyright (C) 1996-2012, International Business Machines Corporation and *
* Copyright (C) 1996-2013, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
@ -11,7 +11,10 @@ import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.regex.Matcher;
import com.ibm.icu.text.UTF16;
@ -542,4 +545,146 @@ public final class CollectionUtilities {
}
}
/**
* Compare, allowing nulls
* @param a
* @param b
* @return
*/
public static <T> boolean equals(T a, T b) {
return a == null
? b == null
: b == null ? false : a.equals(b);
}
/**
* Compare, allowing nulls and putting them first
* @param a
* @param b
* @return
*/
public static <T extends Comparable> int compare(T a, T b) {
return a == null
? b == null ? 0 : -1
: b == null ? 1 : a.compareTo(b);
}
/**
* Compare iterators
* @param iterator1
* @param iterator2
* @return
*/
public static <T extends Comparable> int compare(Iterator<T> iterator1, Iterator<T> iterator2) {
int diff;
while (true) {
if (!iterator1.hasNext()) {
return iterator2.hasNext() ? -1 : 0;
} else if (!iterator2.hasNext()) {
return 1;
}
diff = CollectionUtilities.compare(iterator1.next(), iterator2.next());
if (diff != 0) {
return diff;
}
}
}
/**
* Compare, with shortest first, and otherwise lexicographically
* @param a
* @param b
* @return
*/
public static <T extends Comparable, U extends Collection<T>> int compare(U o1, U o2) {
int diff = o1.size() - o2.size();
if (diff != 0) {
return diff;
}
Iterator<T> iterator1 = o1.iterator();
Iterator<T> iterator2 = o2.iterator();
return compare(iterator1, iterator2);
}
/**
* Compare, with shortest first, and otherwise lexicographically
* @param a
* @param b
* @return
*/
public static <T extends Comparable, U extends Set<T>> int compare(U o1, U o2) {
int diff = o1.size() - o2.size();
if (diff != 0) {
return diff;
}
return compare(new TreeSet(o1), new TreeSet(o2));
}
public static class SetComparator<T extends Comparable>
implements Comparator<Set<T>> {
public int compare(Set<T> o1, Set<T> o2) {
return CollectionUtilities.compare(o1, o2);
}
};
public static class CollectionComparator<T extends Comparable>
implements Comparator<Collection<T>> {
public int compare(Collection<T> o1, Collection<T> o2) {
return CollectionUtilities.compare(o1, o2);
}
};
/**
* Compare, allowing nulls and putting them first
* @param a
* @param b
* @return
*/
public static <K extends Comparable, V extends Comparable, T extends Entry<K, V>> int compare(T a, T b) {
if (a == null) {
return b == null ? 0 : -1;
} else if (b == null) {
return 1;
}
int diff = compare(a.getKey(), b.getKey());
if (diff != 0) {
return diff;
}
return compare(a.getValue(), b.getValue());
}
public static <K extends Comparable, V extends Comparable, T extends Entry<K, V>> int compareEntrySets(Collection<T> o1, Collection<T> o2) {
int diff = o1.size() - o2.size();
if (diff != 0) {
return diff;
}
Iterator<T> iterator1 = o1.iterator();
Iterator<T> iterator2 = o2.iterator();
while (true) {
if (!iterator1.hasNext()) {
return iterator2.hasNext() ? -1 : 0;
} else if (!iterator2.hasNext()) {
return 1;
}
T item1 = iterator1.next();
T item2 = iterator2.next();
diff = CollectionUtilities.compare(item1, item2);
if (diff != 0) {
return diff;
}
}
}
public static class MapComparator<K extends Comparable, V extends Comparable> implements Comparator<Map<K,V>> {
public int compare(Map<K, V> o1, Map<K, V> o2) {
return CollectionUtilities.compareEntrySets(o1.entrySet(), o2.entrySet());
}
};
public static class ComparableComparator<T extends Comparable> implements Comparator<T> {
public int compare(T arg0, T arg1) {
return CollectionUtilities.compare(arg0, arg1);
}
}
}

View File

@ -1,6 +1,6 @@
/*
*******************************************************************************
* Copyright (C) 1996-2012, International Business Machines Corporation and *
* Copyright (C) 1996-2013, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
@ -736,6 +736,9 @@ public abstract class UnicodeProperty extends UnicodeLabel {
public final Factory add(UnicodeProperty sp) {
String name2 = sp.getName();
if (name2.isEmpty()) {
throw new IllegalArgumentException();
}
canonicalNames.put(name2, sp);
skeletonNames.put(toSkeleton(name2), sp);
List c = sp.getNameAliases(new ArrayList(1));