1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html#License
3 /*
4  *******************************************************************************
5  * Copyright (C) 2013-2016, International Business Machines Corporation and
6  * others. All Rights Reserved.
7  *******************************************************************************
8  */
9 package com.ibm.icu.text;
10 
11 import java.text.FieldPosition;
12 
13 import com.ibm.icu.impl.SimpleFormatterImpl;
14 import com.ibm.icu.impl.StandardPlural;
15 import com.ibm.icu.text.PluralRules.FixedDecimal;
16 
17 /**
18  * QuantityFormatter represents an unknown quantity of something and formats a known quantity
19  * in terms of that something. For example, a QuantityFormatter that represents X apples may
20  * format 1 as "1 apple" and 3 as "3 apples"
21  * <p>
22  * QuanitityFormatter appears here instead of in com.ibm.icu.impl because it depends on
23  * PluralRules and DecimalFormat. It is package-protected as it is not meant for public use.
24  */
25 class QuantityFormatter {
26     private final SimpleFormatter[] templates =
27             new SimpleFormatter[StandardPlural.COUNT];
28 
QuantityFormatter()29     public QuantityFormatter() {}
30 
31     /**
32      * Adds a template if there is none yet for the plural form.
33      *
34      * @param variant the plural variant, e.g "zero", "one", "two", "few", "many", "other"
35      * @param template the text for that plural variant with "{0}" as the quantity. For
36      * example, in English, the template for the "one" variant may be "{0} apple" while the
37      * template for the "other" variant may be "{0} apples"
38      * @throws IllegalArgumentException if variant is not recognized or
39      *  if template has more than just the {0} placeholder.
40      */
addIfAbsent(CharSequence variant, String template)41     public void addIfAbsent(CharSequence variant, String template) {
42         int idx = StandardPlural.indexFromString(variant);
43         if (templates[idx] != null) {
44             return;
45         }
46         templates[idx] = SimpleFormatter.compileMinMaxArguments(template, 0, 1);
47     }
48 
49     /**
50      * @return true if this object has at least the "other" variant
51      */
isValid()52     public boolean isValid() {
53         return templates[StandardPlural.OTHER_INDEX] != null;
54     }
55 
56     /**
57      * Format formats a number with this object.
58      * @param number the number to be formatted
59      * @param numberFormat used to actually format the number.
60      * @param pluralRules uses the number and the numberFormat to determine what plural
61      *  variant to use for fetching the formatting template.
62      * @return the formatted string e.g '3 apples'
63      */
format(double number, NumberFormat numberFormat, PluralRules pluralRules)64     public String format(double number, NumberFormat numberFormat, PluralRules pluralRules) {
65         String formatStr = numberFormat.format(number);
66         StandardPlural p = selectPlural(number, numberFormat, pluralRules);
67         SimpleFormatter formatter = templates[p.ordinal()];
68         if (formatter == null) {
69             formatter = templates[StandardPlural.OTHER_INDEX];
70             assert formatter != null;
71         }
72         return formatter.format(formatStr);
73     }
74 
75     /**
76      * Gets the SimpleFormatter for a particular variant.
77      * @param variant "zero", "one", "two", "few", "many", "other"
78      * @return the SimpleFormatter
79      */
getByVariant(CharSequence variant)80     public SimpleFormatter getByVariant(CharSequence variant) {
81         assert isValid();
82         int idx = StandardPlural.indexOrOtherIndexFromString(variant);
83         SimpleFormatter template = templates[idx];
84         return (template == null && idx != StandardPlural.OTHER_INDEX) ?
85                 templates[StandardPlural.OTHER_INDEX] : template;
86     }
87 
88     // The following methods live here so that class PluralRules does not depend on number formatting,
89     // and the SimpleFormatter does not depend on FieldPosition.
90 
91     /**
92      * Selects the standard plural form for the number/formatter/rules.
93      */
selectPlural(double number, NumberFormat numberFormat, PluralRules rules)94     public static StandardPlural selectPlural(double number, NumberFormat numberFormat, PluralRules rules) {
95         String pluralKeyword;
96         if (numberFormat instanceof DecimalFormat) {
97             pluralKeyword = rules.select(((DecimalFormat) numberFormat).getFixedDecimal(number));
98         } else {
99             pluralKeyword = rules.select(number);
100         }
101         return StandardPlural.orOtherFromString(pluralKeyword);
102     }
103 
104     /**
105      * Selects the standard plural form for the number/formatter/rules.
106      */
selectPlural( Number number, NumberFormat fmt, PluralRules rules, StringBuffer formattedNumber, FieldPosition pos)107     public static StandardPlural selectPlural(
108             Number number, NumberFormat fmt, PluralRules rules,
109             StringBuffer formattedNumber, FieldPosition pos) {
110         UFieldPosition fpos = new UFieldPosition(pos.getFieldAttribute(), pos.getField());
111         fmt.format(number, formattedNumber, fpos);
112         // TODO: Long, BigDecimal & BigInteger may not fit into doubleValue().
113         FixedDecimal fd = new FixedDecimal(
114                 number.doubleValue(),
115                 fpos.getCountVisibleFractionDigits(), fpos.getFractionDigits());
116         String pluralKeyword = rules.select(fd);
117         pos.setBeginIndex(fpos.getBeginIndex());
118         pos.setEndIndex(fpos.getEndIndex());
119         return StandardPlural.orOtherFromString(pluralKeyword);
120     }
121 
122     /**
123      * Formats the pattern with the value and adjusts the FieldPosition.
124      */
format(String compiledPattern, CharSequence value, StringBuilder appendTo, FieldPosition pos)125     public static StringBuilder format(String compiledPattern, CharSequence value,
126             StringBuilder appendTo, FieldPosition pos) {
127         int[] offsets = new int[1];
128         SimpleFormatterImpl.formatAndAppend(compiledPattern, appendTo, offsets, value);
129         if (pos.getBeginIndex() != 0 || pos.getEndIndex() != 0) {
130             if (offsets[0] >= 0) {
131                 pos.setBeginIndex(pos.getBeginIndex() + offsets[0]);
132                 pos.setEndIndex(pos.getEndIndex() + offsets[0]);
133             } else {
134                 pos.setBeginIndex(0);
135                 pos.setEndIndex(0);
136             }
137         }
138         return appendTo;
139     }
140 }
141