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