| /* |
| ******************************************************************************* |
| * Copyright (C) 2007-2014, International Business Machines Corporation and |
| * others. All Rights Reserved. |
| ******************************************************************************* |
| */ |
| |
| package com.ibm.icu.text; |
| |
| import java.io.IOException; |
| import java.io.NotSerializableException; |
| import java.io.ObjectInputStream; |
| import java.io.ObjectOutputStream; |
| import java.io.ObjectStreamException; |
| 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.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Set; |
| import java.util.TreeSet; |
| import java.util.regex.Pattern; |
| |
| import com.ibm.icu.impl.PluralRulesLoader; |
| import com.ibm.icu.util.Output; |
| 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> |
| * "one: n is 1; few: n in 2..4" |
| * </pre> |
| * <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> |
| * |
| * <pre> |
| * "zero: n is 0; one: n is 1; zero: n mod 100 in 1..19" |
| * </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> |
| * |
| * <pre> |
| * "one: n is 1; few: n mod 10 in 2..4 and n mod 100 not in 12..14" |
| * </pre> |
| * <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 |
| * previous example. The second part applies a different modulus and also uses negation, thus it matches all numbers |
| * _not_ in 12, 13, 14, 112, 113, 114, 212, 213, 214... |
| * </p> |
| * <p> |
| * Syntax: |
| * </p> |
| * <pre> |
| * rules = rule (';' rule)* |
| * rule = keyword ':' condition |
| * keyword = <identifier> |
| * condition = and_condition ('or' and_condition)* |
| * and_condition = relation ('and' relation)* |
| * relation = not? expr not? rel not? range_list |
| * expr = ('n' | 'i' | 'f' | 'v' | 't') (mod value)? |
| * not = 'not' | '!' |
| * rel = 'in' | 'is' | '=' | '≠' | 'within' |
| * mod = 'mod' | '%' |
| * range_list = (range | value) (',' range_list)* |
| * value = digit+ |
| * digit = 0|1|2|3|4|5|6|7|8|9 |
| * range = value'..'value |
| * </pre> |
| * <p>Each <b>not</b> term inverts the meaning; however, there should not be more than one of them.</p> |
| * <p> |
| * The i, f, t, and v values are defined as follows: |
| * </p> |
| * <ul> |
| * <li>i to be the integer digits.</li> |
| * <li>f to be the visible decimal digits, as an integer.</li> |
| * <li>t to be the visible decimal digits—without trailing zeros—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' style="border-collapse:collapse"> |
| * <tbody> |
| * <tr> |
| * <th>n</th> |
| * <th>i</th> |
| * <th>f</th> |
| * <th>v</th> |
| * </tr> |
| * <tr> |
| * <td>1.0</td> |
| * <td>1</td> |
| * <td align="right">0</td> |
| * <td>1</td> |
| * </tr> |
| * <tr> |
| * <td>1.00</td> |
| * <td>1</td> |
| * <td align="right">0</td> |
| * <td>2</td> |
| * </tr> |
| * <tr> |
| * <td>1.3</td> |
| * <td>1</td> |
| * <td align="right">3</td> |
| * <td>1</td> |
| * </tr> |
| * <tr> |
| * <td>1.03</td> |
| * <td>1</td> |
| * <td align="right">3</td> |
| * <td>2</td> |
| * </tr> |
| * <tr> |
| * <td>1.23</td> |
| * <td>1</td> |
| * <td align="right">23</td> |
| * <td>2</td> |
| * </tr> |
| * </tbody> |
| * </table> |
| * <p> |
| * An "identifier" is a sequence of characters that do not have the Unicode Pattern_Syntax or Pattern_White_Space |
| * properties. |
| * <p> |
| * The difference between 'in' and 'within' is that 'in' only includes integers in the specified range, while 'within' |
| * includes all values. Using 'within' with a range_list consisting entirely of values is the same as using 'in' (it's |
| * not an error). |
| * </p> |
| * |
| * @stable ICU 3.8 |
| */ |
| public class PluralRules implements Serializable { |
| |
| static final UnicodeSet ALLOWED_ID = new UnicodeSet("[a-z]").freeze(); |
| |
| // TODO Remove RulesList by moving its API and fields into PluralRules. |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public static final String CATEGORY_SEPARATOR = "; "; |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public static final String KEYWORD_RULE_SEPARATOR = ": "; |
| |
| private static final long serialVersionUID = 1; |
| |
| private final RuleList rules; |
| private final transient Set<String> keywords; |
| |
| /** |
| * Provides a factory for returning plural rules |
| * |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public static abstract class Factory { |
| /** |
| * Sole constructor |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| protected Factory() { |
| } |
| |
| /** |
| * Provides access to the predefined <code>PluralRules</code> for a given locale and the plural type. |
| * |
| * <p> |
| * ICU defines plural rules for many locales based on CLDR <i>Language Plural Rules</i>. For these predefined |
| * rules, see CLDR page at http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html |
| * |
| * @param locale |
| * The locale for which a <code>PluralRules</code> object is returned. |
| * @param type |
| * The plural type (e.g., cardinal or ordinal). |
| * @return The predefined <code>PluralRules</code> object for this locale. If there's no predefined rules for |
| * this locale, the rules for the closest parent in the locale hierarchy that has one will be returned. |
| * The final fallback always returns the default rules. |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public abstract PluralRules forLocale(ULocale locale, PluralType type); |
| |
| /** |
| * Utility for getting CARDINAL rules. |
| * @param locale the locale |
| * @return plural rules. |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public final PluralRules forLocale(ULocale locale) { |
| return forLocale(locale, PluralType.CARDINAL); |
| } |
| |
| /** |
| * Returns the locales for which there is plurals data. |
| * |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public abstract ULocale[] getAvailableULocales(); |
| |
| /** |
| * Returns the 'functionally equivalent' locale with respect to plural rules. Calling PluralRules.forLocale with |
| * the functionally equivalent locale, and with the provided locale, returns rules that behave the same. <br/> |
| * All locales with the same functionally equivalent locale have plural rules that behave the same. This is not |
| * exaustive; there may be other locales whose plural rules behave the same that do not have the same equivalent |
| * locale. |
| * |
| * @param locale |
| * the locale to check |
| * @param isAvailable |
| * if not null and of length > 0, this will hold 'true' at index 0 if locale is directly defined |
| * (without fallback) as having plural rules |
| * @return the functionally-equivalent locale |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public abstract ULocale getFunctionalEquivalent(ULocale locale, boolean[] isAvailable); |
| |
| /** |
| * Returns the default factory. |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public static PluralRulesLoader getDefaultFactory() { |
| return PluralRulesLoader.loader; |
| } |
| |
| /** |
| * Returns whether or not there are overrides. |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public abstract boolean hasOverride(ULocale locale); |
| } |
| // Standard keywords. |
| |
| /** |
| * Common name for the 'zero' plural form. |
| * @stable ICU 3.8 |
| */ |
| public static final String KEYWORD_ZERO = "zero"; |
| |
| /** |
| * Common name for the 'singular' plural form. |
| * @stable ICU 3.8 |
| */ |
| public static final String KEYWORD_ONE = "one"; |
| |
| /** |
| * Common name for the 'dual' plural form. |
| * @stable ICU 3.8 |
| */ |
| public static final String KEYWORD_TWO = "two"; |
| |
| /** |
| * Common name for the 'paucal' or other special plural form. |
| * @stable ICU 3.8 |
| */ |
| public static final String KEYWORD_FEW = "few"; |
| |
| /** |
| * Common name for the arabic (11 to 99) plural form. |
| * @stable ICU 3.8 |
| */ |
| public static final String KEYWORD_MANY = "many"; |
| |
| /** |
| * Common name for the default plural form. This name is returned |
| * for values to which no other form in the rule applies. It |
| * can additionally be assigned rules of its own. |
| * @stable ICU 3.8 |
| */ |
| public static final String KEYWORD_OTHER = "other"; |
| |
| /** |
| * Value returned by {@link #getUniqueKeywordValue} when there is no |
| * unique value to return. |
| * @stable ICU 4.8 |
| */ |
| public static final double NO_UNIQUE_VALUE = -0.00123456777; |
| |
| /** |
| * Type of plurals and PluralRules. |
| * @stable ICU 50 |
| */ |
| public enum PluralType { |
| /** |
| * Plural rules for cardinal numbers: 1 file vs. 2 files. |
| * @stable ICU 50 |
| */ |
| CARDINAL, |
| /** |
| * Plural rules for ordinal numbers: 1st file, 2nd file, 3rd file, 4th file, etc. |
| * @stable ICU 50 |
| */ |
| ORDINAL |
| }; |
| |
| /* |
| * The default constraint that is always satisfied. |
| */ |
| private static final Constraint NO_CONSTRAINT = new Constraint() { |
| private static final long serialVersionUID = 9163464945387899416L; |
| |
| public boolean isFulfilled(FixedDecimal n) { |
| return true; |
| } |
| |
| public boolean isLimited(SampleType sampleType) { |
| return false; |
| } |
| |
| public String toString() { |
| return ""; |
| } |
| }; |
| |
| /** |
| * |
| */ |
| private static final Rule DEFAULT_RULE = new Rule("other", NO_CONSTRAINT, null, null); |
| |
| /** |
| * Parses a plural rules description and returns a PluralRules. |
| * @param description the rule description. |
| * @throws ParseException if the description cannot be parsed. |
| * The exception index is typically not set, it will be -1. |
| * @stable ICU 3.8 |
| */ |
| public static PluralRules parseDescription(String description) |
| throws ParseException { |
| |
| description = description.trim(); |
| return description.length() == 0 ? DEFAULT : new PluralRules(parseRuleChain(description)); |
| } |
| |
| /** |
| * Creates a PluralRules from a description if it is parsable, |
| * otherwise returns null. |
| * @param description the rule description. |
| * @return the PluralRules |
| * @stable ICU 3.8 |
| */ |
| public static PluralRules createRules(String description) { |
| try { |
| return parseDescription(description); |
| } catch(Exception e) { |
| return null; |
| } |
| } |
| |
| /** |
| * The default rules that accept any number and return |
| * {@link #KEYWORD_OTHER}. |
| * @stable ICU 3.8 |
| */ |
| public static final PluralRules DEFAULT = new PluralRules(new RuleList().addRule(DEFAULT_RULE)); |
| |
| private enum Operand { |
| n, |
| i, |
| f, |
| t, |
| v, |
| w, |
| /* deprecated */ |
| j; |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public static class FixedDecimal extends Number implements Comparable<FixedDecimal> { |
| private static final long serialVersionUID = -4756200506571685661L; |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public final double source; |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public final int visibleDecimalDigitCount; |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public final int visibleDecimalDigitCountWithoutTrailingZeros; |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public final long decimalDigits; |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public final long decimalDigitsWithoutTrailingZeros; |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public final long integerValue; |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public final boolean hasIntegerValue; |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public final boolean isNegative; |
| private final int baseFactor; |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public double getSource() { |
| return source; |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public int getVisibleDecimalDigitCount() { |
| return visibleDecimalDigitCount; |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public int getVisibleDecimalDigitCountWithoutTrailingZeros() { |
| return visibleDecimalDigitCountWithoutTrailingZeros; |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public long getDecimalDigits() { |
| return decimalDigits; |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public long getDecimalDigitsWithoutTrailingZeros() { |
| return decimalDigitsWithoutTrailingZeros; |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public long getIntegerValue() { |
| return integerValue; |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public boolean isHasIntegerValue() { |
| return hasIntegerValue; |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public boolean isNegative() { |
| return isNegative; |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public int getBaseFactor() { |
| return baseFactor; |
| } |
| |
| static final long MAX = (long)1E18; |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| * @param n is the original number |
| * @param v number of digits to the right of the decimal place. e.g 1.00 = 2 25. = 0 |
| * @param f Corresponds to f in the plural rules grammar. |
| * The digits to the right of the decimal place as an integer. e.g 1.10 = 10 |
| */ |
| @Deprecated |
| public FixedDecimal(double n, int v, long f) { |
| isNegative = n < 0; |
| source = isNegative ? -n : n; |
| visibleDecimalDigitCount = v; |
| decimalDigits = f; |
| integerValue = n > MAX |
| ? MAX |
| : (long)n; |
| hasIntegerValue = source == integerValue; |
| // check values. TODO make into unit test. |
| // |
| // long visiblePower = (int) Math.pow(10, v); |
| // if (fractionalDigits > visiblePower) { |
| // throw new IllegalArgumentException(); |
| // } |
| // double fraction = intValue + (fractionalDigits / (double) visiblePower); |
| // if (fraction != source) { |
| // double diff = Math.abs(fraction - source)/(Math.abs(fraction) + Math.abs(source)); |
| // if (diff > 0.00000001d) { |
| // throw new IllegalArgumentException(); |
| // } |
| // } |
| if (f == 0) { |
| decimalDigitsWithoutTrailingZeros = 0; |
| visibleDecimalDigitCountWithoutTrailingZeros = 0; |
| } else { |
| long fdwtz = f; |
| int trimmedCount = v; |
| while ((fdwtz%10) == 0) { |
| fdwtz /= 10; |
| --trimmedCount; |
| } |
| decimalDigitsWithoutTrailingZeros = fdwtz; |
| visibleDecimalDigitCountWithoutTrailingZeros = trimmedCount; |
| } |
| baseFactor = (int) Math.pow(10, v); |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public FixedDecimal(double n, int v) { |
| this(n,v,getFractionalDigits(n, v)); |
| } |
| |
| private static int getFractionalDigits(double n, int v) { |
| if (v == 0) { |
| return 0; |
| } else { |
| if (n < 0) { |
| n = -n; |
| } |
| int baseFactor = (int) Math.pow(10, v); |
| long scaled = Math.round(n * baseFactor); |
| return (int) (scaled % baseFactor); |
| } |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public FixedDecimal(double n) { |
| this(n, decimals(n)); |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public FixedDecimal(long n) { |
| this(n,0); |
| } |
| |
| private static final long MAX_INTEGER_PART = 1000000000; |
| /** |
| * Return a guess as to the number of decimals that would be displayed. This is only a guess; callers should |
| * always supply the decimals explicitly if possible. Currently, it is up to 6 decimals (without trailing zeros). |
| * Returns 0 for infinities and nans. |
| * @internal |
| * @deprecated This API is ICU internal only. |
| * |
| */ |
| @Deprecated |
| public static int decimals(double n) { |
| // Ugly... |
| if (Double.isInfinite(n) || Double.isNaN(n)) { |
| return 0; |
| } |
| if (n < 0) { |
| n = -n; |
| } |
| if (n == Math.floor(n)) { |
| return 0; |
| } |
| if (n < MAX_INTEGER_PART) { |
| long temp = (long)(n * 1000000) % 1000000; // get 6 decimals |
| for (int mask = 10, digits = 6; digits > 0; mask *= 10, --digits) { |
| if ((temp % mask) != 0) { |
| return digits; |
| } |
| } |
| return 0; |
| } else { |
| String buf = String.format(Locale.ENGLISH, "%1.15e", n); |
| int ePos = buf.lastIndexOf('e'); |
| int expNumPos = ePos + 1; |
| if (buf.charAt(expNumPos) == '+') { |
| expNumPos++; |
| } |
| String exponentStr = buf.substring(expNumPos); |
| int exponent = Integer.parseInt(exponentStr); |
| int numFractionDigits = ePos - 2 - exponent; |
| if (numFractionDigits < 0) { |
| return 0; |
| } |
| for (int i=ePos-1; numFractionDigits > 0; --i) { |
| if (buf.charAt(i) != '0') { |
| break; |
| } |
| --numFractionDigits; |
| } |
| return numFractionDigits; |
| } |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public FixedDecimal (String n) { |
| // Ugly, but for samples we don't care. |
| this(Double.parseDouble(n), getVisibleFractionCount(n)); |
| } |
| |
| private static int getVisibleFractionCount(String value) { |
| value = value.trim(); |
| int decimalPos = value.indexOf('.') + 1; |
| if (decimalPos == 0) { |
| return 0; |
| } else { |
| return value.length() - decimalPos; |
| } |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public double get(Operand operand) { |
| switch(operand) { |
| default: return source; |
| case i: return integerValue; |
| case f: return decimalDigits; |
| case t: return decimalDigitsWithoutTrailingZeros; |
| case v: return visibleDecimalDigitCount; |
| case w: return visibleDecimalDigitCountWithoutTrailingZeros; |
| } |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public static Operand getOperand(String t) { |
| return Operand.valueOf(t); |
| } |
| |
| /** |
| * We're not going to care about NaN. |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public int compareTo(FixedDecimal other) { |
| if (integerValue != other.integerValue) { |
| return integerValue < other.integerValue ? -1 : 1; |
| } |
| if (source != other.source) { |
| return source < other.source ? -1 : 1; |
| } |
| if (visibleDecimalDigitCount != other.visibleDecimalDigitCount) { |
| return visibleDecimalDigitCount < other.visibleDecimalDigitCount ? -1 : 1; |
| } |
| long diff = decimalDigits - other.decimalDigits; |
| if (diff != 0) { |
| return diff < 0 ? -1 : 1; |
| } |
| return 0; |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| @Override |
| public boolean equals(Object arg0) { |
| if (arg0 == null) { |
| return false; |
| } |
| if (arg0 == this) { |
| return true; |
| } |
| if (!(arg0 instanceof FixedDecimal)) { |
| return false; |
| } |
| FixedDecimal other = (FixedDecimal)arg0; |
| return source == other.source && visibleDecimalDigitCount == other.visibleDecimalDigitCount && decimalDigits == other.decimalDigits; |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| @Override |
| public int hashCode() { |
| // TODO Auto-generated method stub |
| return (int)(decimalDigits + 37 * (visibleDecimalDigitCount + (int)(37 * source))); |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| @Override |
| public String toString() { |
| return String.format("%." + visibleDecimalDigitCount + "f", source); |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public boolean hasIntegerValue() { |
| return hasIntegerValue; |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| @Override |
| public int intValue() { |
| // TODO Auto-generated method stub |
| return (int)integerValue; |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| @Override |
| public long longValue() { |
| return integerValue; |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| @Override |
| public float floatValue() { |
| return (float) source; |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| @Override |
| public double doubleValue() { |
| return isNegative ? -source : source; |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public long getShiftedValue() { |
| return integerValue * baseFactor + decimalDigits; |
| } |
| |
| private void writeObject( |
| ObjectOutputStream out) |
| throws IOException { |
| throw new NotSerializableException(); |
| } |
| |
| private void readObject(ObjectInputStream in |
| ) throws IOException, ClassNotFoundException { |
| throw new NotSerializableException(); |
| } |
| } |
| |
| /** |
| * Selection parameter for either integer-only or decimal-only. |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public enum SampleType { |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| INTEGER, |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| DECIMAL |
| } |
| |
| /** |
| * A range of NumberInfo that includes all values with the same visibleFractionDigitCount. |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public static class FixedDecimalRange { |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public final FixedDecimal start; |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public final FixedDecimal end; |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public FixedDecimalRange(FixedDecimal start, FixedDecimal end) { |
| if (start.visibleDecimalDigitCount != end.visibleDecimalDigitCount) { |
| throw new IllegalArgumentException("Ranges must have the same number of visible decimals: " + start + "~" + end); |
| } |
| this.start = start; |
| this.end = end; |
| } |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| @Override |
| public String toString() { |
| return start + (end == start ? "" : "~" + end); |
| } |
| } |
| |
| /** |
| * A list of NumberInfo that includes all values with the same visibleFractionDigitCount. |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public static class FixedDecimalSamples { |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public final SampleType sampleType; |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public final Set<FixedDecimalRange> samples; |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public final boolean bounded; |
| /** |
| * The samples must be immutable. |
| * @param sampleType |
| * @param samples |
| */ |
| private FixedDecimalSamples(SampleType sampleType, Set<FixedDecimalRange> samples, boolean bounded) { |
| super(); |
| this.sampleType = sampleType; |
| this.samples = samples; |
| this.bounded = bounded; |
| } |
| /* |
| * Parse a list of the form described in CLDR. The source must be trimmed. |
| */ |
| static FixedDecimalSamples parse(String source) { |
| SampleType sampleType2; |
| boolean bounded2 = true; |
| boolean haveBound = false; |
| Set<FixedDecimalRange> samples2 = new LinkedHashSet<FixedDecimalRange>(); |
| |
| if (source.startsWith("integer")) { |
| sampleType2 = SampleType.INTEGER; |
| } else if (source.startsWith("decimal")) { |
| sampleType2 = SampleType.DECIMAL; |
| } else { |
| throw new IllegalArgumentException("Samples must start with 'integer' or 'decimal'"); |
| } |
| source = source.substring(7).trim(); // remove both |
| |
| for (String range : COMMA_SEPARATED.split(source)) { |
| if (range.equals("…") || range.equals("...")) { |
| bounded2 = false; |
| haveBound = true; |
| continue; |
| } |
| if (haveBound) { |
| throw new IllegalArgumentException("Can only have … at the end of samples: " + range); |
| } |
| String[] rangeParts = TILDE_SEPARATED.split(range); |
| switch (rangeParts.length) { |
| case 1: |
| FixedDecimal sample = new FixedDecimal(rangeParts[0]); |
| checkDecimal(sampleType2, sample); |
| samples2.add(new FixedDecimalRange(sample, sample)); |
| break; |
| case 2: |
| FixedDecimal start = new FixedDecimal(rangeParts[0]); |
| FixedDecimal end = new FixedDecimal(rangeParts[1]); |
| checkDecimal(sampleType2, start); |
| checkDecimal(sampleType2, end); |
| samples2.add(new FixedDecimalRange(start, end)); |
| break; |
| default: throw new IllegalArgumentException("Ill-formed number range: " + range); |
| } |
| } |
| return new FixedDecimalSamples(sampleType2, Collections.unmodifiableSet(samples2), bounded2); |
| } |
| |
| private static void checkDecimal(SampleType sampleType2, FixedDecimal sample) { |
| if ((sampleType2 == SampleType.INTEGER) != (sample.getVisibleDecimalDigitCount() == 0)) { |
| throw new IllegalArgumentException("Ill-formed number range: " + sample); |
| } |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public Set<Double> addSamples(Set<Double> result) { |
| for (FixedDecimalRange item : samples) { |
| // we have to convert to longs so we don't get strange double issues |
| long startDouble = item.start.getShiftedValue(); |
| long endDouble = item.end.getShiftedValue(); |
| |
| for (long d = startDouble; d <= endDouble; d += 1) { |
| result.add(d/(double)item.start.baseFactor); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| @Override |
| public String toString() { |
| StringBuilder b = new StringBuilder("@").append(sampleType.toString().toLowerCase(Locale.ENGLISH)); |
| boolean first = true; |
| for (FixedDecimalRange item : samples) { |
| if (first) { |
| first = false; |
| } else { |
| b.append(","); |
| } |
| b.append(' ').append(item); |
| } |
| if (!bounded) { |
| b.append(", …"); |
| } |
| return b.toString(); |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public Set<FixedDecimalRange> getSamples() { |
| return samples; |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public void getStartEndSamples(Set<FixedDecimal> target) { |
| for (FixedDecimalRange item : samples) { |
| target.add(item.start); |
| target.add(item.end); |
| } |
| } |
| } |
| |
| /* |
| * A constraint on a number. |
| */ |
| private interface Constraint extends Serializable { |
| /* |
| * Returns true if the number fulfills the constraint. |
| * @param n the number to test, >= 0. |
| */ |
| boolean isFulfilled(FixedDecimal n); |
| |
| /* |
| * Returns false if an unlimited number of values fulfills the |
| * constraint. |
| */ |
| boolean isLimited(SampleType sampleType); |
| } |
| |
| static class SimpleTokenizer { |
| static final UnicodeSet BREAK_AND_IGNORE = new UnicodeSet(0x09, 0x0a, 0x0c, 0x0d, 0x20, 0x20).freeze(); |
| static final UnicodeSet BREAK_AND_KEEP = new UnicodeSet('!', '!', '%', '%', ',', ',', '.', '.', '=', '=').freeze(); |
| static String[] split(String source) { |
| int last = -1; |
| List<String> result = new ArrayList<String>(); |
| for (int i = 0; i < source.length(); ++i) { |
| char ch = source.charAt(i); |
| if (BREAK_AND_IGNORE.contains(ch)) { |
| if (last >= 0) { |
| result.add(source.substring(last,i)); |
| last = -1; |
| } |
| } else if (BREAK_AND_KEEP.contains(ch)) { |
| if (last >= 0) { |
| result.add(source.substring(last,i)); |
| } |
| result.add(source.substring(i,i+1)); |
| last = -1; |
| } else if (last < 0) { |
| last = i; |
| } |
| } |
| if (last >= 0) { |
| result.add(source.substring(last)); |
| } |
| return result.toArray(new String[result.size()]); |
| } |
| } |
| |
| /* |
| * syntax: |
| * condition : or_condition |
| * and_condition |
| * or_condition : and_condition 'or' condition |
| * and_condition : relation |
| * relation 'and' relation |
| * relation : in_relation |
| * within_relation |
| * in_relation : not? expr not? in not? range |
| * within_relation : not? expr not? 'within' not? range |
| * not : 'not' |
| * '!' |
| * expr : 'n' |
| * 'n' mod value |
| * mod : 'mod' |
| * '%' |
| * in : 'in' |
| * 'is' |
| * '=' |
| * '≠' |
| * value : digit+ |
| * digit : 0|1|2|3|4|5|6|7|8|9 |
| * range : value'..'value |
| */ |
| private static Constraint parseConstraint(String description) |
| throws ParseException { |
| |
| Constraint result = null; |
| String[] or_together = OR_SEPARATED.split(description); |
| for (int i = 0; i < or_together.length; ++i) { |
| Constraint andConstraint = null; |
| String[] and_together = AND_SEPARATED.split(or_together[i]); |
| for (int j = 0; j < and_together.length; ++j) { |
| Constraint newConstraint = NO_CONSTRAINT; |
| |
| String condition = and_together[j].trim(); |
| String[] tokens = SimpleTokenizer.split(condition); |
| |
| int mod = 0; |
| boolean inRange = true; |
| boolean integersOnly = true; |
| double lowBound = Long.MAX_VALUE; |
| double highBound = Long.MIN_VALUE; |
| long[] vals = null; |
| |
| int x = 0; |
| String t = tokens[x++]; |
| boolean hackForCompatibility = false; |
| Operand operand; |
| try { |
| operand = FixedDecimal.getOperand(t); |
| } catch (Exception e) { |
| throw unexpected(t, condition); |
| } |
| if (x < tokens.length) { |
| t = tokens[x++]; |
| if ("mod".equals(t) || "%".equals(t)) { |
| mod = Integer.parseInt(tokens[x++]); |
| t = nextToken(tokens, x++, condition); |
| } |
| if ("not".equals(t)) { |
| inRange = !inRange; |
| t = nextToken(tokens, x++, condition); |
| if ("=".equals(t)) { |
| throw unexpected(t, condition); |
| } |
| } else if ("!".equals(t)) { |
| inRange = !inRange; |
| t = nextToken(tokens, x++, condition); |
| if (!"=".equals(t)) { |
| throw unexpected(t, condition); |
| } |
| } |
| if ("is".equals(t) || "in".equals(t) || "=".equals(t)) { |
| hackForCompatibility = "is".equals(t); |
| if (hackForCompatibility && !inRange) { |
| throw unexpected(t, condition); |
| } |
| t = nextToken(tokens, x++, condition); |
| } else if ("within".equals(t)) { |
| integersOnly = false; |
| t = nextToken(tokens, x++, condition); |
| } else { |
| throw unexpected(t, condition); |
| } |
| if ("not".equals(t)) { |
| if (!hackForCompatibility && !inRange) { |
| throw unexpected(t, condition); |
| } |
| inRange = !inRange; |
| t = nextToken(tokens, x++, condition); |
| } |
| |
| List<Long> valueList = new ArrayList<Long>(); |
| |
| // the token t is always one item ahead |
| while (true) { |
| long low = Long.parseLong(t); |
| long high = low; |
| if (x < tokens.length) { |
| t = nextToken(tokens, x++, condition); |
| if (t.equals(".")) { |
| t = nextToken(tokens, x++, condition); |
| if (!t.equals(".")) { |
| throw unexpected(t, condition); |
| } |
| t = nextToken(tokens, x++, condition); |
| high = Long.parseLong(t); |
| if (x < tokens.length) { |
| t = nextToken(tokens, x++, condition); |
| if (!t.equals(",")) { // adjacent number: 1 2 |
| // no separator, fail |
| throw unexpected(t, condition); |
| } |
| } |
| } else if (!t.equals(",")) { // adjacent number: 1 2 |
| // no separator, fail |
| throw unexpected(t, condition); |
| } |
| } |
| // at this point, either we are out of tokens, or t is ',' |
| if (low > high) { |
| throw unexpected(low + "~" + high, condition); |
| } else if (mod != 0 && high >= mod) { |
| throw unexpected(high + ">mod=" + mod, condition); |
| } |
| valueList.add(low); |
| valueList.add(high); |
| lowBound = Math.min(lowBound, low); |
| highBound = Math.max(highBound, high); |
| if (x >= tokens.length) { |
| break; |
| } |
| t = nextToken(tokens, x++, condition); |
| } |
| |
| if (t.equals(",")) { |
| throw unexpected(t, condition); |
| } |
| |
| if (valueList.size() == 2) { |
| vals = null; |
| } else { |
| vals = new long[valueList.size()]; |
| for (int k = 0; k < vals.length; ++k) { |
| vals[k] = valueList.get(k); |
| } |
| } |
| |
| // Hack to exclude "is not 1,2" |
| if (lowBound != highBound && hackForCompatibility && !inRange) { |
| throw unexpected("is not <range>", condition); |
| } |
| |
| newConstraint = |
| new RangeConstraint(mod, inRange, operand, integersOnly, lowBound, highBound, vals); |
| } |
| |
| if (andConstraint == null) { |
| andConstraint = newConstraint; |
| } else { |
| andConstraint = new AndConstraint(andConstraint, |
| newConstraint); |
| } |
| } |
| |
| if (result == null) { |
| result = andConstraint; |
| } else { |
| result = new OrConstraint(result, andConstraint); |
| } |
| } |
| return result; |
| } |
| |
| static final Pattern AT_SEPARATED = Pattern.compile("\\s*\\Q\\E@\\s*"); |
| static final Pattern OR_SEPARATED = Pattern.compile("\\s*or\\s*"); |
| static final Pattern AND_SEPARATED = Pattern.compile("\\s*and\\s*"); |
| static final Pattern COMMA_SEPARATED = Pattern.compile("\\s*,\\s*"); |
| static final Pattern DOTDOT_SEPARATED = Pattern.compile("\\s*\\Q..\\E\\s*"); |
| static final Pattern TILDE_SEPARATED = Pattern.compile("\\s*~\\s*"); |
| static final Pattern SEMI_SEPARATED = Pattern.compile("\\s*;\\s*"); |
| |
| |
| /* Returns a parse exception wrapping the token and context strings. */ |
| private static ParseException unexpected(String token, String context) { |
| return new ParseException("unexpected token '" + token + |
| "' in '" + context + "'", -1); |
| } |
| |
| /* |
| * Returns the token at x if available, else throws a parse exception. |
| */ |
| private static String nextToken(String[] tokens, int x, String context) |
| throws ParseException { |
| if (x < tokens.length) { |
| return tokens[x]; |
| } |
| throw new ParseException("missing token at end of '" + context + "'", -1); |
| } |
| |
| /* |
| * Syntax: |
| * rule : keyword ':' condition |
| * keyword: <identifier> |
| */ |
| private static Rule parseRule(String description) throws ParseException { |
| if (description.length() == 0) { |
| return DEFAULT_RULE; |
| } |
| |
| description = description.toLowerCase(Locale.ENGLISH); |
| |
| int x = description.indexOf(':'); |
| if (x == -1) { |
| throw new ParseException("missing ':' in rule description '" + |
| description + "'", 0); |
| } |
| |
| String keyword = description.substring(0, x).trim(); |
| if (!isValidKeyword(keyword)) { |
| throw new ParseException("keyword '" + keyword + |
| " is not valid", 0); |
| } |
| |
| description = description.substring(x+1).trim(); |
| String[] constraintOrSamples = AT_SEPARATED.split(description); |
| boolean sampleFailure = false; |
| FixedDecimalSamples integerSamples = null, decimalSamples = null; |
| switch (constraintOrSamples.length) { |
| case 1: break; |
| case 2: |
| integerSamples = FixedDecimalSamples.parse(constraintOrSamples[1]); |
| if (integerSamples.sampleType == SampleType.DECIMAL) { |
| decimalSamples = integerSamples; |
| integerSamples = null; |
| } |
| break; |
| case 3: |
| integerSamples = FixedDecimalSamples.parse(constraintOrSamples[1]); |
| decimalSamples = FixedDecimalSamples.parse(constraintOrSamples[2]); |
| if (integerSamples.sampleType != SampleType.INTEGER || decimalSamples.sampleType != SampleType.DECIMAL) { |
| throw new IllegalArgumentException("Must have @integer then @decimal in " + description); |
| } |
| break; |
| default: |
| throw new IllegalArgumentException("Too many samples in " + description); |
| } |
| if (sampleFailure) { |
| throw new IllegalArgumentException("Ill-formed samples—'@' characters."); |
| } |
| |
| // 'other' is special, and must have no rules; all other keywords must have rules. |
| boolean isOther = keyword.equals("other"); |
| if (isOther != (constraintOrSamples[0].length() == 0)) { |
| throw new IllegalArgumentException("The keyword 'other' must have no constraints, just samples."); |
| } |
| |
| Constraint constraint; |
| if (isOther) { |
| constraint = NO_CONSTRAINT; |
| } else { |
| constraint = parseConstraint(constraintOrSamples[0]); |
| } |
| return new Rule(keyword, constraint, integerSamples, decimalSamples); |
| } |
| |
| |
| /* |
| * Syntax: |
| * rules : rule |
| * rule ';' rules |
| */ |
| private static RuleList parseRuleChain(String description) |
| throws ParseException { |
| RuleList result = new RuleList(); |
| // remove trailing ; |
| if (description.endsWith(";")) { |
| description = description.substring(0,description.length()-1); |
| } |
| String[] rules = SEMI_SEPARATED.split(description); |
| for (int i = 0; i < rules.length; ++i) { |
| Rule rule = parseRule(rules[i].trim()); |
| result.hasExplicitBoundingInfo |= rule.integerSamples != null || rule.decimalSamples != null; |
| result.addRule(rule); |
| } |
| return result.finish(); |
| } |
| |
| /* |
| * An implementation of Constraint representing a modulus, |
| * a range of values, and include/exclude. Provides lots of |
| * convenience factory methods. |
| */ |
| private static class RangeConstraint implements Constraint, Serializable { |
| private static final long serialVersionUID = 1; |
| |
| private final int mod; |
| private final boolean inRange; |
| private final boolean integersOnly; |
| private final double lowerBound; |
| private final double upperBound; |
| private final long[] range_list; |
| private final Operand operand; |
| |
| RangeConstraint(int mod, boolean inRange, Operand operand, boolean integersOnly, |
| double lowBound, double highBound, long[] vals) { |
| this.mod = mod; |
| this.inRange = inRange; |
| this.integersOnly = integersOnly; |
| this.lowerBound = lowBound; |
| this.upperBound = highBound; |
| this.range_list = vals; |
| this.operand = operand; |
| } |
| |
| public boolean isFulfilled(FixedDecimal number) { |
| double n = number.get(operand); |
| if ((integersOnly && (n - (long)n) != 0.0 |
| || operand == Operand.j && number.visibleDecimalDigitCount != 0)) { |
| return !inRange; |
| } |
| if (mod != 0) { |
| n = n % mod; // java % handles double numerator the way we want |
| } |
| boolean test = n >= lowerBound && n <= upperBound; |
| if (test && range_list != null) { |
| test = false; |
| for (int i = 0; !test && i < range_list.length; i += 2) { |
| test = n >= range_list[i] && n <= range_list[i+1]; |
| } |
| } |
| return inRange == test; |
| } |
| |
| public boolean isLimited(SampleType sampleType) { |
| boolean valueIsZero = lowerBound == upperBound && lowerBound == 0d; |
| boolean hasDecimals = |
| (operand == Operand.v || operand == Operand.w || operand == Operand.f || operand == Operand.t) |
| && inRange != valueIsZero; // either NOT f = zero or f = non-zero |
| switch (sampleType) { |
| case INTEGER: |
| return hasDecimals // will be empty |
| || (operand == Operand.n || operand == Operand.i || operand == Operand.j) |
| && mod == 0 |
| && inRange; |
| |
| case DECIMAL: |
| return (!hasDecimals || operand == Operand.n || operand == Operand.j) |
| && (integersOnly || lowerBound == upperBound) |
| && mod == 0 |
| && inRange; |
| } |
| return false; |
| } |
| |
| public String toString() { |
| StringBuilder result = new StringBuilder(); |
| result.append(operand); |
| if (mod != 0) { |
| result.append(" % ").append(mod); |
| } |
| boolean isList = lowerBound != upperBound; |
| result.append( |
| !isList ? (inRange ? " = " : " != ") |
| : integersOnly ? (inRange ? " = " : " != ") |
| : (inRange ? " within " : " not within ") |
| ); |
| if (range_list != null) { |
| for (int i = 0; i < range_list.length; i += 2) { |
| addRange(result, range_list[i], range_list[i+1], i != 0); |
| } |
| } 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. */ |
| private static abstract class BinaryConstraint implements Constraint, |
| Serializable { |
| private static final long serialVersionUID = 1; |
| protected final Constraint a; |
| protected final Constraint b; |
| |
| protected BinaryConstraint(Constraint a, Constraint b) { |
| this.a = a; |
| this.b = b; |
| } |
| } |
| |
| /* A constraint representing the logical and of two constraints. */ |
| private static class AndConstraint extends BinaryConstraint { |
| private static final long serialVersionUID = 7766999779862263523L; |
| |
| AndConstraint(Constraint a, Constraint b) { |
| super(a, b); |
| } |
| |
| public boolean isFulfilled(FixedDecimal n) { |
| return a.isFulfilled(n) |
| && b.isFulfilled(n); |
| } |
| |
| public boolean isLimited(SampleType sampleType) { |
| // we ignore the case where both a and b are unlimited but no values |
| // satisfy both-- we still consider this 'unlimited' |
| return a.isLimited(sampleType) |
| || b.isLimited(sampleType); |
| } |
| |
| public String toString() { |
| return a.toString() + " and " + b.toString(); |
| } |
| } |
| |
| /* A constraint representing the logical or of two constraints. */ |
| private static class OrConstraint extends BinaryConstraint { |
| private static final long serialVersionUID = 1405488568664762222L; |
| |
| OrConstraint(Constraint a, Constraint b) { |
| super(a, b); |
| } |
| |
| public boolean isFulfilled(FixedDecimal n) { |
| return a.isFulfilled(n) |
| || b.isFulfilled(n); |
| } |
| |
| public boolean isLimited(SampleType sampleType) { |
| return a.isLimited(sampleType) |
| && b.isLimited(sampleType); |
| } |
| |
| public String toString() { |
| return a.toString() + " or " + b.toString(); |
| } |
| } |
| |
| /* |
| * Implementation of Rule that uses a constraint. |
| * Provides 'and' and 'or' to combine constraints. Immutable. |
| */ |
| private static class Rule implements Serializable { |
| private static final long serialVersionUID = 1; |
| private final String keyword; |
| private final Constraint constraint; |
| private final FixedDecimalSamples integerSamples; |
| private final FixedDecimalSamples decimalSamples; |
| |
| public Rule(String keyword, Constraint constraint, FixedDecimalSamples integerSamples, FixedDecimalSamples decimalSamples) { |
| this.keyword = keyword; |
| this.constraint = constraint; |
| this.integerSamples = integerSamples; |
| this.decimalSamples = decimalSamples; |
| } |
| |
| @SuppressWarnings("unused") |
| public Rule and(Constraint c) { |
| return new Rule(keyword, new AndConstraint(constraint, c), integerSamples, decimalSamples); |
| } |
| |
| @SuppressWarnings("unused") |
| public Rule or(Constraint c) { |
| return new Rule(keyword, new OrConstraint(constraint, c), integerSamples, decimalSamples); |
| } |
| |
| public String getKeyword() { |
| return keyword; |
| } |
| |
| public boolean appliesTo(FixedDecimal n) { |
| return constraint.isFulfilled(n); |
| } |
| |
| public boolean isLimited(SampleType sampleType) { |
| return constraint.isLimited(sampleType); |
| } |
| |
| public String toString() { |
| return keyword + ": " + constraint.toString() |
| + (integerSamples == null ? "" : " " + integerSamples.toString()) |
| + (decimalSamples == null ? "" : " " + decimalSamples.toString()); |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| @Override |
| public int hashCode() { |
| return keyword.hashCode() ^ constraint.hashCode(); |
| } |
| |
| public String getConstraint() { |
| return constraint.toString(); |
| } |
| } |
| |
| private static class RuleList implements Serializable { |
| private boolean hasExplicitBoundingInfo = false; |
| private static final long serialVersionUID = 1; |
| private final List<Rule> rules = new ArrayList<Rule>(); |
| |
| public RuleList addRule(Rule nextRule) { |
| String keyword = nextRule.getKeyword(); |
| for (Rule rule : rules) { |
| if (keyword.equals(rule.getKeyword())) { |
| throw new IllegalArgumentException("Duplicate keyword: " + keyword); |
| } |
| } |
| rules.add(nextRule); |
| return this; |
| } |
| |
| public RuleList finish() throws ParseException { |
| // make sure that 'other' is present, and at the end. |
| Rule otherRule = null; |
| for (Iterator<Rule> it = rules.iterator(); it.hasNext();) { |
| Rule rule = it.next(); |
| if ("other".equals(rule.getKeyword())) { |
| otherRule = rule; |
| it.remove(); |
| } |
| } |
| if (otherRule == null) { |
| otherRule = parseRule("other:"); // make sure we have always have an 'other' a rule |
| } |
| rules.add(otherRule); |
| return this; |
| } |
| |
| private Rule selectRule(FixedDecimal n) { |
| for (Rule rule : rules) { |
| if (rule.appliesTo(n)) { |
| return rule; |
| } |
| } |
| return null; |
| } |
| |
| public String select(FixedDecimal n) { |
| if (Double.isInfinite(n.source) || Double.isNaN(n.source)) { |
| return KEYWORD_OTHER; |
| } |
| Rule r = selectRule(n); |
| return r.getKeyword(); |
| } |
| |
| public Set<String> getKeywords() { |
| Set<String> result = new LinkedHashSet<String>(); |
| for (Rule rule : rules) { |
| result.add(rule.getKeyword()); |
| } |
| // since we have explict 'other', we don't need this. |
| //result.add(KEYWORD_OTHER); |
| return result; |
| } |
| |
| public boolean isLimited(String keyword, SampleType sampleType) { |
| if (hasExplicitBoundingInfo) { |
| FixedDecimalSamples mySamples = getDecimalSamples(keyword, sampleType); |
| return mySamples == null ? true : mySamples.bounded; |
| } |
| |
| return computeLimited(keyword, sampleType); |
| } |
| |
| public boolean computeLimited(String keyword, SampleType sampleType) { |
| // if all rules with this keyword are limited, it's limited, |
| // and if there's no rule with this keyword, it's unlimited |
| boolean result = false; |
| for (Rule rule : rules) { |
| if (keyword.equals(rule.getKeyword())) { |
| if (!rule.isLimited(sampleType)) { |
| return false; |
| } |
| result = true; |
| } |
| } |
| return result; |
| } |
| |
| public String toString() { |
| StringBuilder builder = new StringBuilder(); |
| for (Rule rule : rules) { |
| if (builder.length() != 0) { |
| builder.append(CATEGORY_SEPARATOR); |
| } |
| builder.append(rule); |
| } |
| return builder.toString(); |
| } |
| |
| public String getRules(String keyword) { |
| for (Rule rule : rules) { |
| if (rule.getKeyword().equals(keyword)) { |
| return rule.getConstraint(); |
| } |
| } |
| return null; |
| } |
| |
| public boolean select(FixedDecimal sample, String keyword) { |
| for (Rule rule : rules) { |
| if (rule.getKeyword().equals(keyword) && rule.appliesTo(sample)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| public FixedDecimalSamples getDecimalSamples(String keyword, SampleType sampleType) { |
| for (Rule rule : rules) { |
| if (rule.getKeyword().equals(keyword)) { |
| return sampleType == SampleType.INTEGER ? rule.integerSamples : rule.decimalSamples; |
| } |
| } |
| return null; |
| } |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public enum StandardPluralCategories { |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| zero, |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| one, |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| two, |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| few, |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| many, |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| other; |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public static final List<StandardPluralCategories> VALUES |
| = Collections.unmodifiableList(Arrays.asList(values())); |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public static final int COUNT = values().length; |
| |
| static StandardPluralCategories forString(String s) { |
| StandardPluralCategories a; |
| try { |
| a = valueOf(s); |
| } catch (Exception e) { |
| return null; |
| } |
| return a; |
| } |
| } |
| |
| @SuppressWarnings("unused") |
| private boolean addConditional(Set<FixedDecimal> toAddTo, Set<FixedDecimal> others, double trial) { |
| boolean added; |
| FixedDecimal toAdd = new FixedDecimal(trial); |
| if (!toAddTo.contains(toAdd) && !others.contains(toAdd)) { |
| others.add(toAdd); |
| added = true; |
| } else { |
| added = false; |
| } |
| return added; |
| } |
| |
| |
| |
| // ------------------------------------------------------------------------- |
| // Static class methods. |
| // ------------------------------------------------------------------------- |
| |
| /** |
| * Provides access to the predefined cardinal-number <code>PluralRules</code> for a given |
| * locale. |
| * Same as forLocale(locale, PluralType.CARDINAL). |
| * |
| * <p>ICU defines plural rules for many locales based on CLDR <i>Language Plural Rules</i>. |
| * For these predefined rules, see CLDR page at |
| * http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html |
| * |
| * @param locale The locale for which a <code>PluralRules</code> object is |
| * returned. |
| * @return The predefined <code>PluralRules</code> object for this locale. |
| * If there's no predefined rules for this locale, the rules |
| * for the closest parent in the locale hierarchy that has one will |
| * be returned. The final fallback always returns the default |
| * rules. |
| * @stable ICU 3.8 |
| */ |
| public static PluralRules forLocale(ULocale locale) { |
| return Factory.getDefaultFactory().forLocale(locale, PluralType.CARDINAL); |
| } |
| |
| /** |
| * Provides access to the predefined cardinal-number <code>PluralRules</code> for a given |
| * JDK locale. |
| * Same as forLocale(locale, PluralType.CARDINAL). |
| * |
| * <p>ICU defines plural rules for many locales based on CLDR <i>Language Plural Rules</i>. |
| * For these predefined rules, see CLDR page at |
| * http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html |
| * |
| * @param locale The JDK locale for which a <code>PluralRules</code> object is |
| * returned. |
| * @return The predefined <code>PluralRules</code> object for this locale. |
| * If there's no predefined rules for this locale, the rules |
| * for the closest parent in the locale hierarchy that has one will |
| * be returned. The final fallback always returns the default |
| * rules. |
| * @draft ICU 54 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| public static PluralRules forLocale(Locale locale) { |
| return forLocale(ULocale.forLocale(locale)); |
| } |
| |
| /** |
| * Provides access to the predefined <code>PluralRules</code> for a given |
| * locale and the plural type. |
| * |
| * <p>ICU defines plural rules for many locales based on CLDR <i>Language Plural Rules</i>. |
| * For these predefined rules, see CLDR page at |
| * http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html |
| * |
| * @param locale The locale for which a <code>PluralRules</code> object is |
| * returned. |
| * @param type The plural type (e.g., cardinal or ordinal). |
| * @return The predefined <code>PluralRules</code> object for this locale. |
| * If there's no predefined rules for this locale, the rules |
| * for the closest parent in the locale hierarchy that has one will |
| * be returned. The final fallback always returns the default |
| * rules. |
| * @stable ICU 50 |
| */ |
| public static PluralRules forLocale(ULocale locale, PluralType type) { |
| return Factory.getDefaultFactory().forLocale(locale, type); |
| } |
| |
| /** |
| * Provides access to the predefined <code>PluralRules</code> for a given |
| * JDK locale and the plural type. |
| * |
| * <p>ICU defines plural rules for many locales based on CLDR <i>Language Plural Rules</i>. |
| * For these predefined rules, see CLDR page at |
| * http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html |
| * |
| * @param locale The JDK locale for which a <code>PluralRules</code> object is |
| * returned. |
| * @param type The plural type (e.g., cardinal or ordinal). |
| * @return The predefined <code>PluralRules</code> object for this locale. |
| * If there's no predefined rules for this locale, the rules |
| * for the closest parent in the locale hierarchy that has one will |
| * be returned. The final fallback always returns the default |
| * rules. |
| * @draft ICU 54 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| public static PluralRules forLocale(Locale locale, PluralType type) { |
| return forLocale(ULocale.forLocale(locale), type); |
| } |
| |
| /* |
| * Checks whether a token is a valid keyword. |
| * |
| * @param token the token to be checked |
| * @return true if the token is a valid keyword. |
| */ |
| private static boolean isValidKeyword(String token) { |
| return ALLOWED_ID.containsAll(token); |
| } |
| |
| /* |
| * Creates a new <code>PluralRules</code> object. Immutable. |
| */ |
| private PluralRules(RuleList rules) { |
| this.rules = rules; |
| this.keywords = Collections.unmodifiableSet(rules.getKeywords()); |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| @Override |
| public int hashCode() { |
| return rules.hashCode(); |
| } |
| /** |
| * 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. |
| * @stable ICU 4.0 |
| */ |
| public String select(double number) { |
| return rules.select(new FixedDecimal(number)); |
| } |
| |
| /** |
| * 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. |
| */ |
| @Deprecated |
| public String select(double number, int countVisibleFractionDigits, long fractionaldigits) { |
| return rules.select(new FixedDecimal(number, countVisibleFractionDigits, fractionaldigits)); |
| } |
| |
| /** |
| * Given a number information, returns the keyword of the first rule that applies to |
| * the number. |
| * |
| * @param sample The number information for which the rule has to be determined. |
| * @return The keyword of the selected rule. |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public String select(FixedDecimal sample) { |
| return rules.select(sample); |
| } |
| |
| |
| /** |
| * Given a number information, and keyword, return whether the keyword would match the number. |
| * |
| * @param sample The number information for which the rule has to be determined. |
| * @param keyword The keyword to filter on |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public boolean matches(FixedDecimal sample, String keyword) { |
| return rules.select(sample, keyword); |
| } |
| |
| /** |
| * Returns a set of all rule keywords used in this <code>PluralRules</code> |
| * object. The rule "other" is always present by default. |
| * |
| * @return The set of keywords. |
| * @stable ICU 3.8 |
| */ |
| public Set<String> getKeywords() { |
| return keywords; |
| } |
| |
| /** |
| * Returns the unique value that this keyword matches, or {@link #NO_UNIQUE_VALUE} |
| * if the keyword matches multiple values or is not defined for this PluralRules. |
| * |
| * @param keyword the keyword to check for a unique value |
| * @return The unique value for the keyword, or NO_UNIQUE_VALUE. |
| * @stable ICU 4.8 |
| */ |
| public double getUniqueKeywordValue(String keyword) { |
| Collection<Double> values = getAllKeywordValues(keyword); |
| if (values != null && values.size() == 1) { |
| return values.iterator().next(); |
| } |
| return NO_UNIQUE_VALUE; |
| } |
| |
| /** |
| * Returns all the values that trigger this keyword, or null if the number of such |
| * values is unlimited. |
| * |
| * @param keyword the keyword |
| * @return the values that trigger this keyword, or null. The returned collection |
| * is immutable. It will be empty if the keyword is not defined. |
| * @stable ICU 4.8 |
| */ |
| public Collection<Double> getAllKeywordValues(String keyword) { |
| return getAllKeywordValues(keyword, SampleType.INTEGER); |
| } |
| |
| /** |
| * Returns all the values that trigger this keyword, or null if the number of such |
| * values is unlimited. |
| * |
| * @param keyword the keyword |
| * @param type the type of samples requested, INTEGER or DECIMAL |
| * @return the values that trigger this keyword, or null. The returned collection |
| * is immutable. It will be empty if the keyword is not defined. |
| * |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public Collection<Double> getAllKeywordValues(String keyword, SampleType type) { |
| if (!isLimited(keyword, type)) { |
| return null; |
| } |
| Collection<Double> samples = getSamples(keyword, type); |
| return samples == null ? null : Collections.unmodifiableCollection(samples); |
| } |
| |
| /** |
| * Returns a list of integer 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. |
| * @stable ICU 4.8 |
| */ |
| public Collection<Double> getSamples(String keyword) { |
| return getSamples(keyword, SampleType.INTEGER); |
| } |
| |
| /** |
| * 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. The keyword might be defined, and yet have an empty set of samples, |
| * IF there are samples for the other sampleType. |
| * |
| * @param keyword the keyword to test |
| * @param sampleType the type of samples requested, INTEGER or DECIMAL |
| * @return a list of values matching the keyword. |
| * @internal |
| * @deprecated ICU internal only |
| */ |
| @Deprecated |
| public Collection<Double> getSamples(String keyword, SampleType sampleType) { |
| if (!keywords.contains(keyword)) { |
| return null; |
| } |
| Set<Double> result = new TreeSet<Double>(); |
| |
| if (rules.hasExplicitBoundingInfo) { |
| FixedDecimalSamples samples = rules.getDecimalSamples(keyword, sampleType); |
| return samples == null ? Collections.unmodifiableSet(result) |
| : Collections.unmodifiableSet(samples.addSamples(result)); |
| } |
| |
| // hack in case the rule is created without explicit samples |
| int maxCount = isLimited(keyword, sampleType) ? Integer.MAX_VALUE : 20; |
| |
| switch (sampleType) { |
| case INTEGER: |
| for (int i = 0; i < 200; ++i) { |
| if (!addSample(keyword, i, maxCount, result)) { |
| break; |
| } |
| } |
| addSample(keyword, 1000000, maxCount, result); // hack for Welsh |
| break; |
| case DECIMAL: |
| for (int i = 0; i < 2000; ++i) { |
| if (!addSample(keyword, new FixedDecimal(i/10d, 1), maxCount, result)) { |
| break; |
| } |
| } |
| addSample(keyword, new FixedDecimal(1000000d, 1), maxCount, result); // hack for Welsh |
| break; |
| } |
| return result.size() == 0 ? null : Collections.unmodifiableSet(result); |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public boolean addSample(String keyword, Number sample, int maxCount, Set<Double> result) { |
| String selectedKeyword = sample instanceof FixedDecimal ? select((FixedDecimal)sample) : select(sample.doubleValue()); |
| if (selectedKeyword.equals(keyword)) { |
| result.add(sample.doubleValue()); |
| if (--maxCount < 0) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Returns a list of values for which select() would return that keyword, |
| * or null if the keyword is not defined or no samples are available. |
| * 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 |
| * @param sampleType the type of samples requested, INTEGER or DECIMAL |
| * @return a list of values matching the keyword. |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public FixedDecimalSamples getDecimalSamples(String keyword, SampleType sampleType) { |
| return rules.getDecimalSamples(keyword, sampleType); |
| } |
| |
| /** |
| * Returns the set of locales for which PluralRules are known. |
| * @return the set of locales for which PluralRules are known, as a list |
| * @draft ICU 4.2 (retain) |
| * @provisional This API might change or be removed in a future release. |
| */ |
| public static ULocale[] getAvailableULocales() { |
| return Factory.getDefaultFactory().getAvailableULocales(); |
| } |
| |
| /** |
| * Returns the 'functionally equivalent' locale with respect to |
| * plural rules. Calling PluralRules.forLocale with the functionally equivalent |
| * locale, and with the provided locale, returns rules that behave the same. |
| * <br/> |
| * All locales with the same functionally equivalent locale have |
| * plural rules that behave the same. This is not exaustive; |
| * there may be other locales whose plural rules behave the same |
| * that do not have the same equivalent locale. |
| * |
| * @param locale the locale to check |
| * @param isAvailable if not null and of length > 0, this will hold 'true' at |
| * index 0 if locale is directly defined (without fallback) as having plural rules |
| * @return the functionally-equivalent locale |
| * @draft ICU 4.2 (retain) |
| * @provisional This API might change or be removed in a future release. |
| */ |
| public static ULocale getFunctionalEquivalent(ULocale locale, boolean[] isAvailable) { |
| return Factory.getDefaultFactory().getFunctionalEquivalent(locale, isAvailable); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @stable ICU 3.8 |
| */ |
| public String toString() { |
| return rules.toString(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @stable ICU 3.8 |
| */ |
| public boolean equals(Object rhs) { |
| return rhs instanceof PluralRules && equals((PluralRules)rhs); |
| } |
| |
| /** |
| * Returns true if rhs is equal to this. |
| * @param rhs the PluralRules to compare to. |
| * @return true if this and rhs are equal. |
| * @stable ICU 3.8 |
| */ |
| // TODO Optimize this |
| public boolean equals(PluralRules rhs) { |
| return rhs != null && toString().equals(rhs.toString()); |
| } |
| |
| /** |
| * Status of the keyword for the rules, given a set of explicit values. |
| * |
| * @draft ICU 50 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| public enum KeywordStatus { |
| /** |
| * The keyword is not valid for the rules. |
| * |
| * @draft ICU 50 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| INVALID, |
| /** |
| * The keyword is valid, but unused (it is covered by the explicit values, OR has no values for the given {@link SampleType}). |
| * |
| * @draft ICU 50 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| SUPPRESSED, |
| /** |
| * The keyword is valid, used, and has a single possible value (before considering explicit values). |
| * |
| * @draft ICU 50 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| UNIQUE, |
| /** |
| * The keyword is valid, used, not unique, and has a finite set of values. |
| * |
| * @draft ICU 50 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| BOUNDED, |
| /** |
| * The keyword is valid but not bounded; there indefinitely many matching values. |
| * |
| * @draft ICU 50 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| UNBOUNDED |
| } |
| |
| /** |
| * Find the status for the keyword, given a certain set of explicit values. |
| * |
| * @param keyword |
| * the particular keyword (call rules.getKeywords() to get the valid ones) |
| * @param offset |
| * the offset used, or 0.0d if not. Internally, the offset is subtracted from each explicit value before |
| * checking against the keyword values. |
| * @param explicits |
| * a set of Doubles that are used explicitly (eg [=0], "[=1]"). May be empty or null. |
| * @param uniqueValue |
| * If non null, set to the unique value. |
| * @return the KeywordStatus |
| * @draft ICU 50 |
| * @provisional This API might change or be removed in a future release. |
| */ |
| public KeywordStatus getKeywordStatus(String keyword, int offset, Set<Double> explicits, |
| Output<Double> uniqueValue) { |
| return getKeywordStatus(keyword, offset, explicits, uniqueValue, SampleType.INTEGER); |
| } |
| /** |
| * Find the status for the keyword, given a certain set of explicit values. |
| * |
| * @param keyword |
| * the particular keyword (call rules.getKeywords() to get the valid ones) |
| * @param offset |
| * the offset used, or 0.0d if not. Internally, the offset is subtracted from each explicit value before |
| * checking against the keyword values. |
| * @param explicits |
| * a set of Doubles that are used explicitly (eg [=0], "[=1]"). May be empty or null. |
| * @param sampleType |
| * request KeywordStatus relative to INTEGER or DECIMAL values |
| * @param uniqueValue |
| * If non null, set to the unique value. |
| * @return the KeywordStatus |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public KeywordStatus getKeywordStatus(String keyword, int offset, Set<Double> explicits, |
| Output<Double> uniqueValue, SampleType sampleType) { |
| if (uniqueValue != null) { |
| uniqueValue.value = null; |
| } |
| |
| if (!keywords.contains(keyword)) { |
| return KeywordStatus.INVALID; |
| } |
| |
| if (!isLimited(keyword, sampleType)) { |
| return KeywordStatus.UNBOUNDED; |
| } |
| |
| Collection<Double> values = getSamples(keyword, sampleType); |
| |
| int originalSize = values.size(); |
| |
| if (explicits == null) { |
| explicits = Collections.emptySet(); |
| } |
| |
| // Quick check on whether there are multiple elements |
| |
| if (originalSize > explicits.size()) { |
| if (originalSize == 1) { |
| if (uniqueValue != null) { |
| uniqueValue.value = values.iterator().next(); |
| } |
| return KeywordStatus.UNIQUE; |
| } |
| return KeywordStatus.BOUNDED; |
| } |
| |
| // Compute if the quick test is insufficient. |
| |
| HashSet<Double> subtractedSet = new HashSet<Double>(values); |
| for (Double explicit : explicits) { |
| subtractedSet.remove(explicit - offset); |
| } |
| if (subtractedSet.size() == 0) { |
| return KeywordStatus.SUPPRESSED; |
| } |
| |
| if (uniqueValue != null && subtractedSet.size() == 1) { |
| uniqueValue.value = subtractedSet.iterator().next(); |
| } |
| |
| return originalSize == 1 ? KeywordStatus.UNIQUE : KeywordStatus.BOUNDED; |
| } |
| |
| /** |
| * @internal |
| * @deprecated This API is ICU internal only. |
| */ |
| @Deprecated |
| public String getRules(String keyword) { |
| return rules.getRules(keyword); |
| } |
| |
| private void writeObject( |
| ObjectOutputStream out) |
| throws IOException { |
| throw new NotSerializableException(); |
| } |
| |
| private void readObject(ObjectInputStream in |
| ) throws IOException, ClassNotFoundException { |
| throw new NotSerializableException(); |
| } |
| |
| private Object writeReplace() throws ObjectStreamException { |
| return new PluralRulesSerialProxy(toString()); |
| } |
| |
| /** |
| * @internal |
| * @deprecated internal |
| */ |
| @Deprecated |
| public int compareTo(PluralRules other) { |
| return toString().compareTo(other.toString()); |
| } |
| |
| /** |
| * @internal |
| * @deprecated internal |
| */ |
| @Deprecated |
| public Boolean isLimited(String keyword) { |
| return rules.isLimited(keyword, SampleType.INTEGER); |
| } |
| |
| /** |
| * @internal |
| * @deprecated internal |
| */ |
| @Deprecated |
| public boolean isLimited(String keyword, SampleType sampleType) { |
| return rules.isLimited(keyword, sampleType); |
| } |
| |
| /** |
| * @internal |
| * @deprecated internal |
| */ |
| @Deprecated |
| public boolean computeLimited(String keyword, SampleType sampleType) { |
| return rules.computeLimited(keyword, sampleType); |
| } |
| } |