]> granicus.if.org Git - icu/commitdiff
ICU-8474 made syntax fixes in accordance with doc, adjusted code for currencies to...
authorMark Davis <mark@macchiato.com>
Fri, 19 Jul 2013 19:38:17 +0000 (19:38 +0000)
committerMark Davis <mark@macchiato.com>
Fri, 19 Jul 2013 19:38:17 +0000 (19:38 +0000)
X-SVN-Rev: 33945

icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java
icu4j/main/classes/core/src/com/ibm/icu/text/PluralRules.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/PluralRulesTest.java

index a99b4234ebdb782b9da37178031fd2dc09c41df8..32a60ea1bc00df76b6a29e6f81dd0430c620660e 100644 (file)
@@ -27,6 +27,7 @@ import com.ibm.icu.impl.Utility;
 import com.ibm.icu.lang.UCharacter;
 import com.ibm.icu.math.BigDecimal;
 import com.ibm.icu.math.MathContext;
+import com.ibm.icu.text.PluralRules.NumberInfo;
 import com.ibm.icu.util.Currency;
 import com.ibm.icu.util.CurrencyAmount;
 import com.ibm.icu.util.ULocale;
@@ -1216,18 +1217,62 @@ public class DecimalFormat extends NumberFormat {
     private StringBuffer subformat(int number, StringBuffer result, FieldPosition fieldPosition,
                                    boolean isNegative, boolean isInteger, boolean parseAttr) {
         if (currencySignCount == CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT) {
-            return subformat(currencyPluralInfo.select(number), result, fieldPosition, isNegative,
+            // compute the plural category from the digitList plus other settings
+            return subformat(getPluralCategory(number), result, fieldPosition, isNegative,
                              isInteger, parseAttr);
         } else {
             return subformat(result, fieldPosition, isNegative, isInteger, parseAttr);
         }
     }
 
+    /**
+     * This is ugly, but don't see a better way to do it without major restructuring of the code.
+     */
+    private String getPluralCategory(double number) {
+        // get the visible fractions and the number of fraction digits.
+        int fractionalDigitsInDigitList = digitList.count - digitList.decimalAt;
+        int v;
+        long f;
+        int maxFractionalDigits;
+        int minFractionalDigits;
+        if (useSignificantDigits) {
+            maxFractionalDigits = maxSignificantDigits - digitList.decimalAt;
+            minFractionalDigits = minSignificantDigits - digitList.decimalAt;
+            if (minFractionalDigits < 0) {
+                minFractionalDigits = 0;
+            }
+            if (maxFractionalDigits < 0) {
+                maxFractionalDigits = 0;
+            }
+        } else {
+            maxFractionalDigits = getMaximumFractionDigits();
+            minFractionalDigits = getMinimumFractionDigits();
+        }
+        v = fractionalDigitsInDigitList;
+        if (v < minFractionalDigits) {
+            v = minFractionalDigits;
+        } else if (v > maxFractionalDigits) {
+            v = maxFractionalDigits;
+        }
+        f = 0;
+        if (v > 0) {
+            for (int i = digitList.decimalAt; i < digitList.count; ++i) {
+                f *= 10;
+                f += digitList.digits[i];
+            }
+            for (int i = v; i < fractionalDigitsInDigitList; ++i) {
+                f *= 10;
+            }
+        }
+        return currencyPluralInfo.select(new NumberInfo(number, v, f));
+    }
+
     private StringBuffer subformat(double number, StringBuffer result, FieldPosition fieldPosition,
                                    boolean isNegative,
             boolean isInteger, boolean parseAttr) {
         if (currencySignCount == CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT) {
-            return subformat(currencyPluralInfo.select(number), result, fieldPosition, isNegative,
+            // compute the plural category from the digitList plus other settings
+            return subformat(getPluralCategory(number), result, fieldPosition, isNegative,
                              isInteger, parseAttr);
         } else {
             return subformat(result, fieldPosition, isNegative, isInteger, parseAttr);
index 469e7e469f8e4ae649c835e8dcd8fe8cf8431488..891070f186e7eb74b50943c0adcc5272610c8b33 100644 (file)
@@ -33,6 +33,7 @@ import com.ibm.icu.impl.PatternProps;
 import com.ibm.icu.impl.PluralRulesLoader;
 import com.ibm.icu.impl.Utility;
 import com.ibm.icu.util.Output;
+import com.ibm.icu.util.StringTokenizer;
 import com.ibm.icu.util.ULocale;
 
 /**
@@ -95,23 +96,24 @@ import com.ibm.icu.util.ULocale;
  * keyword       = &lt;identifier&gt;
  * condition     = and_condition ('or' and_condition)*
  * and_condition = relation ('and' relation)*
- * relation      = is_relation | in_relation | within_relation | 'n' <EOL>
- * is_relation   = expr 'is' ('not')? value
- * in_relation   = expr ('not')? 'in' range_list
- * within_relation = expr ('not')? 'within' range_list
- * expr          = ('n' | 'i' | 'f' | 'v') ('mod' value)?
+ * 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+)?
+ * 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, and v values are defined as follows:
+ * 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 fractional digits, as an integer.</li>
+ * <li>t to be the visible fractional 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>
@@ -228,7 +230,7 @@ public class PluralRules implements Serializable {
         public final PluralRules forLocale(ULocale locale) {
             return forLocale(locale, PluralType.CARDINAL);
         }
-        
+
         /**
          * Returns the locales for which there is plurals data.
          * 
@@ -254,7 +256,7 @@ public class PluralRules implements Serializable {
          * @internal
          */
         public abstract ULocale getFunctionalEquivalent(ULocale locale, boolean[] isAvailable);
-        
+
         /**
          * Returns the default factory.
          * @deprecated This API is ICU internal only.
@@ -263,7 +265,7 @@ public class PluralRules implements Serializable {
         public static PluralRulesLoader getDefaultFactory() {
             return PluralRulesLoader.loader;
         }
-        
+
         /**
          * Returns whether or not there are overrides.
          * @deprecated This API is ICU internal only.
@@ -445,6 +447,7 @@ public class PluralRules implements Serializable {
         n,
         i,
         f,
+        t,
         v,
         j;
     }
@@ -457,6 +460,7 @@ public class PluralRules implements Serializable {
         private final double source;
         private final int visibleFractionDigitCount;
         private final long fractionalDigits;
+        private final long fractionalDigitsWithoutTrailingZeros;
         private final long intValue;
         private final boolean hasIntegerValue;
         private final boolean isNegative;
@@ -485,6 +489,15 @@ public class PluralRules implements Serializable {
             //                    throw new IllegalArgumentException();
             //                }
             //            }
+            if (f == 0) {
+                fractionalDigitsWithoutTrailingZeros = 0;
+            } else {
+                long fdwtz = f;
+                while ((fdwtz%10) == 0) {
+                    fdwtz /= 10;
+                }
+                fractionalDigitsWithoutTrailingZeros = fdwtz;
+            }
         }
 
         /**
@@ -492,7 +505,6 @@ public class PluralRules implements Serializable {
          * @deprecated This API is ICU internal only.
          */
         public NumberInfo(double n, int v) {
-            // Ugly, but for samples we don't care.
             this(n,v,getFractionalDigits(n, v));
         }
 
@@ -519,7 +531,7 @@ public class PluralRules implements Serializable {
          * @deprecated This API is ICU internal only.
          */
         public static int decimals(double n) {
-            // Ugly, but for samples we don't care.
+            // Ugly...
             String temp = String.valueOf(n);
             return temp.endsWith(".0") ? 0 : temp.length() - temp.indexOf('.') - 1;
         }
@@ -560,6 +572,7 @@ public class PluralRules implements Serializable {
             default: return source;
             case i: return intValue;
             case f: return fractionalDigits;
+            case t: return fractionalDigitsWithoutTrailingZeros;
             case v: return visibleFractionDigitCount;
             }
         }
@@ -699,6 +712,36 @@ public class PluralRules implements Serializable {
         public String getConstraint();
     }
 
+    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()]);
+        }
+    }
+    
     //    /*
     //     * A list of rules to apply in order.
     //     */
@@ -728,26 +771,28 @@ public class PluralRules implements Serializable {
 
     /*
      * syntax:
-     * condition :     or_condition
-     *                 and_condition
-     * or_condition :  and_condition 'or' condition
-     * and_condition : relation
-     *                 relation 'and' relation
-     * relation :      is_relation
-     *                 in_relation
-     *                 within_relation
-     *                 'n' EOL
-     * is_relation :   expr 'is' value
-     *                 expr 'is' 'not' value
-     * in_relation :   expr 'in' range
-     *                 expr 'not' 'in' range
-     * within_relation : expr 'within' range
-     *                   expr 'not' 'within' range
-     * expr :          'n'
-     *                 'n' 'mod' value
-     * value :         digit+
-     * digit :         0|1|2|3|4|5|6|7|8|9
-     * range :         value'..'value
+     * 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 {
@@ -763,20 +808,22 @@ public class PluralRules implements Serializable {
                 Constraint newConstraint = NO_CONSTRAINT;
 
                 String condition = and_together[j].trim();
-                String[] tokens = Utility.splitWhitespace(condition);
+                String[] tokens = SimpleTokenizer.split(condition);
 
                 int mod = 0;
                 boolean inRange = true;
                 boolean integersOnly = true;
                 double lowBound = Long.MAX_VALUE;
                 double highBound = Long.MIN_VALUE;
-                double[] vals = null;
-
-                boolean isRange = false;
+                long[] vals = null;
 
                 int x = 0;
                 String t = tokens[x++];
                 Operand operand;
+                if ("not".equals(t) || "!".equals(t)) {
+                    inRange = !inRange;
+                    t = nextToken(tokens, x++, condition);
+                }
                 try {
                     operand = NumberInfo.getOperand(t);
                 } catch (Exception e) {
@@ -784,60 +831,54 @@ public class PluralRules implements Serializable {
                 }
                 if (x < tokens.length) {
                     t = tokens[x++];
-                    if ("mod".equals(t)) {
+                    if ("mod".equals(t) || "%".equals(t)) {
                         mod = Integer.parseInt(tokens[x++]);
                         t = nextToken(tokens, x++, condition);
                     }
-                    if ("is".equals(t)) {
+                    if ("not".equals(t) || "!".equals(t)) {
+                        inRange = !inRange;
+                        t = nextToken(tokens, x++, condition);
+                    }
+                    if ("is".equals(t) || "in".equals(t) || "=".equals(t)) {
+                        t = nextToken(tokens, x++, condition);
+                    } else if ("≠".equals(t) || "!=".equals(t)) {
+                        inRange = !inRange;
+                        t = nextToken(tokens, x++, condition);
+                    } else if ("within".equals(t)) {
+                        integersOnly = false;
                         t = nextToken(tokens, x++, condition);
-                        if ("not".equals(t)) {
-                            inRange = false;
-                            t = nextToken(tokens, x++, condition);
-                        }
                     } else {
-                        isRange = true;
-                        if ("not".equals(t)) {
-                            inRange = false;
-                            t = nextToken(tokens, x++, condition);
-                        }
-                        if ("in".equals(t)) {
-                            t = nextToken(tokens, x++, condition);
-                        } else if ("within".equals(t)) {
-                            integersOnly = false;
-                            t = nextToken(tokens, x++, condition);
-                        } else {
-                            throw unexpected(t, condition);
-                        }
+                        throw unexpected(t, condition);
+                    }
+                    if ("not".equals(t) || "!".equals(t)) {
+                        inRange = !inRange;
+                        t = nextToken(tokens, x++, condition);
                     }
 
-                    if (isRange) {
-                        String[] range_list = Utility.splitString(t, ",");
-                        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, "..");
-                            double low, high;
-                            if (pair.length == 2) {
-                                low = Double.parseDouble(pair[0]);
-                                high = Double.parseDouble(pair[1]);
-                                if (low > high) {
-                                    throw unexpected(range, condition);
-                                }
-                            } else if (pair.length == 1) {
-                                low = high = Double.parseDouble(pair[0]);
-                            } else {
+                    String[] range_list = Utility.splitString(t, ",");
+                    vals = new long[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;
+                        if (pair.length == 2) {
+                            low = Long.parseLong(pair[0]);
+                            high = Long.parseLong(pair[1]);
+                            if (low > high) {
                                 throw unexpected(range, condition);
                             }
-                            vals[k2] = low;
-                            vals[k2+1] = high;
-                            lowBound = Math.min(lowBound, low);
-                            highBound = Math.max(highBound, high);
-                        }
-                        if (vals.length == 2) {
-                            vals = null;
+                        } else if (pair.length == 1) {
+                            low = high = Long.parseLong(pair[0]);
+                        } else {
+                            throw unexpected(range, condition);
                         }
-                    } else {
-                        lowBound = highBound = Double.parseDouble(t);
+                        vals[k2] = low;
+                        vals[k2+1] = high;
+                        lowBound = Math.min(lowBound, low);
+                        highBound = Math.max(highBound, high);
+                    }
+                    if (vals.length == 2) {
+                        vals = null;
                     }
 
                     if (x != tokens.length) {
@@ -943,11 +984,11 @@ public class PluralRules implements Serializable {
         private final boolean integersOnly;
         private final double lowerBound;
         private final double upperBound;
-        private final double[] range_list;
+        private final long[] range_list;
         private final Operand operand;
 
         RangeConstraint(int mod, boolean inRange, Operand operand, boolean integersOnly,
-                double lowBound, double highBound, double[] vals) {
+                double lowBound, double highBound, long[] vals) {
             this.mod = mod;
             this.inRange = inRange;
             this.integersOnly = integersOnly;
@@ -1491,7 +1532,7 @@ public class PluralRules implements Serializable {
         return rules.select(new NumberInfo(number, countVisibleFractionDigits, fractionaldigits));
     }
 
-    
+
     /**
      * Given a number information, returns the keyword of the first rule that applies to
      * the number.
index 47dc01b1cbe95b968a18d32df70c2b27762b91b5..73e8090a73c668408281223dc45e549096dceb4b 100644 (file)
@@ -34,6 +34,8 @@ import com.ibm.icu.text.MeasureFormat;
 import com.ibm.icu.text.NumberFormat;
 import com.ibm.icu.text.NumberFormat.NumberFormatFactory;
 import com.ibm.icu.text.NumberFormat.SimpleNumberFormatFactory;
+import com.ibm.icu.text.PluralRules;
+import com.ibm.icu.text.PluralRules.NumberInfo;
 import com.ibm.icu.util.Currency;
 import com.ibm.icu.util.CurrencyAmount;
 import com.ibm.icu.util.ULocale;
@@ -3287,4 +3289,34 @@ public class NumberFormatTest extends com.ibm.icu.dev.test.TestFmwk {
         numberFormat.setMaximumSignificantDigits(3);
         assertEquals("TestShowZero", "0", numberFormat.format(0.0));
     }
+    
+    public void TestCurrencyPlurals() {
+        String[][] tests = {
+                {"en", "USD", "1", "1 US dollar"},
+                {"en", "USD", "1.0", "1.0 US dollars"},
+                {"en", "USD", "1.00", "1.00 US dollars"},
+                {"en", "AUD", "1", "1 Australian dollar"},
+                {"en", "AUD", "1.00", "1.00 Australian dollars"},
+                {"sl", "USD", "1", "1 ameriški dolar"},
+                {"sl", "USD", "2", "2 ameriška dolarja"},
+                {"sl", "USD", "3", "3 ameriški dolarji"},
+                {"sl", "USD", "5", "5 ameriških dolarjev"},
+        };
+        for (String test[] : tests) {
+            DecimalFormat numberFormat = (DecimalFormat) DecimalFormat.getInstance(new ULocale(test[0]), NumberFormat.PLURALCURRENCYSTYLE);
+            numberFormat.setCurrency(Currency.getInstance(test[1]));
+            double number = Double.parseDouble(test[2]);
+            int dotPos = test[2].indexOf('.');
+            int decimals = dotPos < 0 ? 0 : test[2].length() - dotPos - 1;
+            int digits = dotPos < 0 ? test[2].length() : test[2].length() - 1;
+            numberFormat.setMaximumFractionDigits(decimals);
+            numberFormat.setMinimumFractionDigits(decimals);
+            String actual = numberFormat.format(number);
+            assertEquals(test[0] + "\t" + test[1] + "\t" + test[2], test[3], actual);
+            numberFormat.setMaximumSignificantDigits(digits);
+            numberFormat.setMinimumSignificantDigits(digits);
+            actual = numberFormat.format(number);
+            assertEquals(test[0] + "\t" + test[1] + "\t" + test[2], test[3], actual);
+        }
+    }
 }
index b6165fcdffcc196bb01001b2c49ae4f1a4a44526..8759f3483158b6e097ac060f4011a52c795c1c24 100644 (file)
@@ -135,6 +135,11 @@ public class PluralRulesTest extends TestFmwk {
     }
 
     private static String[][] operandTestData = {
+        {"a: n 3", "FAIL"},
+        {"a: !n!≠!1,2; b: not n is 3..5; c:n≠5", "a:1,2; b:6,7; c:3,4"},
+        {"a: !n!≠!1,2; b: n!=3..5; c:n≠5", "a:1,2; b:6,7; c:3,4"},
+        {"a: t is 1", "a:1.1,1.1000,99.100; other:1.2,1.0"},
+        {"a: f is 1", "a:1.1; other:1.1000,99.100"},
         {"a: i is 2; b:i is 3", 
         "b: 3.5; a: 2.5"},
         {"a: f is 0; b:f is 50", 
@@ -156,13 +161,21 @@ public class PluralRulesTest extends TestFmwk {
             String categoriesAndExpected = pair[1].trim();
 
             //            logln("pattern[" + i + "] " + pattern);
+            boolean FAIL_EXPECTED = categoriesAndExpected.equalsIgnoreCase("fail");
             try {
+                logln(pattern);
                 PluralRules rules = PluralRules.createRules(pattern);
-                logln(rules.toString());
-                checkCategoriesAndExpected(pattern, categoriesAndExpected, rules);
+                if (FAIL_EXPECTED) {
+                    assertNull("Should fail with 'null' return.", rules);
+                } else {
+                    logln(rules.toString());
+                    checkCategoriesAndExpected(pattern, categoriesAndExpected, rules);
+                }
             } catch (Exception e) {
-                e.printStackTrace();
-                throw new RuntimeException(e.getMessage());
+                if (!FAIL_EXPECTED) {
+                    e.printStackTrace();
+                    throw new RuntimeException(e.getMessage());
+                }
             }
         }
     }
@@ -234,14 +247,14 @@ public class PluralRulesTest extends TestFmwk {
         { "a:n in 2;b:n in 5",
         "b: n in 5;a: n in 2;" },
 
-//        { "a: n is 5",
-//        "a: n in 2..6 and n not in 2..4 and n is not 6" },
-//        { "a: n in 2..3",
-//            "a: n is 2 or n is 3",
-//        "a: n is 3 and n in 2..5 or n is 2" },
-//        { "a: n is 12; b:n mod 10 in 2..3",
-//            "b: n mod 10 in 2..3 and n is not 12; a: n in 12..12",
-//        "b: n is 13; a: n is 12; b: n mod 10 is 2 or n mod 10 is 3" },
+        //        { "a: n is 5",
+        //        "a: n in 2..6 and n not in 2..4 and n is not 6" },
+        //        { "a: n in 2..3",
+        //            "a: n is 2 or n is 3",
+        //        "a: n is 3 and n in 2..5 or n is 2" },
+        //        { "a: n is 12; b:n mod 10 in 2..3",
+        //            "b: n mod 10 in 2..3 and n is not 12; a: n in 12..12",
+        //        "b: n is 13; a: n is 12; b: n mod 10 is 2 or n mod 10 is 3" },
     };
 
     private static String[][] inequalityTestData = {