• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GENERATED SOURCE. DO NOT MODIFY. */
2 // © 2016 and later: Unicode, Inc. and others.
3 // License & terms of use: http://www.unicode.org/copyright.html#License
4 /*
5  *******************************************************************************
6  * Copyright (C) 2007-2016, International Business Machines Corporation and
7  * others. All Rights Reserved.
8  *******************************************************************************
9  */
10 
11 package ohos.global.icu.text;
12 
13 import java.io.IOException;
14 import java.io.ObjectInputStream;
15 import java.text.FieldPosition;
16 import java.text.ParsePosition;
17 import java.util.Locale;
18 import java.util.Map;
19 import java.util.Objects;
20 
21 import ohos.global.icu.number.FormattedNumber;
22 import ohos.global.icu.number.LocalizedNumberFormatter;
23 import ohos.global.icu.text.PluralRules.FixedDecimal;
24 import ohos.global.icu.text.PluralRules.IFixedDecimal;
25 import ohos.global.icu.text.PluralRules.PluralType;
26 import ohos.global.icu.util.ULocale;
27 import ohos.global.icu.util.ULocale.Category;
28 
29 /**
30  * <code>PluralFormat</code> supports the creation of internationalized
31  * messages with plural inflection. It is based on <i>plural
32  * selection</i>, i.e. the caller specifies messages for each
33  * plural case that can appear in the user's language and the
34  * <code>PluralFormat</code> selects the appropriate message based on
35  * the number.
36  *
37  * <h3>The Problem of Plural Forms in Internationalized Messages</h3>
38  * <p>
39  * Different languages have different ways to inflect
40  * plurals. Creating internationalized messages that include plural
41  * forms is only feasible when the framework is able to handle plural
42  * forms of <i>all</i> languages correctly. <code>ChoiceFormat</code>
43  * doesn't handle this well, because it attaches a number interval to
44  * each message and selects the message whose interval contains a
45  * given number. This can only handle a finite number of
46  * intervals. But in some languages, like Polish, one plural case
47  * applies to infinitely many intervals (e.g., the paucal case applies to
48  * numbers ending with 2, 3, or 4 except those ending with 12, 13, or
49  * 14). Thus <code>ChoiceFormat</code> is not adequate.
50  * <p>
51  * <code>PluralFormat</code> deals with this by breaking the problem
52  * into two parts:
53  * <ul>
54  * <li>It uses <code>PluralRules</code> that can define more complex
55  *     conditions for a plural case than just a single interval. These plural
56  *     rules define both what plural cases exist in a language, and to
57  *     which numbers these cases apply.
58  * <li>It provides predefined plural rules for many languages. Thus, the programmer
59  *     need not worry about the plural cases of a language and
60  *     does not have to define the plural cases; they can simply
61  *     use the predefined keywords. The whole plural formatting of messages can
62  *     be done using localized patterns from resource bundles. For predefined plural
63  *     rules, see the CLDR <i>Language Plural Rules</i> page at
64  *    http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
65  * </ul>
66  *
67  * <h4>Usage of <code>PluralFormat</code></h4>
68  * <p>Note: Typically, plural formatting is done via <code>MessageFormat</code>
69  * with a <code>plural</code> argument type,
70  * rather than using a stand-alone <code>PluralFormat</code>.
71  * <p>
72  * This discussion assumes that you use <code>PluralFormat</code> with
73  * a predefined set of plural rules. You can create one using one of
74  * the constructors that takes a <code>ULocale</code> object. To
75  * specify the message pattern, you can either pass it to the
76  * constructor or set it explicitly using the
77  * <code>applyPattern()</code> method. The <code>format()</code>
78  * method takes a number object and selects the message of the
79  * matching plural case. This message will be returned.
80  *
81  * <h5>Patterns and Their Interpretation</h5>
82  * <p>
83  * The pattern text defines the message output for each plural case of the
84  * specified locale. Syntax:
85  * <blockquote><pre>
86  * pluralStyle = [offsetValue] (selector '{' message '}')+
87  * offsetValue = "offset:" number
88  * selector = explicitValue | keyword
89  * explicitValue = '=' number  // adjacent, no white space in between
90  * keyword = [^[[:Pattern_Syntax:][:Pattern_White_Space:]]]+
91  * message: see {@link MessageFormat}
92  * </pre></blockquote>
93  * Pattern_White_Space between syntax elements is ignored, except
94  * between the {curly braces} and their sub-message,
95  * and between the '=' and the number of an explicitValue.
96  * <p>
97  * There are 6 predefined case keywords in CLDR/ICU - 'zero', 'one', 'two', 'few', 'many' and
98  * 'other'. You always have to define a message text for the default plural case
99  * "<code>other</code>" which is contained in every rule set.
100  * If you do not specify a message text for a particular plural case, the
101  * message text of the plural case "<code>other</code>" gets assigned to this
102  * plural case.
103  * <p>
104  * When formatting, the input number is first matched against the explicitValue clauses.
105  * If there is no exact-number match, then a keyword is selected by calling
106  * the <code>PluralRules</code> with the input number <em>minus the offset</em>.
107  * (The offset defaults to 0 if it is omitted from the pattern string.)
108  * If there is no clause with that keyword, then the "other" clauses is returned.
109  * <p>
110  * An unquoted pound sign (<code>#</code>) in the selected sub-message
111  * itself (i.e., outside of arguments nested in the sub-message)
112  * is replaced by the input number minus the offset.
113  * The number-minus-offset value is formatted using a
114  * <code>NumberFormat</code> for the <code>PluralFormat</code>'s locale. If you
115  * need special number formatting, you have to use a <code>MessageFormat</code>
116  * and explicitly specify a <code>NumberFormat</code> argument.
117  * <strong>Note:</strong> That argument is formatting without subtracting the offset!
118  * If you need a custom format and have a non-zero offset, then you need to pass the
119  * number-minus-offset value as a separate parameter.
120  *
121  * <p>For a usage example, see the {@link MessageFormat} class documentation.
122  *
123  * <h4>Defining Custom Plural Rules</h4>
124  * <p>If you need to use <code>PluralFormat</code> with custom rules, you can
125  * create a <code>PluralRules</code> object and pass it to
126  * <code>PluralFormat</code>'s constructor. If you also specify a locale in this
127  * constructor, this locale will be used to format the number in the message
128  * texts.
129  * <p>
130  * For more information about <code>PluralRules</code>, see
131  * {@link PluralRules}.
132  *
133  * @author tschumann (Tim Schumann)
134  */
135 public class PluralFormat extends UFormat {
136     private static final long serialVersionUID = 1L;
137 
138     /**
139      * The locale used for standard number formatting and getting the predefined
140      * plural rules (if they were not defined explicitely).
141      * @serial
142      */
143     private ULocale ulocale = null;
144 
145     /**
146      * The plural rules used for plural selection.
147      * @serial
148      */
149     private PluralRules pluralRules = null;
150 
151     /**
152      * The applied pattern string.
153      * @serial
154      */
155     private String pattern = null;
156 
157     /**
158      * The MessagePattern which contains the parsed structure of the pattern string.
159      */
160     transient private MessagePattern msgPattern;
161 
162     /**
163      * Obsolete with use of MessagePattern since ICU 4.8. Used to be:
164      * The format messages for each plural case. It is a mapping:
165      *  <code>String</code>(plural case keyword) --&gt; <code>String</code>
166      *  (message for this plural case).
167      * @serial
168      */
169     private Map<String, String> parsedValues = null;
170 
171     /**
172      * This <code>NumberFormat</code> is used for the standard formatting of
173      * the number inserted into the message.
174      * @serial
175      */
176     private NumberFormat numberFormat = null;
177 
178     /**
179      * The offset to subtract before invoking plural rules.
180      */
181     transient private double offset = 0;
182 
183     /**
184      * Creates a new cardinal-number <code>PluralFormat</code> for the default <code>FORMAT</code> locale.
185      * This locale will be used to get the set of plural rules and for standard
186      * number formatting.
187      * @see Category#FORMAT
188      */
PluralFormat()189     public PluralFormat() {
190         init(null, PluralType.CARDINAL, ULocale.getDefault(Category.FORMAT), null);
191     }
192 
193     /**
194      * Creates a new cardinal-number <code>PluralFormat</code> for a given locale.
195      * @param ulocale the <code>PluralFormat</code> will be configured with
196      *        rules for this locale. This locale will also be used for standard
197      *        number formatting.
198      */
PluralFormat(ULocale ulocale)199     public PluralFormat(ULocale ulocale) {
200         init(null, PluralType.CARDINAL, ulocale, null);
201     }
202 
203     /**
204      * Creates a new cardinal-number <code>PluralFormat</code> for a given
205      * {@link java.util.Locale}.
206      * @param locale the <code>PluralFormat</code> will be configured with
207      *        rules for this locale. This locale will also be used for standard
208      *        number formatting.
209      */
PluralFormat(Locale locale)210     public PluralFormat(Locale locale) {
211         this(ULocale.forLocale(locale));
212     }
213 
214     /**
215      * Creates a new cardinal-number <code>PluralFormat</code> for a given set of rules.
216      * The standard number formatting will be done using the default <code>FORMAT</code> locale.
217      * @param rules defines the behavior of the <code>PluralFormat</code>
218      *        object.
219      * @see Category#FORMAT
220      */
PluralFormat(PluralRules rules)221     public PluralFormat(PluralRules rules) {
222         init(rules, PluralType.CARDINAL, ULocale.getDefault(Category.FORMAT), null);
223     }
224 
225     /**
226      * Creates a new cardinal-number <code>PluralFormat</code> for a given set of rules.
227      * The standard number formatting will be done using the given locale.
228      * @param ulocale the default number formatting will be done using this
229      *        locale.
230      * @param rules defines the behavior of the <code>PluralFormat</code>
231      *        object.
232      */
PluralFormat(ULocale ulocale, PluralRules rules)233     public PluralFormat(ULocale ulocale, PluralRules rules) {
234         init(rules, PluralType.CARDINAL, ulocale, null);
235     }
236 
237     /**
238      * Creates a new cardinal-number <code>PluralFormat</code> for a given set of rules.
239      * The standard number formatting will be done using the given locale.
240      * @param locale the default number formatting will be done using this
241      *        locale.
242      * @param rules defines the behavior of the <code>PluralFormat</code>
243      *        object.
244      */
PluralFormat(Locale locale, PluralRules rules)245     public PluralFormat(Locale locale, PluralRules rules) {
246         this(ULocale.forLocale(locale), rules);
247     }
248 
249     /**
250      * Creates a new <code>PluralFormat</code> for the plural type.
251      * The standard number formatting will be done using the given locale.
252      * @param ulocale the default number formatting will be done using this
253      *        locale.
254      * @param type The plural type (e.g., cardinal or ordinal).
255      */
PluralFormat(ULocale ulocale, PluralType type)256     public PluralFormat(ULocale ulocale, PluralType type) {
257         init(null, type, ulocale, null);
258     }
259 
260     /**
261      * Creates a new <code>PluralFormat</code> for the plural type.
262      * The standard number formatting will be done using the given {@link java.util.Locale}.
263      * @param locale the default number formatting will be done using this
264      *        locale.
265      * @param type The plural type (e.g., cardinal or ordinal).
266      */
PluralFormat(Locale locale, PluralType type)267     public PluralFormat(Locale locale, PluralType type) {
268         this(ULocale.forLocale(locale), type);
269     }
270 
271     /**
272      * Creates a new cardinal-number <code>PluralFormat</code> for a given pattern string.
273      * The default <code>FORMAT</code> locale will be used to get the set of plural rules and for
274      * standard number formatting.
275      * @param  pattern the pattern for this <code>PluralFormat</code>.
276      * @throws IllegalArgumentException if the pattern is invalid.
277      * @see Category#FORMAT
278      */
PluralFormat(String pattern)279     public PluralFormat(String pattern) {
280         init(null, PluralType.CARDINAL, ULocale.getDefault(Category.FORMAT), null);
281         applyPattern(pattern);
282     }
283 
284     /**
285      * Creates a new cardinal-number <code>PluralFormat</code> for a given pattern string and
286      * locale.
287      * The locale will be used to get the set of plural rules and for
288      * standard number formatting.
289      * <p>Example code:{@sample external/icu/ohos_icu4j/src/samples/java/ohos/global/icu/samples/text/pluralformat/PluralFormatSample.java PluralFormatExample}
290      * @param ulocale the <code>PluralFormat</code> will be configured with
291      *        rules for this locale. This locale will also be used for standard
292      *        number formatting.
293      * @param  pattern the pattern for this <code>PluralFormat</code>.
294      * @throws IllegalArgumentException if the pattern is invalid.
295      */
PluralFormat(ULocale ulocale, String pattern)296     public PluralFormat(ULocale ulocale, String pattern) {
297         init(null, PluralType.CARDINAL, ulocale, null);
298         applyPattern(pattern);
299     }
300 
301     /**
302      * Creates a new cardinal-number <code>PluralFormat</code> for a given set of rules and a
303      * pattern.
304      * The standard number formatting will be done using the default <code>FORMAT</code> locale.
305      * @param rules defines the behavior of the <code>PluralFormat</code>
306      *        object.
307      * @param  pattern the pattern for this <code>PluralFormat</code>.
308      * @throws IllegalArgumentException if the pattern is invalid.
309      * @see Category#FORMAT
310      */
PluralFormat(PluralRules rules, String pattern)311     public PluralFormat(PluralRules rules, String pattern) {
312         init(rules, PluralType.CARDINAL, ULocale.getDefault(Category.FORMAT), null);
313         applyPattern(pattern);
314     }
315 
316     /**
317      * Creates a new cardinal-number <code>PluralFormat</code> for a given set of rules, a
318      * pattern and a locale.
319      * @param ulocale the <code>PluralFormat</code> will be configured with
320      *        rules for this locale. This locale will also be used for standard
321      *        number formatting.
322      * @param rules defines the behavior of the <code>PluralFormat</code>
323      *        object.
324      * @param  pattern the pattern for this <code>PluralFormat</code>.
325      * @throws IllegalArgumentException if the pattern is invalid.
326      */
PluralFormat(ULocale ulocale, PluralRules rules, String pattern)327     public PluralFormat(ULocale ulocale, PluralRules rules, String pattern) {
328         init(rules, PluralType.CARDINAL, ulocale, null);
329         applyPattern(pattern);
330     }
331 
332     /**
333      * Creates a new <code>PluralFormat</code> for a plural type, a
334      * pattern and a locale.
335      * @param ulocale the <code>PluralFormat</code> will be configured with
336      *        rules for this locale. This locale will also be used for standard
337      *        number formatting.
338      * @param type The plural type (e.g., cardinal or ordinal).
339      * @param  pattern the pattern for this <code>PluralFormat</code>.
340      * @throws IllegalArgumentException if the pattern is invalid.
341      */
PluralFormat(ULocale ulocale, PluralType type, String pattern)342     public PluralFormat(ULocale ulocale, PluralType type, String pattern) {
343         init(null, type, ulocale, null);
344         applyPattern(pattern);
345     }
346 
347     /**
348      * Creates a new <code>PluralFormat</code> for a plural type, a
349      * pattern and a locale.
350      * @param ulocale the <code>PluralFormat</code> will be configured with
351      *        rules for this locale. This locale will also be used for standard
352      *        number formatting.
353      * @param type The plural type (e.g., cardinal or ordinal).
354      * @param pattern the pattern for this <code>PluralFormat</code>.
355      * @param numberFormat The number formatter to use.
356      * @throws IllegalArgumentException if the pattern is invalid.
357      */
PluralFormat(ULocale ulocale, PluralType type, String pattern, NumberFormat numberFormat)358     /*package*/ PluralFormat(ULocale ulocale, PluralType type, String pattern, NumberFormat numberFormat) {
359         init(null, type, ulocale, numberFormat);
360         applyPattern(pattern);
361     }
362 
363     /*
364      * Initializes the <code>PluralRules</code> object.
365      * Postcondition:<br/>
366      *   <code>ulocale</code>    :  is <code>locale</code><br/>
367      *   <code>pluralRules</code>:  if <code>rules</code> != <code>null</code>
368      *                              it's set to rules, otherwise it is the
369      *                              predefined plural rule set for the locale
370      *                              <code>ulocale</code>.<br/>
371      *   <code>parsedValues</code>: is <code>null</code><br/>
372      *   <code>pattern</code>:      is <code>null</code><br/>
373      *   <code>numberFormat</code>: a <code>NumberFormat</code> for the locale
374      *                              <code>ulocale</code>.
375      */
init(PluralRules rules, PluralType type, ULocale locale, NumberFormat numberFormat)376     private void init(PluralRules rules, PluralType type, ULocale locale, NumberFormat numberFormat) {
377         ulocale = locale;
378         pluralRules = (rules == null) ? PluralRules.forLocale(ulocale, type)
379                                       : rules;
380         resetPattern();
381         this.numberFormat = (numberFormat == null) ? NumberFormat.getInstance(ulocale) : numberFormat;
382     }
383 
resetPattern()384     private void resetPattern() {
385         pattern = null;
386         if(msgPattern != null) {
387             msgPattern.clear();
388         }
389         offset = 0;
390     }
391 
392     /**
393      * Sets the pattern used by this plural format.
394      * The method parses the pattern and creates a map of format strings
395      * for the plural rules.
396      * Patterns and their interpretation are specified in the class description.
397      *
398      * @param pattern the pattern for this plural format.
399      * @throws IllegalArgumentException if the pattern is invalid.
400      */
applyPattern(String pattern)401     public void applyPattern(String pattern) {
402         this.pattern = pattern;
403         if (msgPattern == null) {
404             msgPattern = new MessagePattern();
405         }
406         try {
407             msgPattern.parsePluralStyle(pattern);
408             offset = msgPattern.getPluralOffset(0);
409         } catch(RuntimeException e) {
410             resetPattern();
411             throw e;
412         }
413     }
414 
415     /**
416      * Returns the pattern for this PluralFormat.
417      *
418      * @return the pattern string
419      */
toPattern()420     public String toPattern() {
421         return pattern;
422     }
423 
424     /**
425      * Finds the PluralFormat sub-message for the given number, or the "other" sub-message.
426      * @param pattern A MessagePattern.
427      * @param partIndex the index of the first PluralFormat argument style part.
428      * @param selector the PluralSelector for mapping the number (minus offset) to a keyword.
429      * @param context worker object for the selector.
430      * @param number a number to be matched to one of the PluralFormat argument's explicit values,
431      *        or mapped via the PluralSelector.
432      * @return the sub-message start part index.
433      */
findSubMessage( MessagePattern pattern, int partIndex, PluralSelector selector, Object context, double number)434     /*package*/ static int findSubMessage(
435             MessagePattern pattern, int partIndex,
436             PluralSelector selector, Object context, double number) {
437         int count=pattern.countParts();
438         double offset;
439         MessagePattern.Part part=pattern.getPart(partIndex);
440         if(part.getType().hasNumericValue()) {
441             offset=pattern.getNumericValue(part);
442             ++partIndex;
443         } else {
444             offset=0;
445         }
446         // The keyword is null until we need to match against a non-explicit, not-"other" value.
447         // Then we get the keyword from the selector.
448         // (In other words, we never call the selector if we match against an explicit value,
449         // or if the only non-explicit keyword is "other".)
450         String keyword=null;
451         // When we find a match, we set msgStart>0 and also set this boolean to true
452         // to avoid matching the keyword again (duplicates are allowed)
453         // while we continue to look for an explicit-value match.
454         boolean haveKeywordMatch=false;
455         // msgStart is 0 until we find any appropriate sub-message.
456         // We remember the first "other" sub-message if we have not seen any
457         // appropriate sub-message before.
458         // We remember the first matching-keyword sub-message if we have not seen
459         // one of those before.
460         // (The parser allows [does not check for] duplicate keywords.
461         // We just have to make sure to take the first one.)
462         // We avoid matching the keyword twice by also setting haveKeywordMatch=true
463         // at the first keyword match.
464         // We keep going until we find an explicit-value match or reach the end of the plural style.
465         int msgStart=0;
466         // Iterate over (ARG_SELECTOR [ARG_INT|ARG_DOUBLE] message) tuples
467         // until ARG_LIMIT or end of plural-only pattern.
468         do {
469             part=pattern.getPart(partIndex++);
470             MessagePattern.Part.Type type=part.getType();
471             if(type==MessagePattern.Part.Type.ARG_LIMIT) {
472                 break;
473             }
474             assert type==MessagePattern.Part.Type.ARG_SELECTOR;
475             // part is an ARG_SELECTOR followed by an optional explicit value, and then a message
476             if(pattern.getPartType(partIndex).hasNumericValue()) {
477                 // explicit value like "=2"
478                 part=pattern.getPart(partIndex++);
479                 if(number==pattern.getNumericValue(part)) {
480                     // matches explicit value
481                     return partIndex;
482                 }
483             } else if(!haveKeywordMatch) {
484                 // plural keyword like "few" or "other"
485                 // Compare "other" first and call the selector if this is not "other".
486                 if(pattern.partSubstringMatches(part, "other")) {
487                     if(msgStart==0) {
488                         msgStart=partIndex;
489                         if(keyword!=null && keyword.equals("other")) {
490                             // This is the first "other" sub-message,
491                             // and the selected keyword is also "other".
492                             // Do not match "other" again.
493                             haveKeywordMatch=true;
494                         }
495                     }
496                 } else {
497                     if(keyword==null) {
498                         keyword=selector.select(context, number-offset);
499                         if(msgStart!=0 && keyword.equals("other")) {
500                             // We have already seen an "other" sub-message.
501                             // Do not match "other" again.
502                             haveKeywordMatch=true;
503                             // Skip keyword matching but do getLimitPartIndex().
504                         }
505                     }
506                     if(!haveKeywordMatch && pattern.partSubstringMatches(part, keyword)) {
507                         // keyword matches
508                         msgStart=partIndex;
509                         // Do not match this keyword again.
510                         haveKeywordMatch=true;
511                     }
512                 }
513             }
514             partIndex=pattern.getLimitPartIndex(partIndex);
515         } while(++partIndex<count);
516         return msgStart;
517     }
518 
519     /**
520      * Interface for selecting PluralFormat keywords for numbers.
521      * The PluralRules class was intended to implement this interface,
522      * but there is no public API that uses a PluralSelector,
523      * only MessageFormat and PluralFormat have PluralSelector implementations.
524      * Therefore, PluralRules is not marked to implement this non-public interface,
525      * to avoid confusing users.
526      * @hide draft / provisional / internal are hidden on OHOS
527      */
528     /*package*/ interface PluralSelector {
529         /**
530          * Given a number, returns the appropriate PluralFormat keyword.
531          *
532          * @param context worker object for the selector.
533          * @param number The number to be plural-formatted.
534          * @return The selected PluralFormat keyword.
535          */
select(Object context, double number)536         public String select(Object context, double number);
537     }
538 
539     // See PluralSelector:
540     // We could avoid this adapter class if we made PluralSelector public
541     // (or at least publicly visible) and had PluralRules implement PluralSelector.
542     private final class PluralSelectorAdapter implements PluralSelector {
543         @Override
select(Object context, double number)544         public String select(Object context, double number) {
545             IFixedDecimal dec = (IFixedDecimal) context;
546             return pluralRules.select(dec);
547         }
548     }
549     transient private PluralSelectorAdapter pluralRulesWrapper = new PluralSelectorAdapter();
550 
551     /**
552      * Formats a plural message for a given number.
553      *
554      * @param number a number for which the plural message should be formatted.
555      *        If no pattern has been applied to this
556      *        <code>PluralFormat</code> object yet, the formatted number will
557      *        be returned.
558      * @return the string containing the formatted plural message.
559      */
format(double number)560     public final String format(double number) {
561         return format(number, number);
562     }
563 
564     /**
565      * Formats a plural message for a given number and appends the formatted
566      * message to the given <code>StringBuffer</code>.
567      * @param number a number object (instance of <code>Number</code> for which
568      *        the plural message should be formatted. If no pattern has been
569      *        applied to this <code>PluralFormat</code> object yet, the
570      *        formatted number will be returned.
571      *        Note: If this object is not an instance of <code>Number</code>,
572      *              the <code>toAppendTo</code> will not be modified.
573      * @param toAppendTo the formatted message will be appended to this
574      *        <code>StringBuffer</code>.
575      * @param pos will be ignored by this method.
576      * @return the string buffer passed in as toAppendTo, with formatted text
577      *         appended.
578      * @throws IllegalArgumentException if number is not an instance of Number
579      */
580     @Override
format(Object number, StringBuffer toAppendTo, FieldPosition pos)581     public StringBuffer format(Object number, StringBuffer toAppendTo,
582             FieldPosition pos) {
583         if (!(number instanceof Number)) {
584             throw new IllegalArgumentException("'" + number + "' is not a Number");
585         }
586         Number numberObject = (Number) number;
587         toAppendTo.append(format(numberObject, numberObject.doubleValue()));
588         return toAppendTo;
589     }
590 
format(Number numberObject, double number)591     private String format(Number numberObject, double number) {
592         // If no pattern was applied, return the formatted number.
593         if (msgPattern == null || msgPattern.countParts() == 0) {
594             return numberFormat.format(numberObject);
595         }
596 
597         // Get the appropriate sub-message.
598         // Select it based on the formatted number-offset.
599         double numberMinusOffset = number - offset;
600         String numberString;
601         IFixedDecimal dec;
602         if(numberFormat instanceof DecimalFormat) {
603             // Call NumberFormatter to get both the DecimalQuantity and the string.
604             LocalizedNumberFormatter f = ((DecimalFormat) numberFormat).toNumberFormatter();
605             FormattedNumber result;
606             if (offset == 0) {
607                 // could be BigDecimal etc.
608                 result  = f.format(numberObject);
609             } else {
610                 result  = f.format(numberMinusOffset);
611             }
612             numberString = result.toString();
613             dec = result.getFixedDecimal();
614         } else {
615             if (offset == 0) {
616                 numberString = numberFormat.format(numberObject);
617             } else {
618                 numberString = numberFormat.format(numberMinusOffset);
619             }
620             dec = new FixedDecimal(numberMinusOffset);
621         }
622 
623         int partIndex = findSubMessage(msgPattern, 0, pluralRulesWrapper, dec, number);
624         // Replace syntactic # signs in the top level of this sub-message
625         // (not in nested arguments) with the formatted number-offset.
626         StringBuilder result = null;
627         int prevIndex = msgPattern.getPart(partIndex).getLimit();
628         for (;;) {
629             MessagePattern.Part part = msgPattern.getPart(++partIndex);
630             MessagePattern.Part.Type type = part.getType();
631             int index = part.getIndex();
632             if (type == MessagePattern.Part.Type.MSG_LIMIT) {
633                 if (result == null) {
634                     return pattern.substring(prevIndex, index);
635                 } else {
636                     return result.append(pattern, prevIndex, index).toString();
637                 }
638             } else if (type == MessagePattern.Part.Type.REPLACE_NUMBER ||
639                         // JDK compatibility mode: Remove SKIP_SYNTAX.
640                         (type == MessagePattern.Part.Type.SKIP_SYNTAX && msgPattern.jdkAposMode())) {
641                 if (result == null) {
642                     result = new StringBuilder();
643                 }
644                 result.append(pattern, prevIndex, index);
645                 if (type == MessagePattern.Part.Type.REPLACE_NUMBER) {
646                     result.append(numberString);
647                 }
648                 prevIndex = part.getLimit();
649             } else if (type == MessagePattern.Part.Type.ARG_START) {
650                 if (result == null) {
651                     result = new StringBuilder();
652                 }
653                 result.append(pattern, prevIndex, index);
654                 prevIndex = index;
655                 partIndex = msgPattern.getLimitPartIndex(partIndex);
656                 index = msgPattern.getPart(partIndex).getLimit();
657                 MessagePattern.appendReducedApostrophes(pattern, prevIndex, index, result);
658                 prevIndex = index;
659             }
660         }
661     }
662 
663     /**
664      * This method is not yet supported by <code>PluralFormat</code>.
665      * @param text the string to be parsed.
666      * @param parsePosition defines the position where parsing is to begin,
667      * and upon return, the position where parsing left off.  If the position
668      * has not changed upon return, then parsing failed.
669      * @return nothing because this method is not yet implemented.
670      * @throws UnsupportedOperationException will always be thrown by this method.
671      */
parse(String text, ParsePosition parsePosition)672     public Number parse(String text, ParsePosition parsePosition) {
673         // You get number ranges from this. You can't get an exact number.
674         throw new UnsupportedOperationException();
675     }
676 
677     /**
678      * This method is not yet supported by <code>PluralFormat</code>.
679      * @param source the string to be parsed.
680      * @param pos defines the position where parsing is to begin,
681      * and upon return, the position where parsing left off.  If the position
682      * has not changed upon return, then parsing failed.
683      * @return nothing because this method is not yet implemented.
684      * @throws UnsupportedOperationException will always be thrown by this method.
685      */
686     @Override
parseObject(String source, ParsePosition pos)687     public Object parseObject(String source, ParsePosition pos) {
688         throw new UnsupportedOperationException();
689     }
690 
691     /**
692      * This method returns the PluralRules type found from parsing.
693      * @param source the string to be parsed.
694      * @param pos defines the position where parsing is to begin,
695      * and upon return, the position where parsing left off.  If the position
696      * is a negative index, then parsing failed.
697      * @return Returns the PluralRules type. For example, it could be "zero", "one", "two", "few", "many" or "other")
698      */
parseType(String source, RbnfLenientScanner scanner, FieldPosition pos)699     /*package*/ String parseType(String source, RbnfLenientScanner scanner, FieldPosition pos) {
700         // If no pattern was applied, return null.
701         if (msgPattern == null || msgPattern.countParts() == 0) {
702             pos.setBeginIndex(-1);
703             pos.setEndIndex(-1);
704             return null;
705         }
706         int partIndex = 0;
707         int currMatchIndex;
708         int count=msgPattern.countParts();
709         int startingAt = pos.getBeginIndex();
710         if (startingAt < 0) {
711             startingAt = 0;
712         }
713 
714         // The keyword is null until we need to match against a non-explicit, not-"other" value.
715         // Then we get the keyword from the selector.
716         // (In other words, we never call the selector if we match against an explicit value,
717         // or if the only non-explicit keyword is "other".)
718         String keyword = null;
719         String matchedWord = null;
720         int matchedIndex = -1;
721         // Iterate over (ARG_SELECTOR ARG_START message ARG_LIMIT) tuples
722         // until the end of the plural-only pattern.
723         while (partIndex < count) {
724             MessagePattern.Part partSelector=msgPattern.getPart(partIndex++);
725             if (partSelector.getType() != MessagePattern.Part.Type.ARG_SELECTOR) {
726                 // Bad format
727                 continue;
728             }
729 
730             MessagePattern.Part partStart=msgPattern.getPart(partIndex++);
731             if (partStart.getType() != MessagePattern.Part.Type.MSG_START) {
732                 // Bad format
733                 continue;
734             }
735 
736             MessagePattern.Part partLimit=msgPattern.getPart(partIndex++);
737             if (partLimit.getType() != MessagePattern.Part.Type.MSG_LIMIT) {
738                 // Bad format
739                 continue;
740             }
741 
742             String currArg = pattern.substring(partStart.getLimit(), partLimit.getIndex());
743             if (scanner != null) {
744                 // If lenient parsing is turned ON, we've got some time consuming parsing ahead of us.
745                 int[] scannerMatchResult = scanner.findText(source, currArg, startingAt);
746                 currMatchIndex = scannerMatchResult[0];
747             }
748             else {
749                 currMatchIndex = source.indexOf(currArg, startingAt);
750             }
751             if (currMatchIndex >= 0 && currMatchIndex >= matchedIndex && (matchedWord == null || currArg.length() > matchedWord.length())) {
752                 matchedIndex = currMatchIndex;
753                 matchedWord = currArg;
754                 keyword = pattern.substring(partStart.getLimit(), partLimit.getIndex());
755             }
756         }
757         if (keyword != null) {
758             pos.setBeginIndex(matchedIndex);
759             pos.setEndIndex(matchedIndex + matchedWord.length());
760             return keyword;
761         }
762 
763         // Not found!
764         pos.setBeginIndex(-1);
765         pos.setEndIndex(-1);
766         return null;
767     }
768 
769     /**
770      * Sets the locale used by this <code>PluraFormat</code> object.
771      * Note: Calling this method resets this <code>PluraFormat</code> object,
772      *     i.e., a pattern that was applied previously will be removed,
773      *     and the NumberFormat is set to the default number format for
774      *     the locale.  The resulting format behaves the same as one
775      *     constructed from {@link #PluralFormat(ULocale, PluralRules.PluralType)}
776      *     with PluralType.CARDINAL.
777      * @param ulocale the <code>ULocale</code> used to configure the
778      *     formatter. If <code>ulocale</code> is <code>null</code>, the
779      *     default <code>FORMAT</code> locale will be used.
780      * @see Category#FORMAT
781      * @deprecated ICU 50 This method clears the pattern and might create
782      *             a different kind of PluralRules instance;
783      *             use one of the constructors to create a new instance instead.
784      * @hide deprecated on icu4j-org
785      */
786     @Deprecated
setLocale(ULocale ulocale)787     public void setLocale(ULocale ulocale) {
788         if (ulocale == null) {
789             ulocale = ULocale.getDefault(Category.FORMAT);
790         }
791         init(null, PluralType.CARDINAL, ulocale, null);
792     }
793 
794     /**
795      * Sets the number format used by this formatter.  You only need to
796      * call this if you want a different number format than the default
797      * formatter for the locale.
798      * @param format the number format to use.
799      */
setNumberFormat(NumberFormat format)800     public void setNumberFormat(NumberFormat format) {
801         numberFormat = format;
802     }
803 
804     /**
805      * {@inheritDoc}
806      */
807     @Override
equals(Object rhs)808     public boolean equals(Object rhs) {
809         if(this == rhs) {
810             return true;
811         }
812         if(rhs == null || getClass() != rhs.getClass()) {
813             return false;
814         }
815         PluralFormat pf = (PluralFormat)rhs;
816         return
817             Objects.equals(ulocale, pf.ulocale) &&
818             Objects.equals(pluralRules, pf.pluralRules) &&
819             Objects.equals(msgPattern, pf.msgPattern) &&
820             Objects.equals(numberFormat, pf.numberFormat);
821     }
822 
823     /**
824      * Returns true if this equals the provided PluralFormat.
825      * @param rhs the PluralFormat to compare against
826      * @return true if this equals rhs
827      */
equals(PluralFormat rhs)828     public boolean equals(PluralFormat rhs) {
829         return equals((Object)rhs);
830     }
831 
832     /**
833      * {@inheritDoc}
834      */
835     @Override
hashCode()836     public int hashCode() {
837         return pluralRules.hashCode() ^ parsedValues.hashCode();
838     }
839 
840     /**
841      * {@inheritDoc}
842      */
843     @Override
toString()844     public String toString() {
845         StringBuilder buf = new StringBuilder();
846         buf.append("locale=" + ulocale);
847         buf.append(", rules='" + pluralRules + "'");
848         buf.append(", pattern='" + pattern + "'");
849         buf.append(", format='" + numberFormat + "'");
850         return buf.toString();
851     }
852 
readObject(ObjectInputStream in)853     private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
854         in.defaultReadObject();
855         pluralRulesWrapper = new PluralSelectorAdapter();
856         // Ignore the parsedValues from an earlier class version (before ICU 4.8)
857         // and rebuild the msgPattern.
858         parsedValues = null;
859         if (pattern != null) {
860             applyPattern(pattern);
861         }
862     }
863 }
864