]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_2_1-src/src/com/ibm/icu/text/PluralFormat.java
icu4jsrc
[Dictionary.git] / jars / icu4j-4_2_1-src / src / com / ibm / icu / text / PluralFormat.java
1 /*\r
2  *******************************************************************************\r
3  * Copyright (C) 2007-2009, International Business Machines Corporation and    *\r
4  * others. All Rights Reserved.                                                *\r
5  *******************************************************************************\r
6  */\r
7 \r
8 package com.ibm.icu.text;\r
9 \r
10 import com.ibm.icu.impl.UCharacterProperty;\r
11 import com.ibm.icu.util.ULocale;\r
12 \r
13 import java.text.FieldPosition;\r
14 import java.text.ParsePosition;\r
15 import java.util.HashMap;\r
16 import java.util.Locale;\r
17 import java.util.Map;\r
18 import java.util.Set;\r
19 \r
20 /**\r
21  * <p>\r
22  * <code>PluralFormat</code> supports the creation of internationalized\r
23  * messages with plural inflection. It is based on <i>plural\r
24  * selection</i>, i.e. the caller specifies messages for each\r
25  * plural case that can appear in the users language and the\r
26  * <code>PluralFormat</code> selects the appropriate message based on\r
27  * the number.\r
28  * </p>\r
29  * <h4>The Problem of Plural Forms in Internationalized Messages</h4>\r
30  * <p>\r
31  * Different languages have different ways to inflect\r
32  * plurals. Creating internationalized messages that include plural\r
33  * forms is only feasible when the framework is able to handle plural\r
34  * forms of <i>all</i> languages correctly. <code>ChoiceFormat</code>\r
35  * doesn't handle this well, because it attaches a number interval to\r
36  * each message and selects the message whose interval contains a\r
37  * given number. This can only handle a finite number of\r
38  * intervals. But in some languages, like Polish, one plural case\r
39  * applies to infinitely many intervals (e.g., paucal applies to\r
40  * numbers ending with 2, 3, or 4 except those ending with 12, 13, or\r
41  * 14). Thus <code>ChoiceFormat</code> is not adequate.\r
42  * </p><p>\r
43  * <code>PluralFormat</code> deals with this by breaking the problem\r
44  * into two parts:\r
45  * <ul>\r
46  * <li>It uses <code>PluralRules</code> that can define more complex\r
47  *     conditions for a plural case than just a single interval. These plural\r
48  *     rules define both what plural cases exist in a language, and to\r
49  *     which numbers these cases apply.\r
50  * <li>It provides predefined plural rules for many locales. Thus, the programmer\r
51  *     need not worry about the plural cases of a language. On the flip side,\r
52  *     the localizer does not have to specify the plural cases; he can simply\r
53  *     use the predefined keywords. The whole plural formatting of messages can\r
54  *     be done using localized patterns from resource bundles.\r
55  * </ul>\r
56  * </p>\r
57  * <h4>Usage of <code>PluralFormat</code></h4>\r
58  * <p>\r
59  * This discussion assumes that you use <code>PluralFormat</code> with\r
60  * a predefined set of plural rules. You can create one using one of\r
61  * the constructors that takes a <code>ULocale</code> object. To\r
62  * specify the message pattern, you can either pass it to the\r
63  * constructor or set it explicitly using the\r
64  * <code>applyPattern()</code> method. The <code>format()</code>\r
65  * method takes a number object and selects the message of the\r
66  * matching plural case. This message will be returned.\r
67  * </p>\r
68  * <h5>Patterns and Their Interpretation</h5>\r
69  * <p>\r
70  * The pattern text defines the message output for each plural case of the\r
71  * used locale. The pattern is a sequence of\r
72  * <code><i>caseKeyword</i>{<i>message</i>}</code> clauses, separated by white\r
73  * space characters. Each clause assigns the message <code><i>message</i></code>\r
74  * to the plural case identified by <code><i>caseKeyword</i></code>.\r
75  * </p><p>\r
76  * You always have to define a message text for the default plural case\r
77  * "<code>other</code>" which is contained in every rule set. If the plural\r
78  * rules of the <code>PluralFormat</code> object do not contain a plural case\r
79  * identified by <code><i>caseKeyword</i></code>, an\r
80  * <code>IllegalArgumentException</code> is thrown.\r
81  * If you do not specify a message text for a particular plural case, the\r
82  * message text of the plural case "<code>other</code>" gets assigned to this\r
83  * plural case. If you specify more than one message for the same plural case,\r
84  * an <code>IllegalArgumentException</code> is thrown.\r
85  * <br/>\r
86  * Spaces between <code><i>caseKeyword</i></code> and\r
87  * <code><i>message</i></code>  will be ignored; spaces within\r
88  * <code><i>message</i></code> will be preserved.\r
89  * </p><p>\r
90  * The message text for a particular plural case may contain other message\r
91  * format patterns. <code>PluralFormat</code> preserves these so that you\r
92  * can use the strings produced by <code>PluralFormat</code> with other\r
93  * formatters. If you are using <code>PluralFormat</code> inside a\r
94  * <code>MessageFormat</code> pattern, <code>MessageFormat</code> will\r
95  * automatically evaluate the resulting format pattern.<br/>\r
96  * Thus, curly braces (<code>{</code>, <code>}</code>) are <i>only</i> allowed\r
97  * in message texts to define a nested format pattern.<br/>\r
98  * The pound sign (<code>#</code>) will be interpreted as the number placeholder\r
99  * in the message text, if it is not contained in curly braces (to preserve\r
100  * <code>NumberFormat</code> patterns). <code>PluralFormat</code> will\r
101  * replace each of those pound signs by the number passed to the\r
102  * <code>format()</code> method. It will be formatted using a\r
103  * <code>NumberFormat</code> for the <code>PluralFormat</code>'s locale. If you\r
104  * need special number formatting, you have to explicitly specify a\r
105  * <code>NumberFormat</code> for the <code>PluralFormat</code> to use.\r
106  * </p>\r
107  * Example\r
108  * <pre>\r
109  * MessageFormat msgFmt = new MessageFormat("{0, plural, " +\r
110  *     "one{{0, number, C''''est #,##0.0#  fichier}} " +\r
111  *     "other {Ce sont # fichiers}} dans la liste.",\r
112  *     new ULocale("fr"));\r
113  * Object args[] = {new Long(0)};\r
114  * System.out.println(msgFmt.format(args));\r
115  * args = {new Long(3)};\r
116  * System.out.println(msgFmt.format(args));\r
117  * </pre>\r
118  * Produces the output:<br />\r
119  * <code>C'est 0,0 fichier dans la liste.</code><br />\r
120  * <code>Ce sont 3 fichiers dans la liste."</code>\r
121  * <p>\r
122  * <strong>Note:</strong><br />\r
123  *   Currently <code>PluralFormat</code>\r
124  *   does not make use of quotes like <code>MessageFormat</code>.\r
125  *   If you use plural format strings with <code>MessageFormat</code> and want\r
126  *   to use a quote sign "<code>'</code>", you have to write "<code>''</code>".\r
127  *   <code>MessageFormat</code> unquotes this pattern and  passes the unquoted\r
128  *   pattern to <code>PluralFormat</code>. It's a bit trickier if you use\r
129  *   nested formats that do quoting. In the example above, we wanted to insert\r
130  *   "<code>'</code>" in the number format pattern. Since\r
131  *   <code>NumberFormat</code> supports quotes, we had to insert\r
132  *   "<code>''</code>". But since <code>MessageFormat</code> unquotes the\r
133  *   pattern before it gets passed to <code>PluralFormat</code>, we have to\r
134  *   double these quotes, i.e. write "<code>''''</code>".\r
135  * </p>\r
136  * <h4>Defining Custom Plural Rules</h4>\r
137  * <p>If you need to use <code>PluralFormat</code> with custom rules, you can\r
138  * create a <code>PluralRules</code> object and pass it to\r
139  * <code>PluralFormat</code>'s constructor. If you also specify a locale in this\r
140  * constructor, this locale will be used to format the number in the message\r
141  * texts.\r
142  * </p><p>\r
143  * For more information about <code>PluralRules</code>, see\r
144  * {@link PluralRules}.\r
145  * </p>\r
146  *\r
147  * @author tschumann (Tim Schumann)\r
148  * @stable ICU 3.8\r
149  */\r
150 public class PluralFormat extends UFormat {\r
151     private static final long serialVersionUID = 1L;\r
152 \r
153     /*\r
154      * The locale used for standard number formatting and getting the predefined\r
155      * plural rules (if they were not defined explicitely).\r
156      */\r
157     private ULocale ulocale = null;\r
158 \r
159     /*\r
160      * The plural rules used for plural selection.\r
161      */\r
162     private PluralRules pluralRules = null;\r
163 \r
164     /*\r
165      * The applied pattern string.\r
166      */\r
167     private String pattern = null;\r
168 \r
169     /*\r
170      * The format messages for each plural case. It is a mapping:\r
171      *  <code>String</code>(plural case keyword) --&gt; <code>String</code>\r
172      *  (message for this plural case).\r
173      */\r
174     private Map parsedValues = null;\r
175 \r
176     /*\r
177      * This <code>NumberFormat</code> is used for the standard formatting of\r
178      * the number inserted into the message.\r
179      */\r
180     private NumberFormat numberFormat = null;\r
181 \r
182     /**\r
183      * Creates a new <code>PluralFormat</code> for the default locale.\r
184      * This locale will be used to get the set of plural rules and for standard\r
185      * number formatting.\r
186      * @stable ICU 3.8\r
187      */\r
188     public PluralFormat() {\r
189         init(null, ULocale.getDefault());\r
190     }\r
191 \r
192     /**\r
193      * Creates a new <code>PluralFormat</code> for a given locale.\r
194      * @param ulocale the <code>PluralFormat</code> will be configured with\r
195      *        rules for this locale. This locale will also be used for standard\r
196      *        number formatting.\r
197      * @stable ICU 3.8\r
198      */\r
199     public PluralFormat(ULocale ulocale) {\r
200         init(null, ulocale);\r
201     }\r
202 \r
203     /**\r
204      * Creates a new <code>PluralFormat</code> for a given set of rules.\r
205      * The standard number formatting will be done using the default locale.\r
206      * @param rules defines the behavior of the <code>PluralFormat</code>\r
207      *        object.\r
208      * @stable ICU 3.8\r
209      */\r
210     public PluralFormat(PluralRules rules) {\r
211         init(rules, ULocale.getDefault());\r
212     }\r
213 \r
214     /**\r
215      * Creates a new <code>PluralFormat</code> for a given set of rules.\r
216      * The standard number formatting will be done using the given locale.\r
217      * @param ulocale the default number formatting will be done using this\r
218      *        locale.\r
219      * @param rules defines the behavior of the <code>PluralFormat</code>\r
220      *        object.\r
221      * @stable ICU 3.8\r
222      */\r
223     public PluralFormat(ULocale ulocale, PluralRules rules) {\r
224         init(rules, ulocale);\r
225     }\r
226 \r
227     /**\r
228      * Creates a new <code>PluralFormat</code> for a given pattern string.\r
229      * The default locale will be used to get the set of plural rules and for\r
230      * standard number formatting.\r
231      * @param  pattern the pattern for this <code>PluralFormat</code>.\r
232      * @throws IllegalArgumentException if the pattern is invalid.\r
233      * @stable ICU 3.8\r
234      */\r
235     public PluralFormat(String pattern) {\r
236         init(null, ULocale.getDefault());\r
237         applyPattern(pattern);\r
238     }\r
239 \r
240     /**\r
241      * Creates a new <code>PluralFormat</code> for a given pattern string and\r
242      * locale.\r
243      * The locale will be used to get the set of plural rules and for\r
244      * standard number formatting.\r
245      * @param ulocale the <code>PluralFormat</code> will be configured with\r
246      *        rules for this locale. This locale will also be used for standard\r
247      *        number formatting.\r
248      * @param  pattern the pattern for this <code>PluralFormat</code>.\r
249      * @throws IllegalArgumentException if the pattern is invalid.\r
250      * @stable ICU 3.8\r
251      */\r
252     public PluralFormat(ULocale ulocale, String pattern) {\r
253         init(null, ulocale);\r
254         applyPattern(pattern);\r
255     }\r
256 \r
257     /**\r
258      * Creates a new <code>PluralFormat</code> for a given set of rules and a\r
259      * pattern.\r
260      * The standard number formatting will be done using the default locale.\r
261      * @param rules defines the behavior of the <code>PluralFormat</code>\r
262      *        object.\r
263      * @param  pattern the pattern for this <code>PluralFormat</code>.\r
264      * @throws IllegalArgumentException if the pattern is invalid.\r
265      * @stable ICU 3.8\r
266      */\r
267     public PluralFormat(PluralRules rules, String pattern) {\r
268         init(rules, ULocale.getDefault());\r
269         applyPattern(pattern);\r
270     }\r
271 \r
272     /**\r
273      * Creates a new <code>PluralFormat</code> for a given set of rules, a\r
274      * pattern and a locale.\r
275      * @param ulocale the <code>PluralFormat</code> will be configured with\r
276      *        rules for this locale. This locale will also be used for standard\r
277      *        number formatting.\r
278      * @param rules defines the behavior of the <code>PluralFormat</code>\r
279      *        object.\r
280      * @param  pattern the pattern for this <code>PluralFormat</code>.\r
281      * @throws IllegalArgumentException if the pattern is invalid.\r
282      * @stable ICU 3.8\r
283      */\r
284     public PluralFormat(ULocale ulocale, PluralRules rules, String pattern) {\r
285         init(rules, ulocale);\r
286         applyPattern(pattern);\r
287     }\r
288 \r
289     /*\r
290      * Initializes the <code>PluralRules</code> object.\r
291      * Postcondition:<br/>\r
292      *   <code>ulocale</code>    :  is <code>locale</code><br/>\r
293      *   <code>pluralRules</code>:  if <code>rules</code> != <code>null</code>\r
294      *                              it's set to rules, otherwise it is the\r
295      *                              predefined plural rule set for the locale\r
296      *                              <code>ulocale</code>.<br/>\r
297      *   <code>parsedValues</code>: is <code>null</code><br/>\r
298      *   <code>pattern</code>:      is <code>null</code><br/>\r
299      *   <code>numberFormat</code>: a <code>NumberFormat</code> for the locale\r
300      *                              <code>ulocale</code>.\r
301      */\r
302     private void init(PluralRules rules, ULocale locale) {\r
303         ulocale = locale;\r
304         pluralRules = (rules == null) ? PluralRules.forLocale(ulocale)\r
305                                       : rules;\r
306         parsedValues = null;\r
307         pattern = null;\r
308         numberFormat = NumberFormat.getInstance(ulocale);\r
309     }\r
310 \r
311     /**\r
312      * Sets the pattern used by this plural format.\r
313      * The method parses the pattern and creates a map of format strings\r
314      * for the plural rules.\r
315      * Patterns and their interpretation are specified in the class description.\r
316      *\r
317      * @param pttrn the pattern for this plural format.\r
318      * @throws IllegalArgumentException if the pattern is invalid.\r
319      * @stable ICU 3.8\r
320      */\r
321     public void applyPattern(String pttrn) {\r
322         pttrn = pttrn.trim();\r
323 \r
324         this.pattern = pttrn;\r
325         int braceStack = 0;\r
326         Set ruleNames = pluralRules.getKeywords();\r
327         parsedValues = new HashMap();\r
328 \r
329         // Format string has to include keywords.\r
330         // states:\r
331         // 0: Reading keyword.\r
332         // 1: Reading value for preceding keyword.\r
333         int state = 0;\r
334         StringBuffer token = new StringBuffer();\r
335         String currentKeyword = null;\r
336         boolean readSpaceAfterKeyword = false;\r
337         for (int i = 0; i < pttrn.length(); ++i) {\r
338             char ch = pttrn.charAt(i);\r
339             switch (state) {\r
340             case 0: // Reading value.\r
341                 if (token.length() == 0) {\r
342                     readSpaceAfterKeyword = false;\r
343                 }\r
344                 if (UCharacterProperty.isRuleWhiteSpace(ch)) {\r
345                     if (token.length() > 0) {\r
346                         readSpaceAfterKeyword = true;\r
347                     }\r
348                     // Skip leading and trailing whitespaces.\r
349                     break;\r
350                 }\r
351                 if (ch == '{') { // End of keyword definition reached.\r
352                     currentKeyword = token.toString().toLowerCase(\r
353                             Locale.ENGLISH);\r
354                     if (!ruleNames.contains(currentKeyword)) {\r
355                         parsingFailure("Malformed formatting expression. "\r
356                                 + "Unknown keyword \"" + currentKeyword\r
357                                 + "\" at position " + i + ".");\r
358                     }\r
359                     if (parsedValues.get(currentKeyword) != null) {\r
360                         parsingFailure("Malformed formatting expression. "\r
361                                 + "Text for case \"" + currentKeyword\r
362                                 + "\" at position " + i + " already defined!");\r
363                     }\r
364                     token.delete(0, token.length());\r
365                     braceStack++;\r
366                     state = 1;\r
367                     break;\r
368                 }\r
369                 if (readSpaceAfterKeyword) {\r
370                     parsingFailure("Malformed formatting expression. " +\r
371                             "Invalid keyword definition. Character \"" + ch +\r
372                             "\" at position " + i + " not expected!");\r
373                 }\r
374                 token.append(ch);\r
375                 break;\r
376             case 1: // Reading value.\r
377                 switch (ch) {\r
378                 case '{':\r
379                     braceStack++;\r
380                     token.append(ch);\r
381                     break;\r
382                 case '}':\r
383                     braceStack--;\r
384                     if (braceStack == 0) { // End of value reached.\r
385                         parsedValues.put(currentKeyword, token.toString());\r
386                         token.delete(0, token.length());\r
387                         state = 0;\r
388                     } else if (braceStack < 0) {\r
389                         parsingFailure("Malformed formatting expression. "\r
390                                 + "Braces do not match.");\r
391                     } else { // braceStack > 0\r
392                         token.append(ch);\r
393                     }\r
394                     break;\r
395                 default:\r
396                     token.append(ch);\r
397                 }\r
398                 break;\r
399             } // switch state\r
400         } // for loop.\r
401         if (braceStack != 0) {\r
402             parsingFailure(\r
403                     "Malformed formatting expression. Braces do not match.");\r
404         }\r
405         checkSufficientDefinition();\r
406     }\r
407 \r
408     /**\r
409      * Returns the pattern for this PluralFormat.\r
410      *\r
411      * @return the pattern string\r
412      * @draft ICU 4.2\r
413      * @provisional This API might change or be removed in a future release.\r
414      */\r
415     public String toPattern() {\r
416         return pattern;\r
417     }\r
418 \r
419     /**\r
420      * Formats a plural message for a given number.\r
421      *\r
422      * @param number a number for which the plural message should be formatted.\r
423      *        If no pattern has been applied to this\r
424      *        <code>PluralFormat</code> object yet, the formatted number will\r
425      *        be returned.\r
426      * @return the string containing the formatted plural message.\r
427      * @stable ICU 4.0\r
428      */\r
429     public final String format(double number) {\r
430         // If no pattern was applied, return the formatted number.\r
431         if (parsedValues == null) {\r
432             return numberFormat.format(number);\r
433         }\r
434 \r
435         // Get appropriate format pattern.\r
436         String selectedRule = pluralRules.select(number);\r
437         String selectedPattern = (String) parsedValues.get(selectedRule);\r
438         if (selectedPattern == null) { // Fallback to others.\r
439             selectedPattern =\r
440                 (String) parsedValues.get(PluralRules.KEYWORD_OTHER);\r
441         }\r
442         // Get formatted number and insert it into String.\r
443         // Will replace all '#' which are not inside curly braces by the\r
444         // formatted number.\r
445         return insertFormattedNumber(number, selectedPattern);\r
446     }\r
447 \r
448     /**\r
449      * Formats a plural message for a given number and appends the formatted\r
450      * message to the given <code>StringBuffer</code>.\r
451      * @param number a number object (instance of <code>Number</code> for which\r
452      *        the plural message should be formatted. If no pattern has been\r
453      *        applied to this <code>PluralFormat</code> object yet, the\r
454      *        formatted number will be returned.\r
455      *        Note: If this object is not an instance of <code>Number</code>,\r
456      *              the <code>toAppendTo</code> will not be modified.\r
457      * @param toAppendTo the formatted message will be appended to this\r
458      *        <code>StringBuffer</code>.\r
459      * @param pos will be ignored by this method.\r
460      * @return the string buffer passed in as toAppendTo, with formatted text\r
461      *         appended.\r
462      * @throws IllegalArgumentException if number is not an instance of Number\r
463      * @stable ICU 3.8\r
464      */\r
465     public StringBuffer format(Object number, StringBuffer toAppendTo,\r
466             FieldPosition pos) {\r
467         if (number instanceof Number) {\r
468             toAppendTo.append(format(((Number) number).doubleValue()));\r
469             return toAppendTo;\r
470         }\r
471         throw new IllegalArgumentException("'" + number +\r
472                                            "' is not a Number");\r
473     }\r
474 \r
475     /**\r
476      * This method is not yet supported by <code>PluralFormat</code>.\r
477      * @param text the string to be parsed.\r
478      * @param parsePosition defines the position where parsing is to begin,\r
479      * and upon return, the position where parsing left off.  If the position\r
480      * has not changed upon return, then parsing failed.\r
481      * @return nothing because this method is not yet implemented.\r
482      * @throws UnsupportedOperationException\r
483      *     will always be thrown by this method.\r
484      * @stable ICU 3.8\r
485      */\r
486     public Number parse(String text, ParsePosition parsePosition) {\r
487         throw new UnsupportedOperationException();\r
488     }\r
489 \r
490     /**\r
491      * This method is not yet supported by <code>PluralFormat</code>.\r
492      * @param source the string to be parsed.\r
493      * @param pos defines the position where parsing is to begin,\r
494      * and upon return, the position where parsing left off.  If the position\r
495      * has not changed upon return, then parsing failed.\r
496      * @return nothing because this method is not yet implemented.\r
497      * @throws UnsupportedOperationException\r
498      *     will always be thrown by this method.\r
499      * @stable ICU 3.8\r
500      */\r
501     public Object parseObject(String source, ParsePosition pos) {\r
502         throw new UnsupportedOperationException();\r
503     }\r
504 \r
505     /**\r
506      * Sets the locale used by this <code>PluraFormat</code> object.\r
507      * Note: Calling this method resets this <code>PluraFormat</code> object,\r
508      *     i.e., a pattern that was applied previously will be removed,\r
509      *     and the NumberFormat is set to the default number format for\r
510      *     the locale.  The resulting format behaves the same as one\r
511      *     constructed from {@link #PluralFormat(ULocale)}.\r
512      * @param ulocale the <code>ULocale</code> used to configure the\r
513      *     formatter. If <code>ulocale</code> is <code>null</code>, the\r
514      *     default locale will be used.\r
515      * @stable ICU 3.8\r
516      */\r
517     public void setLocale(ULocale ulocale) {\r
518         if (ulocale == null) {\r
519             ulocale = ULocale.getDefault();\r
520         }\r
521         init(null, ulocale);\r
522     }\r
523 \r
524     /**\r
525      * Sets the number format used by this formatter.  You only need to\r
526      * call this if you want a different number format than the default\r
527      * formatter for the locale.\r
528      * @param format the number format to use.\r
529      * @stable ICU 3.8\r
530      */\r
531     public void setNumberFormat(NumberFormat format) {\r
532         numberFormat = format;\r
533     }\r
534 \r
535     /*\r
536      * Checks if the applied pattern provided enough information,\r
537      * i.e., if the attribute <code>parsedValues</code> stores enough\r
538      * information for plural formatting.\r
539      * Will be called at the end of pattern parsing.\r
540      * @throws IllegalArgumentException if there's not sufficient information\r
541      *     provided.\r
542      */\r
543     private void checkSufficientDefinition() {\r
544         // Check that at least the default rule is defined.\r
545         if (parsedValues.get(PluralRules.KEYWORD_OTHER) == null) {\r
546             parsingFailure("Malformed formatting expression.\n"\r
547                     + "Value for case \"" + PluralRules.KEYWORD_OTHER\r
548                     + "\" was not defined.");\r
549         }\r
550     }\r
551 \r
552     /*\r
553      * Helper method that resets the <code>PluralFormat</code> object and throws\r
554      * an <code>IllegalArgumentException</code> with a given error text.\r
555      * @param errorText the error text of the exception message.\r
556      * @throws IllegalArgumentException will always be thrown by this method.\r
557      */\r
558     private void parsingFailure(String errorText) {\r
559         // Set PluralFormat to a valid state.\r
560         init(null, ULocale.getDefault());\r
561         throw new IllegalArgumentException(errorText);\r
562     }\r
563 \r
564     /*\r
565      * Helper method that is called during formatting.\r
566      * It replaces the character '#' by the number used for plural selection in\r
567      * a message text. Only '#' are replaced, that are not written inside curly\r
568      * braces. This allows the use of nested number formats.\r
569      * The number will be formatted using the attribute\r
570      * <code>numberformat</code>.\r
571      * @param number the number used for plural selection.\r
572      * @param message is the text in which '#' will be replaced.\r
573      * @return the text with inserted numbers.\r
574      */\r
575     private String insertFormattedNumber(double number, String message) {\r
576         if (message == null) {\r
577             return "";\r
578         }\r
579         String formattedNumber = numberFormat.format(number);\r
580         StringBuffer result = new StringBuffer();\r
581         int braceStack = 0;\r
582         int startIndex = 0;\r
583         for (int i = 0; i < message.length(); ++i) {\r
584             switch (message.charAt(i)) {\r
585             case '{':\r
586                 ++braceStack;\r
587                 break;\r
588             case '}':\r
589                 --braceStack;\r
590                 break;\r
591             case '#':\r
592                 if (braceStack == 0) {\r
593                     result.append(message.substring(startIndex,i));\r
594                     startIndex = i + 1;\r
595                     result.append(formattedNumber);\r
596                 }\r
597                 break;\r
598             }\r
599         }\r
600         if (startIndex < message.length()) {\r
601             result.append(message.substring(startIndex, message.length()));\r
602         }\r
603         return result.toString();\r
604     }\r
605 \r
606     /**\r
607      * {@inheritDoc}\r
608      * @stable ICU 3.8\r
609      */\r
610     public boolean equals(Object rhs) {\r
611         return rhs instanceof PluralFormat && equals((PluralFormat) rhs);\r
612     }\r
613 \r
614     /**\r
615      * Returns true if this equals the provided PluralFormat.\r
616      * @param rhs the PluralFormat to compare against\r
617      * @return true if this equals rhs\r
618      * @stable ICU 3.8\r
619      */\r
620     public boolean equals(PluralFormat rhs) {\r
621       return pluralRules.equals(rhs.pluralRules) &&\r
622           parsedValues.equals(rhs.parsedValues) &&\r
623           numberFormat.equals(rhs.numberFormat);\r
624     }\r
625 \r
626     /**\r
627      * {@inheritDoc}\r
628      * @stable ICU 3.8\r
629      */\r
630     public int hashCode() {\r
631         return pluralRules.hashCode() ^ parsedValues.hashCode();\r
632     }\r
633 \r
634     /**\r
635      * For debugging purposes only\r
636      * @return a text representation of the format data.\r
637      * @stable ICU 3.8\r
638      */\r
639     public String toString() {\r
640         StringBuffer buf = new StringBuffer();\r
641         buf.append("locale=" + ulocale);\r
642         buf.append(", rules='" + pluralRules + "'");\r
643         buf.append(", pattern='" + pattern + "'");\r
644         buf.append(", parsedValues='" + parsedValues + "'");\r
645         buf.append(", format='" + numberFormat + "'");\r
646         return buf.toString();\r
647     }\r
648 }\r