Home | History | Annotate | Download | only in text
      1 /*
      2  *******************************************************************************
      3  * Copyright (C) 2007-2015, International Business Machines Corporation and
      4  * others. All Rights Reserved.
      5  *******************************************************************************
      6  */
      7 
      8 package com.ibm.icu.text;
      9 
     10 import java.io.IOException;
     11 import java.io.ObjectInputStream;
     12 import java.text.FieldPosition;
     13 import java.text.ParsePosition;
     14 import java.util.Locale;
     15 import java.util.Map;
     16 
     17 import com.ibm.icu.impl.Utility;
     18 import com.ibm.icu.text.PluralRules.FixedDecimal;
     19 import com.ibm.icu.text.PluralRules.PluralType;
     20 import com.ibm.icu.util.ULocale;
     21 import com.ibm.icu.util.ULocale.Category;
     22 
     23 /**
     24  * <p>
     25  * <code>PluralFormat</code> supports the creation of internationalized
     26  * messages with plural inflection. It is based on <i>plural
     27  * selection</i>, i.e. the caller specifies messages for each
     28  * plural case that can appear in the user's language and the
     29  * <code>PluralFormat</code> selects the appropriate message based on
     30  * the number.
     31  * </p>
     32  * <h4>The Problem of Plural Forms in Internationalized Messages</h4>
     33  * <p>
     34  * Different languages have different ways to inflect
     35  * plurals. Creating internationalized messages that include plural
     36  * forms is only feasible when the framework is able to handle plural
     37  * forms of <i>all</i> languages correctly. <code>ChoiceFormat</code>
     38  * doesn't handle this well, because it attaches a number interval to
     39  * each message and selects the message whose interval contains a
     40  * given number. This can only handle a finite number of
     41  * intervals. But in some languages, like Polish, one plural case
     42  * applies to infinitely many intervals (e.g., the paucal case applies to
     43  * numbers ending with 2, 3, or 4 except those ending with 12, 13, or
     44  * 14). Thus <code>ChoiceFormat</code> is not adequate.
     45  * </p><p>
     46  * <code>PluralFormat</code> deals with this by breaking the problem
     47  * into two parts:
     48  * <ul>
     49  * <li>It uses <code>PluralRules</code> that can define more complex
     50  *     conditions for a plural case than just a single interval. These plural
     51  *     rules define both what plural cases exist in a language, and to
     52  *     which numbers these cases apply.
     53  * <li>It provides predefined plural rules for many languages. Thus, the programmer
     54  *     need not worry about the plural cases of a language and
     55  *     does not have to define the plural cases; they can simply
     56  *     use the predefined keywords. The whole plural formatting of messages can
     57  *     be done using localized patterns from resource bundles. For predefined plural
     58  *     rules, see the CLDR <i>Language Plural Rules</i> page at
     59  *    http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
     60  * </ul>
     61  * </p>
     62  * <h4>Usage of <code>PluralFormat</code></h4>
     63  * <p>Note: Typically, plural formatting is done via <code>MessageFormat</code>
     64  * with a <code>plural</code> argument type,
     65  * rather than using a stand-alone <code>PluralFormat</code>.
     66  * </p><p>
     67  * This discussion assumes that you use <code>PluralFormat</code> with
     68  * a predefined set of plural rules. You can create one using one of
     69  * the constructors that takes a <code>ULocale</code> object. To
     70  * specify the message pattern, you can either pass it to the
     71  * constructor or set it explicitly using the
     72  * <code>applyPattern()</code> method. The <code>format()</code>
     73  * method takes a number object and selects the message of the
     74  * matching plural case. This message will be returned.
     75  * </p>
     76  * <h5>Patterns and Their Interpretation</h5>
     77  * <p>
     78  * The pattern text defines the message output for each plural case of the
     79  * specified locale. Syntax:
     80  * <blockquote><pre>
     81  * pluralStyle = [offsetValue] (selector '{' message '}')+
     82  * offsetValue = "offset:" number
     83  * selector = explicitValue | keyword
     84  * explicitValue = '=' number  // adjacent, no white space in between
     85  * keyword = [^[[:Pattern_Syntax:][:Pattern_White_Space:]]]+
     86  * message: see {@link MessageFormat}
     87  * </pre></blockquote>
     88  * Pattern_White_Space between syntax elements is ignored, except
     89  * between the {curly braces} and their sub-message,
     90  * and between the '=' and the number of an explicitValue.
     91  *
     92  * </p><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><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><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  * </p>
    117  * 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><p>
    126  * For more information about <code>PluralRules</code>, see
    127  * {@link PluralRules}.
    128  * </p>
    129  *
    130  * @author tschumann (Tim Schumann)
    131  * @stable ICU 3.8
    132  */
    133 public class PluralFormat extends UFormat {
    134     private static final long serialVersionUID = 1L;
    135 
    136     /**
    137      * The locale used for standard number formatting and getting the predefined
    138      * plural rules (if they were not defined explicitely).
    139      * @serial
    140      */
    141     private ULocale ulocale = null;
    142 
    143     /**
    144      * The plural rules used for plural selection.
    145      * @serial
    146      */
    147     private PluralRules pluralRules = null;
    148 
    149     /**
    150      * The applied pattern string.
    151      * @serial
    152      */
    153     private String pattern = null;
    154 
    155     /**
    156      * The MessagePattern which contains the parsed structure of the pattern string.
    157      */
    158     transient private MessagePattern msgPattern;
    159 
    160     /**
    161      * Obsolete with use of MessagePattern since ICU 4.8. Used to be:
    162      * The format messages for each plural case. It is a mapping:
    163      *  <code>String</code>(plural case keyword) --&gt; <code>String</code>
    164      *  (message for this plural case).
    165      * @serial
    166      */
    167     private Map<String, String> parsedValues = null;
    168 
    169     /**
    170      * This <code>NumberFormat</code> is used for the standard formatting of
    171      * the number inserted into the message.
    172      * @serial
    173      */
    174     private NumberFormat numberFormat = null;
    175 
    176     /**
    177      * The offset to subtract before invoking plural rules.
    178      */
    179     transient private double offset = 0;
    180 
    181     /**
    182      * Creates a new cardinal-number <code>PluralFormat</code> for the default <code>FORMAT</code> locale.
    183      * This locale will be used to get the set of plural rules and for standard
    184      * number formatting.
    185      * @see Category#FORMAT
    186      * @stable ICU 3.8
    187      */
    188     public PluralFormat() {
    189         init(null, PluralType.CARDINAL, ULocale.getDefault(Category.FORMAT), null);
    190     }
    191 
    192     /**
    193      * Creates a new cardinal-number <code>PluralFormat</code> for a given locale.
    194      * @param ulocale the <code>PluralFormat</code> will be configured with
    195      *        rules for this locale. This locale will also be used for standard
    196      *        number formatting.
    197      * @stable ICU 3.8
    198      */
    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 JDK 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      * @draft ICU 54
    209      * @provisional This API might change or be removed in a future release.
    210      */
    211     public PluralFormat(Locale locale) {
    212         this(ULocale.forLocale(locale));
    213     }
    214 
    215     /**
    216      * Creates a new cardinal-number <code>PluralFormat</code> for a given set of rules.
    217      * The standard number formatting will be done using the default <code>FORMAT</code> locale.
    218      * @param rules defines the behavior of the <code>PluralFormat</code>
    219      *        object.
    220      * @see Category#FORMAT
    221      * @stable ICU 3.8
    222      */
    223     public PluralFormat(PluralRules rules) {
    224         init(rules, PluralType.CARDINAL, ULocale.getDefault(Category.FORMAT), null);
    225     }
    226 
    227     /**
    228      * Creates a new cardinal-number <code>PluralFormat</code> for a given set of rules.
    229      * The standard number formatting will be done using the given locale.
    230      * @param ulocale the default number formatting will be done using this
    231      *        locale.
    232      * @param rules defines the behavior of the <code>PluralFormat</code>
    233      *        object.
    234      * @stable ICU 3.8
    235      */
    236     public PluralFormat(ULocale ulocale, PluralRules rules) {
    237         init(rules, PluralType.CARDINAL, ulocale, null);
    238     }
    239 
    240     /**
    241      * Creates a new cardinal-number <code>PluralFormat</code> for a given set of rules.
    242      * The standard number formatting will be done using the given locale.
    243      * @param locale the default number formatting will be done using this
    244      *        locale.
    245      * @param rules defines the behavior of the <code>PluralFormat</code>
    246      *        object.
    247      * @draft ICU 54
    248      * @provisional This API might change or be removed in a future release.
    249      */
    250     public PluralFormat(Locale locale, PluralRules rules) {
    251         this(ULocale.forLocale(locale), rules);
    252     }
    253 
    254     /**
    255      * Creates a new <code>PluralFormat</code> for the plural type.
    256      * The standard number formatting will be done using the given locale.
    257      * @param ulocale the default number formatting will be done using this
    258      *        locale.
    259      * @param type The plural type (e.g., cardinal or ordinal).
    260      * @stable ICU 50
    261      */
    262     public PluralFormat(ULocale ulocale, PluralType type) {
    263         init(null, type, ulocale, null);
    264     }
    265 
    266     /**
    267      * Creates a new <code>PluralFormat</code> for the plural type.
    268      * The standard number formatting will be done using the given JDK locale.
    269      * @param locale the default number formatting will be done using this
    270      *        locale.
    271      * @param type The plural type (e.g., cardinal or ordinal).
    272      * @draft ICU 54
    273      * @provisional This API might change or be removed in a future release.
    274      */
    275     public PluralFormat(Locale locale, PluralType type) {
    276         this(ULocale.forLocale(locale), type);
    277     }
    278 
    279     /**
    280      * Creates a new cardinal-number <code>PluralFormat</code> for a given pattern string.
    281      * The default <code>FORMAT</code> locale will be used to get the set of plural rules and for
    282      * standard number formatting.
    283      * @param  pattern the pattern for this <code>PluralFormat</code>.
    284      * @throws IllegalArgumentException if the pattern is invalid.
    285      * @see Category#FORMAT
    286      * @stable ICU 3.8
    287      */
    288     public PluralFormat(String pattern) {
    289         init(null, PluralType.CARDINAL, ULocale.getDefault(Category.FORMAT), null);
    290         applyPattern(pattern);
    291     }
    292 
    293     /**
    294      * Creates a new cardinal-number <code>PluralFormat</code> for a given pattern string and
    295      * locale.
    296      * The locale will be used to get the set of plural rules and for
    297      * standard number formatting.
    298      * <p>Example code:{@.jcite com.ibm.icu.samples.text.pluralformat.PluralFormatSample:---PluralFormatExample}
    299      * @param ulocale the <code>PluralFormat</code> will be configured with
    300      *        rules for this locale. This locale will also be used for standard
    301      *        number formatting.
    302      * @param  pattern the pattern for this <code>PluralFormat</code>.
    303      * @throws IllegalArgumentException if the pattern is invalid.
    304      * @stable ICU 3.8
    305      */
    306     public PluralFormat(ULocale ulocale, String pattern) {
    307         init(null, PluralType.CARDINAL, ulocale, null);
    308         applyPattern(pattern);
    309     }
    310 
    311     /**
    312      * Creates a new cardinal-number <code>PluralFormat</code> for a given set of rules and a
    313      * pattern.
    314      * The standard number formatting will be done using the default <code>FORMAT</code> locale.
    315      * @param rules defines the behavior of the <code>PluralFormat</code>
    316      *        object.
    317      * @param  pattern the pattern for this <code>PluralFormat</code>.
    318      * @throws IllegalArgumentException if the pattern is invalid.
    319      * @see Category#FORMAT
    320      * @stable ICU 3.8
    321      */
    322     public PluralFormat(PluralRules rules, String pattern) {
    323         init(rules, PluralType.CARDINAL, ULocale.getDefault(Category.FORMAT), null);
    324         applyPattern(pattern);
    325     }
    326 
    327     /**
    328      * Creates a new cardinal-number <code>PluralFormat</code> for a given set of rules, a
    329      * pattern and a locale.
    330      * @param ulocale the <code>PluralFormat</code> will be configured with
    331      *        rules for this locale. This locale will also be used for standard
    332      *        number formatting.
    333      * @param rules defines the behavior of the <code>PluralFormat</code>
    334      *        object.
    335      * @param  pattern the pattern for this <code>PluralFormat</code>.
    336      * @throws IllegalArgumentException if the pattern is invalid.
    337      * @stable ICU 3.8
    338      */
    339     public PluralFormat(ULocale ulocale, PluralRules rules, String pattern) {
    340         init(rules, PluralType.CARDINAL, ulocale, null);
    341         applyPattern(pattern);
    342     }
    343 
    344     /**
    345      * Creates a new <code>PluralFormat</code> for a plural type, a
    346      * pattern and a locale.
    347      * @param ulocale the <code>PluralFormat</code> will be configured with
    348      *        rules for this locale. This locale will also be used for standard
    349      *        number formatting.
    350      * @param type The plural type (e.g., cardinal or ordinal).
    351      * @param  pattern the pattern for this <code>PluralFormat</code>.
    352      * @throws IllegalArgumentException if the pattern is invalid.
    353      * @stable ICU 50
    354      */
    355     public PluralFormat(ULocale ulocale, PluralType type, String pattern) {
    356         init(null, type, ulocale, null);
    357         applyPattern(pattern);
    358     }
    359 
    360     /**
    361      * Creates a new <code>PluralFormat</code> for a plural type, a
    362      * pattern and a locale.
    363      * @param ulocale the <code>PluralFormat</code> will be configured with
    364      *        rules for this locale. This locale will also be used for standard
    365      *        number formatting.
    366      * @param type The plural type (e.g., cardinal or ordinal).
    367      * @param pattern the pattern for this <code>PluralFormat</code>.
    368      * @param numberFormat The number formatter to use.
    369      * @throws IllegalArgumentException if the pattern is invalid.
    370      */
    371     /*package*/ PluralFormat(ULocale ulocale, PluralType type, String pattern, NumberFormat numberFormat) {
    372         init(null, type, ulocale, numberFormat);
    373         applyPattern(pattern);
    374     }
    375 
    376     /*
    377      * Initializes the <code>PluralRules</code> object.
    378      * Postcondition:<br/>
    379      *   <code>ulocale</code>    :  is <code>locale</code><br/>
    380      *   <code>pluralRules</code>:  if <code>rules</code> != <code>null</code>
    381      *                              it's set to rules, otherwise it is the
    382      *                              predefined plural rule set for the locale
    383      *                              <code>ulocale</code>.<br/>
    384      *   <code>parsedValues</code>: is <code>null</code><br/>
    385      *   <code>pattern</code>:      is <code>null</code><br/>
    386      *   <code>numberFormat</code>: a <code>NumberFormat</code> for the locale
    387      *                              <code>ulocale</code>.
    388      */
    389     private void init(PluralRules rules, PluralType type, ULocale locale, NumberFormat numberFormat) {
    390         ulocale = locale;
    391         pluralRules = (rules == null) ? PluralRules.forLocale(ulocale, type)
    392                                       : rules;
    393         resetPattern();
    394         this.numberFormat = (numberFormat == null) ? NumberFormat.getInstance(ulocale) : numberFormat;
    395     }
    396 
    397     private void resetPattern() {
    398         pattern = null;
    399         if(msgPattern != null) {
    400             msgPattern.clear();
    401         }
    402         offset = 0;
    403     }
    404 
    405     /**
    406      * Sets the pattern used by this plural format.
    407      * The method parses the pattern and creates a map of format strings
    408      * for the plural rules.
    409      * Patterns and their interpretation are specified in the class description.
    410      *
    411      * @param pattern the pattern for this plural format.
    412      * @throws IllegalArgumentException if the pattern is invalid.
    413      * @stable ICU 3.8
    414      */
    415     public void applyPattern(String pattern) {
    416         this.pattern = pattern;
    417         if (msgPattern == null) {
    418             msgPattern = new MessagePattern();
    419         }
    420         try {
    421             msgPattern.parsePluralStyle(pattern);
    422             offset = msgPattern.getPluralOffset(0);
    423         } catch(RuntimeException e) {
    424             resetPattern();
    425             throw e;
    426         }
    427     }
    428 
    429     /**
    430      * Returns the pattern for this PluralFormat.
    431      *
    432      * @return the pattern string
    433      * @stable ICU 4.2
    434      */
    435     public String toPattern() {
    436         return pattern;
    437     }
    438 
    439     /**
    440      * Finds the PluralFormat sub-message for the given number, or the "other" sub-message.
    441      * @param pattern A MessagePattern.
    442      * @param partIndex the index of the first PluralFormat argument style part.
    443      * @param selector the PluralSelector for mapping the number (minus offset) to a keyword.
    444      * @param context worker object for the selector.
    445      * @param number a number to be matched to one of the PluralFormat argument's explicit values,
    446      *        or mapped via the PluralSelector.
    447      * @return the sub-message start part index.
    448      */
    449     /*package*/ static int findSubMessage(
    450             MessagePattern pattern, int partIndex,
    451             PluralSelector selector, Object context, double number) {
    452         int count=pattern.countParts();
    453         double offset;
    454         MessagePattern.Part part=pattern.getPart(partIndex);
    455         if(part.getType().hasNumericValue()) {
    456             offset=pattern.getNumericValue(part);
    457             ++partIndex;
    458         } else {
    459             offset=0;
    460         }
    461         // The keyword is null until we need to match against a non-explicit, not-"other" value.
    462         // Then we get the keyword from the selector.
    463         // (In other words, we never call the selector if we match against an explicit value,
    464         // or if the only non-explicit keyword is "other".)
    465         String keyword=null;
    466         // When we find a match, we set msgStart>0 and also set this boolean to true
    467         // to avoid matching the keyword again (duplicates are allowed)
    468         // while we continue to look for an explicit-value match.
    469         boolean haveKeywordMatch=false;
    470         // msgStart is 0 until we find any appropriate sub-message.
    471         // We remember the first "other" sub-message if we have not seen any
    472         // appropriate sub-message before.
    473         // We remember the first matching-keyword sub-message if we have not seen
    474         // one of those before.
    475         // (The parser allows [does not check for] duplicate keywords.
    476         // We just have to make sure to take the first one.)
    477         // We avoid matching the keyword twice by also setting haveKeywordMatch=true
    478         // at the first keyword match.
    479         // We keep going until we find an explicit-value match or reach the end of the plural style.
    480         int msgStart=0;
    481         // Iterate over (ARG_SELECTOR [ARG_INT|ARG_DOUBLE] message) tuples
    482         // until ARG_LIMIT or end of plural-only pattern.
    483         do {
    484             part=pattern.getPart(partIndex++);
    485             MessagePattern.Part.Type type=part.getType();
    486             if(type==MessagePattern.Part.Type.ARG_LIMIT) {
    487                 break;
    488             }
    489             assert type==MessagePattern.Part.Type.ARG_SELECTOR;
    490             // part is an ARG_SELECTOR followed by an optional explicit value, and then a message
    491             if(pattern.getPartType(partIndex).hasNumericValue()) {
    492                 // explicit value like "=2"
    493                 part=pattern.getPart(partIndex++);
    494                 if(number==pattern.getNumericValue(part)) {
    495                     // matches explicit value
    496                     return partIndex;
    497                 }
    498             } else if(!haveKeywordMatch) {
    499                 // plural keyword like "few" or "other"
    500                 // Compare "other" first and call the selector if this is not "other".
    501                 if(pattern.partSubstringMatches(part, "other")) {
    502                     if(msgStart==0) {
    503                         msgStart=partIndex;
    504                         if(keyword!=null && keyword.equals("other")) {
    505                             // This is the first "other" sub-message,
    506                             // and the selected keyword is also "other".
    507                             // Do not match "other" again.
    508                             haveKeywordMatch=true;
    509                         }
    510                     }
    511                 } else {
    512                     if(keyword==null) {
    513                         keyword=selector.select(context, number-offset);
    514                         if(msgStart!=0 && keyword.equals("other")) {
    515                             // We have already seen an "other" sub-message.
    516                             // Do not match "other" again.
    517                             haveKeywordMatch=true;
    518                             // Skip keyword matching but do getLimitPartIndex().
    519                         }
    520                     }
    521                     if(!haveKeywordMatch && pattern.partSubstringMatches(part, keyword)) {
    522                         // keyword matches
    523                         msgStart=partIndex;
    524                         // Do not match this keyword again.
    525                         haveKeywordMatch=true;
    526                     }
    527                 }
    528             }
    529             partIndex=pattern.getLimitPartIndex(partIndex);
    530         } while(++partIndex<count);
    531         return msgStart;
    532     }
    533 
    534     /**
    535      * Interface for selecting PluralFormat keywords for numbers.
    536      * The PluralRules class was intended to implement this interface,
    537      * but there is no public API that uses a PluralSelector,
    538      * only MessageFormat and PluralFormat have PluralSelector implementations.
    539      * Therefore, PluralRules is not marked to implement this non-public interface,
    540      * to avoid confusing users.
    541      * @internal
    542      */
    543     /*package*/ interface PluralSelector {
    544         /**
    545          * Given a number, returns the appropriate PluralFormat keyword.
    546          *
    547          * @param context worker object for the selector.
    548          * @param number The number to be plural-formatted.
    549          * @return The selected PluralFormat keyword.
    550          */
    551         public String select(Object context, double number);
    552     }
    553 
    554     // See PluralSelector:
    555     // We could avoid this adapter class if we made PluralSelector public
    556     // (or at least publicly visible) and had PluralRules implement PluralSelector.
    557     private final class PluralSelectorAdapter implements PluralSelector {
    558         public String select(Object context, double number) {
    559             FixedDecimal dec = (FixedDecimal) context;
    560             assert dec.source == number;
    561             return pluralRules.select(dec);
    562         }
    563     }
    564     transient private PluralSelectorAdapter pluralRulesWrapper = new PluralSelectorAdapter();
    565 
    566     /**
    567      * Formats a plural message for a given number.
    568      *
    569      * @param number a number for which the plural message should be formatted.
    570      *        If no pattern has been applied to this
    571      *        <code>PluralFormat</code> object yet, the formatted number will
    572      *        be returned.
    573      * @return the string containing the formatted plural message.
    574      * @stable ICU 4.0
    575      */
    576     public final String format(double number) {
    577         return format(number, number);
    578     }
    579 
    580     /**
    581      * Formats a plural message for a given number and appends the formatted
    582      * message to the given <code>StringBuffer</code>.
    583      * @param number a number object (instance of <code>Number</code> for which
    584      *        the plural message should be formatted. If no pattern has been
    585      *        applied to this <code>PluralFormat</code> object yet, the
    586      *        formatted number will be returned.
    587      *        Note: If this object is not an instance of <code>Number</code>,
    588      *              the <code>toAppendTo</code> will not be modified.
    589      * @param toAppendTo the formatted message will be appended to this
    590      *        <code>StringBuffer</code>.
    591      * @param pos will be ignored by this method.
    592      * @return the string buffer passed in as toAppendTo, with formatted text
    593      *         appended.
    594      * @throws IllegalArgumentException if number is not an instance of Number
    595      * @stable ICU 3.8
    596      */
    597     public StringBuffer format(Object number, StringBuffer toAppendTo,
    598             FieldPosition pos) {
    599         if (!(number instanceof Number)) {
    600             throw new IllegalArgumentException("'" + number + "' is not a Number");
    601         }
    602         Number numberObject = (Number) number;
    603         toAppendTo.append(format(numberObject, numberObject.doubleValue()));
    604         return toAppendTo;
    605     }
    606 
    607     private String format(Number numberObject, double number) {
    608         // If no pattern was applied, return the formatted number.
    609         if (msgPattern == null || msgPattern.countParts() == 0) {
    610             return numberFormat.format(numberObject);
    611         }
    612 
    613         // Get the appropriate sub-message.
    614         // Select it based on the formatted number-offset.
    615         double numberMinusOffset = number - offset;
    616         String numberString;
    617         if (offset == 0) {
    618             numberString = numberFormat.format(numberObject);  // could be BigDecimal etc.
    619         } else {
    620             numberString = numberFormat.format(numberMinusOffset);
    621         }
    622         FixedDecimal dec;
    623         if(numberFormat instanceof DecimalFormat) {
    624             dec = ((DecimalFormat) numberFormat).getFixedDecimal(numberMinusOffset);
    625         } else {
    626             dec = new FixedDecimal(numberMinusOffset);
    627         }
    628         int partIndex = findSubMessage(msgPattern, 0, pluralRulesWrapper, dec, number);
    629         // Replace syntactic # signs in the top level of this sub-message
    630         // (not in nested arguments) with the formatted number-offset.
    631         StringBuilder result = null;
    632         int prevIndex = msgPattern.getPart(partIndex).getLimit();
    633         for (;;) {
    634             MessagePattern.Part part = msgPattern.getPart(++partIndex);
    635             MessagePattern.Part.Type type = part.getType();
    636             int index = part.getIndex();
    637             if (type == MessagePattern.Part.Type.MSG_LIMIT) {
    638                 if (result == null) {
    639                     return pattern.substring(prevIndex, index);
    640                 } else {
    641                     return result.append(pattern, prevIndex, index).toString();
    642                 }
    643             } else if (type == MessagePattern.Part.Type.REPLACE_NUMBER ||
    644                         // JDK compatibility mode: Remove SKIP_SYNTAX.
    645                         (type == MessagePattern.Part.Type.SKIP_SYNTAX && msgPattern.jdkAposMode())) {
    646                 if (result == null) {
    647                     result = new StringBuilder();
    648                 }
    649                 result.append(pattern, prevIndex, index);
    650                 if (type == MessagePattern.Part.Type.REPLACE_NUMBER) {
    651                     result.append(numberString);
    652                 }
    653                 prevIndex = part.getLimit();
    654             } else if (type == MessagePattern.Part.Type.ARG_START) {
    655                 if (result == null) {
    656                     result = new StringBuilder();
    657                 }
    658                 result.append(pattern, prevIndex, index);
    659                 prevIndex = index;
    660                 partIndex = msgPattern.getLimitPartIndex(partIndex);
    661                 index = msgPattern.getPart(partIndex).getLimit();
    662                 MessagePattern.appendReducedApostrophes(pattern, prevIndex, index, result);
    663                 prevIndex = index;
    664             }
    665         }
    666     }
    667 
    668     /**
    669      * This method is not yet supported by <code>PluralFormat</code>.
    670      * @param text the string to be parsed.
    671      * @param parsePosition defines the position where parsing is to begin,
    672      * and upon return, the position where parsing left off.  If the position
    673      * has not changed upon return, then parsing failed.
    674      * @return nothing because this method is not yet implemented.
    675      * @throws UnsupportedOperationException will always be thrown by this method.
    676      * @stable ICU 3.8
    677      */
    678     public Number parse(String text, ParsePosition parsePosition) {
    679         // You get number ranges from this. You can't get an exact number.
    680         throw new UnsupportedOperationException();
    681     }
    682 
    683     /**
    684      * This method is not yet supported by <code>PluralFormat</code>.
    685      * @param source the string to be parsed.
    686      * @param pos defines the position where parsing is to begin,
    687      * and upon return, the position where parsing left off.  If the position
    688      * has not changed upon return, then parsing failed.
    689      * @return nothing because this method is not yet implemented.
    690      * @throws UnsupportedOperationException will always be thrown by this method.
    691      * @stable ICU 3.8
    692      */
    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