KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > google > gwt > i18n > client > NumberFormat


1 /*
2  * Copyright 2007 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */

16 package com.google.gwt.i18n.client;
17
18 import com.google.gwt.core.client.GWT;
19 import com.google.gwt.i18n.client.constants.CurrencyCodeMapConstants;
20 import com.google.gwt.i18n.client.constants.NumberConstants;
21
22 import java.util.Map JavaDoc;
23
24 /**
25  * Formats and parses numbers using locale-sensitive patterns.
26  *
27  * This class provides comprehensive and flexible support for a wide variety of
28  * localized formats, including
29  * <ul>
30  * <li><b>Locale-specific symbols</b> such as decimal point, group separator,
31  * digit representation, currency symbol, percent, and permill</li>
32  * <li><b>Numeric variations</b> including integers ("123"), fixed-point
33  * numbers ("123.4"), scientific notation ("1.23E4"), percentages ("12%"), and
34  * currency amounts ("$123")</li>
35  * <li><b>Predefined standard patterns</b> that can be used both for parsing
36  * and formatting, including {@link #getDecimalFormat() decimal},
37  * {@link #getCurrencyFormat() currency},
38  * {@link #getPercentFormat() percentages}, and
39  * {@link #getScientificFormat() scientific}</li>
40  * <li><b>Custom patterns</b> and supporting features designed to make it
41  * possible to parse and format numbers in any locale, including support for
42  * Western, Arabic, and Indic digits</li>
43  * </ul>
44  *
45  * <h3>Patterns</h3>
46  * <p>
47  * Formatting and parsing are based on customizable patterns that can include a
48  * combination of literal characters and special characters that act as
49  * placeholders and are replaced by their localized counterparts. Many
50  * characters in a pattern are taken literally; they are matched during parsing
51  * and output unchanged during formatting. Special characters, on the other
52  * hand, stand for other characters, strings, or classes of characters. For
53  * example, the '<code>#</code>' character is replaced by a localized digit.
54  * </p>
55  *
56  * <p>
57  * Often the replacement character is the same as the pattern character. In the
58  * U.S. locale, for example, the '<code>,</code>' grouping character is
59  * replaced by the same character '<code>,</code>'. However, the replacement
60  * is still actually happening, and in a different locale, the grouping
61  * character may change to a different character, such as '<code>.</code>'.
62  * Some special characters affect the behavior of the formatter by their
63  * presence. For example, if the percent character is seen, then the value is
64  * multiplied by 100 before being displayed.
65  * </p>
66  *
67  * <p>
68  * The characters listed below are used in patterns. Localized symbols use the
69  * corresponding characters taken from corresponding locale symbol collection,
70  * which can be found in the properties files residing in the
71  * <code><nobr>com.google.gwt.i18n.client.constants</nobr></code>. To insert
72  * a special character in a pattern as a literal (that is, without any special
73  * meaning) the character must be quoted. There are some exceptions to this
74  * which are noted below.
75  * </p>
76  *
77  * <table>
78  * <tr>
79  * <th>Symbol</th>
80  * <th>Location</th>
81  * <th>Localized?</th>
82  * <th>Meaning</th>
83  * </tr>
84  *
85  * <tr>
86  * <td><code>0</code></td>
87  * <td>Number</td>
88  * <td>Yes</td>
89  * <td>Digit</td>
90  * </tr>
91  *
92  * <tr>
93  * <td><code>#</code></td>
94  * <td>Number</td>
95  * <td>Yes</td>
96  * <td>Digit, zero shows as absent</td>
97  * </tr>
98  *
99  * <tr>
100  * <td><code>.</code></td>
101  * <td>Number</td>
102  * <td>Yes</td>
103  * <td>Decimal separator or monetary decimal separator</td>
104  * </tr>
105  *
106  * <tr>
107  * <td><code>-</code></td>
108  * <td>Number</td>
109  * <td>Yes</td>
110  * <td>Minus sign</td>
111  * </tr>
112  *
113  * <tr>
114  * <td><code>,</code></td>
115  * <td>Number</td>
116  * <td>Yes</td>
117  * <td>Grouping separator</td>
118  * </tr>
119  *
120  * <tr>
121  * <td><code>E</code></td>
122  * <td>Number</td>
123  * <td>Yes</td>
124  * <td>Separates mantissa and exponent in scientific notation; need not be
125  * quoted in prefix or suffix</td>
126  * </tr>
127  *
128  * <tr>
129  * <td><code>E</code></td>
130  * <td>Subpattern boundary</td>
131  * <td>Yes</td>
132  * <td>Separates positive and negative subpatterns</td>
133  * </tr>
134  *
135  * <tr>
136  * <td><code>%</code></td>
137  * <td>Prefix or suffix</td>
138  * <td>Yes</td>
139  * <td>Multiply by 100 and show as percentage</td>
140  * </tr>
141  *
142  * <tr>
143  * <td><nobr><code>‰</code> (\u2030)</nobr></td>
144  * <td>Prefix or suffix</td>
145  * <td>Yes</td>
146  * <td>Multiply by 1000 and show as per mille</td>
147  * </tr>
148  *
149  * <tr>
150  * <td><nobr><code>¤</code> (\u00A4)</nobr></td>
151  * <td>Prefix or suffix</td>
152  * <td>No</td>
153  * <td>Currency sign, replaced by currency symbol; if doubled, replaced by
154  * international currency symbol; if present in a pattern, the monetary decimal
155  * separator is used instead of the decimal separator</td>
156  * </tr>
157  *
158  * <tr>
159  * <td><code>'</code></td>
160  * <td>Prefix or suffix</td>
161  * <td>No</td>
162  * <td>Used to quote special characters in a prefix or suffix; for example,
163  * <code>"'#'#"</code> formats <code>123</code> to <code>"#123"</code>;
164  * to create a single quote itself, use two in succession, such as
165  * <code>"# o''clock"</code></td>
166  * </tr>
167  *
168  * </table>
169  *
170  * <p>
171  * A <code>NumberFormat</code> pattern contains a postive and negative
172  * subpattern separated by a semicolon, such as
173  * <code>"#,##0.00;(#,##0.00)"</code>. Each subpattern has a prefix, a
174  * numeric part, and a suffix. If there is no explicit negative subpattern, the
175  * negative subpattern is the localized minus sign prefixed to the positive
176  * subpattern. That is, <code>"0.00"</code> alone is equivalent to
177  * <code>"0.00;-0.00"</code>. If there is an explicit negative subpattern, it
178  * serves only to specify the negative prefix and suffix; the number of digits,
179  * minimal digits, and other characteristics are ignored in the negative
180  * subpattern. That means that <code>"#,##0.0#;(#)"</code> has precisely the
181  * same result as <code>"#,##0.0#;(#,##0.0#)"</code>.
182  * </p>
183  *
184  * <p>
185  * The prefixes, suffixes, and various symbols used for infinity, digits,
186  * thousands separators, decimal separators, etc. may be set to arbitrary
187  * values, and they will appear properly during formatting. However, care must
188  * be taken that the symbols and strings do not conflict, or parsing will be
189  * unreliable. For example, the decimal separator and thousands separator should
190  * be distinct characters, or parsing will be impossible.
191  * </p>
192  *
193  * <p>
194  * The grouping separator is a character that separates clusters of integer
195  * digits to make large numbers more legible. It commonly used for thousands,
196  * but in some locales it separates ten-thousands. The grouping size is the
197  * number of digits between the grouping separators, such as 3 for "100,000,000"
198  * or 4 for "1 0000 0000".
199  * </p>
200  *
201  * <h3>Pattern Grammar (BNF)</h3>
202  * <p>
203  * The pattern itself uses the following grammar:
204  * </p>
205  *
206  * <table>
207  * <tr>
208  * <td>pattern</td>
209  * <td>:=</td>
210  * <td style="white-space: nowrap">subpattern ('<code>;</code>'
211  * subpattern)?</td>
212  * </tr>
213  * <tr>
214  * <td>subpattern</td>
215  * <td>:=</td>
216  * <td>prefix? number exponent? suffix?</td>
217  * </tr>
218  * <tr>
219  * <td>number</td>
220  * <td>:=</td>
221  * <td style="white-space: nowrap">(integer ('<code>.</code>' fraction)?) |
222  * sigDigits</td>
223  * </tr>
224  * <tr>
225  * <td>prefix</td>
226  * <td>:=</td>
227  * <td style="white-space: nowrap">'<code>\u0000</code>'..'<code>\uFFFD</code>' -
228  * specialCharacters</td>
229  * </tr>
230  * <tr>
231  * <td>suffix</td>
232  * <td>:=</td>
233  * <td style="white-space: nowrap">'<code>\u0000</code>'..'<code>\uFFFD</code>' -
234  * specialCharacters</td>
235  * </tr>
236  * <tr>
237  * <td>integer</td>
238  * <td>:=</td>
239  * <td style="white-space: nowrap">'<code>#</code>'* '<code>0</code>'*'<code>0</code>'</td>
240  * </tr>
241  * <tr>
242  * <td>fraction</td>
243  * <td>:=</td>
244  * <td style="white-space: nowrap">'<code>0</code>'* '<code>#</code>'*</td>
245  * </tr>
246  * <tr>
247  * <td>sigDigits</td>
248  * <td>:=</td>
249  * <td style="white-space: nowrap">'<code>#</code>'* '<code>@</code>''<code>@</code>'* '<code>#</code>'*</td>
250  * </tr>
251  * <tr>
252  * <td>exponent</td>
253  * <td>:=</td>
254  * <td style="white-space: nowrap">'<code>E</code>' '<code>+</code>'? '<code>0</code>'* '<code>0</code>'</td>
255  * </tr>
256  * <tr>
257  * <td>padSpec</td>
258  * <td>:=</td>
259  * <td style="white-space: nowrap">'<code>*</code>' padChar</td>
260  * </tr>
261  * <tr>
262  * <td>padChar</td>
263  * <td>:=</td>
264  * <td>'<code>\u0000</code>'..'<code>\uFFFD</code>' - quote</td>
265  * </tr>
266  * </table>
267  *
268  * <p>
269  * Notation:
270  * </p>
271  *
272  * <table>
273  * <tr>
274  * <td>X*</td>
275  * <td style="white-space: nowrap">0 or more instances of X</td>
276  * </tr>
277  *
278  * <tr>
279  * <td>X?</td>
280  * <td style="white-space: nowrap">0 or 1 instances of X</td>
281  * </tr>
282  *
283  * <tr>
284  * <td>X|Y</td>
285  * <td style="white-space: nowrap">either X or Y</td>
286  * </tr>
287  *
288  * <tr>
289  * <td>C..D</td>
290  * <td style="white-space: nowrap">any character from C up to D, inclusive</td>
291  * </tr>
292  *
293  * <tr>
294  * <td>S-T</td>
295  * <td style="white-space: nowrap">characters in S, except those in T</td>
296  * </tr>
297  * </table>
298  *
299  * <p>
300  * The first subpattern is for positive numbers. The second (optional)
301  * subpattern is for negative numbers.
302  * </p>
303  */

304 public class NumberFormat {
305
306   // Sets of constants as defined for the default locale.
307
private static final NumberConstants defaultNumberConstants = (NumberConstants) GWT.create(NumberConstants.class);
308   private static final CurrencyCodeMapConstants defaultCurrencyCodeMapConstants = (CurrencyCodeMapConstants) GWT.create(CurrencyCodeMapConstants.class);
309
310   // Constants for characters used in programmatic (unlocalized) patterns.
311
private static final char PATTERN_ZERO_DIGIT = '0';
312   private static final char PATTERN_GROUPING_SEPARATOR = ',';
313   private static final char PATTERN_DECIMAL_SEPARATOR = '.';
314   private static final char PATTERN_PER_MILLE = '\u2030';
315   private static final char PATTERN_PERCENT = '%';
316   private static final char PATTERN_DIGIT = '#';
317   private static final char PATTERN_SEPARATOR = ';';
318   private static final char PATTERN_EXPONENT = 'E';
319   private static final char PATTERN_MINUS = '-';
320   private static final char CURRENCY_SIGN = '\u00A4';
321   private static final char QUOTE = '\'';
322
323   // Cached instances of standard formatters.
324
private static NumberFormat cachedDecimalFormat;
325   private static NumberFormat cachedScientificFormat;
326   private static NumberFormat cachedPercentFormat;
327   private static NumberFormat cachedCurrencyFormat;
328
329   /**
330    * Provides the standard currency format for the default locale.
331    *
332    * @return a <code>NumberFormat</code> capable of producing and consuming
333    * currency format for the default locale
334    */

335   public static NumberFormat getCurrencyFormat() {
336     if (cachedCurrencyFormat == null) {
337       cachedCurrencyFormat = new NumberFormat(
338           defaultNumberConstants.currencyPattern(),
339           defaultNumberConstants.defCurrencyCode());
340     }
341     return cachedCurrencyFormat;
342   }
343
344   /**
345    * Provides the standard decimal format for the default locale.
346    *
347    * @return a <code>NumberFormat</code> capable of producing and consuming
348    * decimal format for the default locale
349    */

350   public static NumberFormat getDecimalFormat() {
351     if (cachedDecimalFormat == null) {
352       cachedDecimalFormat = new NumberFormat(
353           defaultNumberConstants.decimalPattern(),
354           defaultNumberConstants.defCurrencyCode());
355     }
356     return cachedDecimalFormat;
357   }
358
359   /**
360    * Gets a <code>NumberFormat</code> instance for the default locale using
361    * the specified pattern and the default currencyCode.
362    *
363    * @param pattern pattern for this formatter
364    * @return a NumberFormat instance
365    * @throws IllegalArgumentException if the specified pattern is invalid
366    */

367   public static NumberFormat getFormat(String JavaDoc pattern) {
368     return new NumberFormat(pattern, defaultNumberConstants.defCurrencyCode());
369   }
370
371   /**
372    * Gets a custom <code>NumberFormat</code> instance for the default locale
373    * using the specified pattern and currency code.
374    *
375    * @param pattern pattern for this formatter
376    * @param currencyCode international currency code
377    * @return a NumberFormat instance
378    * @throws IllegalArgumentException if the specified pattern is invalid
379    */

380   public static NumberFormat getFormat(String JavaDoc pattern, String JavaDoc currencyCode) {
381     return new NumberFormat(pattern, currencyCode);
382   }
383
384   /**
385    * Provides the standard percent format for the default locale.
386    *
387    * @return a <code>NumberFormat</code> capable of producing and consuming
388    * percent format for the default locale
389    */

390   public static NumberFormat getPercentFormat() {
391     if (cachedPercentFormat == null) {
392       cachedPercentFormat = new NumberFormat(
393           defaultNumberConstants.percentPattern(),
394           defaultNumberConstants.defCurrencyCode());
395     }
396     return cachedPercentFormat;
397   }
398
399   /**
400    * Provides the standard scientific format for the default locale.
401    *
402    * @return a <code>NumberFormat</code> capable of producing and consuming
403    * scientific format for the default locale
404    */

405   public static NumberFormat getScientificFormat() {
406     if (cachedScientificFormat == null) {
407       cachedScientificFormat = new NumberFormat(
408           defaultNumberConstants.scientificPattern(),
409           defaultNumberConstants.defCurrencyCode());
410     }
411     return cachedScientificFormat;
412   }
413
414   // Locale specific symbol collection.
415
private final NumberConstants numberConstants;
416
417   private int maximumIntegerDigits = 40;
418   private int minimumIntegerDigits = 1;
419   private int maximumFractionDigits = 3; // invariant, >= minFractionDigits.
420
private int minimumFractionDigits = 0;
421   private int minExponentDigits;
422
423   private String JavaDoc positivePrefix = "";
424   private String JavaDoc positiveSuffix = "";
425   private String JavaDoc negativePrefix = "-";
426   private String JavaDoc negativeSuffix = "";
427
428   // The multiplier for use in percent, per mille, etc.
429
private int multiplier = 1;
430
431   // The number of digits between grouping separators in the integer
432
// portion of a number.
433
private int groupingSize = 3;
434
435   // Forces the decimal separator to always appear in a formatted number.
436
private boolean decimalSeparatorAlwaysShown = false;
437
438   private boolean isCurrencyFormat = false;
439
440   // True to force the use of exponential (i.e. scientific) notation.
441
private boolean useExponentialNotation = false;
442
443   // Currency setting.
444
private final String JavaDoc currencySymbol;
445
446   // The currency code.
447
private final String JavaDoc currencyCode;
448
449   // The pattern to use for formatting and parsing.
450
private final String JavaDoc pattern;
451
452   /**
453    * Constructs a format object based on the specified settings.
454    *
455    * @param numberConstants the locale-specific number constants to use for this
456    * format
457    * @param currencyCodeMapConstants the locale-specific currency code map to
458    * use for this format
459    * @param pattern pattern that specify how number should be formatted
460    * @param currencyCode currency that should be used
461    * @skip
462    */

463   protected NumberFormat(NumberConstants numberConstants,
464       CurrencyCodeMapConstants currencyCodeMapConstants, String JavaDoc pattern,
465       String JavaDoc currencyCode) {
466     this.numberConstants = numberConstants;
467     this.pattern = pattern;
468     this.currencyCode = currencyCode;
469
470     Map JavaDoc currencyMap = currencyCodeMapConstants.currencyMap();
471     currencySymbol = (String JavaDoc) currencyMap.get(currencyCode);
472
473     parsePattern(this.pattern);
474   }
475
476   /**
477    * Constructs a format object for the default locale based on the specified
478    * settings.
479    *
480    * @param pattern pattern that specify how number should be formatted
481    * @param currencyCode currency that should be used
482    * @skip
483    */

484   protected NumberFormat(String JavaDoc pattern, String JavaDoc currencyCode) {
485     this(defaultNumberConstants, defaultCurrencyCodeMapConstants, pattern,
486         currencyCode);
487   }
488
489   /**
490    * This method formats a double to produce a string.
491    *
492    * @param number The double to format
493    * @return the formatted number string
494    */

495   public String JavaDoc format(double number) {
496     StringBuffer JavaDoc result = new StringBuffer JavaDoc();
497
498     if (Double.isNaN(number)) {
499       result.append(numberConstants.notANumber());
500       return result.toString();
501     }
502
503     boolean isNegative = ((number < 0.0) || (number == 0.0 && 1 / number < 0.0));
504
505     result.append(isNegative ? negativePrefix : positivePrefix);
506     if (Double.isInfinite(number)) {
507       result.append(numberConstants.infinity());
508     } else {
509       if (isNegative) {
510         number = -number;
511       }
512       number *= multiplier;
513       if (useExponentialNotation) {
514         subformatExponential(number, result);
515       } else {
516         subformatFixed(number, result, minimumIntegerDigits);
517       }
518     }
519
520     result.append(isNegative ? negativeSuffix : positiveSuffix);
521
522     return result.toString();
523   }
524
525   /**
526    * Returns the pattern used by this number format.
527    */

528   public String JavaDoc getPattern() {
529     return pattern;
530   }
531
532   /**
533    * Parses text to produce a numeric value. A {@link NumberFormatException} is
534    * thrown if either the text is empty or if the parse does not consume all
535    * characters of the text.
536    *
537    * @param text the string being parsed
538    * @return a parsed number value
539    * @throws NumberFormatException if the entire text could not be converted
540    * into a number
541    */

542   public double parse(String JavaDoc text) {
543     int[] pos = {0};
544     double result = parse(text, pos);
545     if (pos[0] == 0 || pos[0] != text.length()) {
546       throw new NumberFormatException JavaDoc(text);
547     }
548     return result;
549   }
550
551   /**
552    * Parses text to produce a numeric value.
553    *
554    * <p>
555    * The method attempts to parse text starting at the index given by pos. If
556    * parsing succeeds, then the index of <code>pos</code> is updated to the
557    * index after the last character used (parsing does not necessarily use all
558    * characters up to the end of the string), and the parsed number is returned.
559    * The updated <code>pos</code> can be used to indicate the starting point
560    * for the next call to this method. If an error occurs, then the index of
561    * <code>pos</code> is not changed.
562    * </p>
563    *
564    * @param text the string to be parsed
565    * @param inOutPos position to pass in and get back
566    * @return a double value representing the parsed number, or <code>0.0</code>
567    * if the parse fails
568    */

569   public double parse(String JavaDoc text, int[] inOutPos) {
570     int start = inOutPos[0];
571     boolean gotPositive, gotNegative;
572     double ret = 0.0;
573
574     gotPositive = (text.indexOf(positivePrefix, inOutPos[0]) == inOutPos[0]);
575     gotNegative = (text.indexOf(negativePrefix, inOutPos[0]) == inOutPos[0]);
576
577     if (gotPositive && gotNegative) {
578       if (positivePrefix.length() > negativePrefix.length()) {
579         gotNegative = false;
580       } else if (positivePrefix.length() < negativePrefix.length()) {
581         gotPositive = false;
582       }
583     }
584
585     if (gotPositive) {
586       inOutPos[0] += positivePrefix.length();
587     } else if (gotNegative) {
588       inOutPos[0] += negativePrefix.length();
589     }
590
591     // Process digits or Inf, and find decimal position.
592
if (text.indexOf(numberConstants.infinity(), inOutPos[0]) == inOutPos[0]) {
593       inOutPos[0] += numberConstants.infinity().length();
594       ret = Double.POSITIVE_INFINITY;
595     } else if (text.indexOf(numberConstants.notANumber(), inOutPos[0]) == inOutPos[0]) {
596       inOutPos[0] += numberConstants.notANumber().length();
597       ret = Double.NaN;
598     } else {
599       ret = parseNumber(text, inOutPos);
600     }
601
602     // Check for suffix.
603
if (gotPositive) {
604       if (!(text.indexOf(positiveSuffix, inOutPos[0]) == inOutPos[0])) {
605         inOutPos[0] = start;
606         return 0.0;
607       }
608       inOutPos[0] += positiveSuffix.length();
609     } else if (gotNegative) {
610       if (!(text.indexOf(negativeSuffix, inOutPos[0]) == inOutPos[0])) {
611         inOutPos[0] = start;
612         return 0.0;
613       }
614       inOutPos[0] += negativeSuffix.length();
615     }
616
617     if (gotNegative) {
618       ret = -ret;
619     }
620
621     return ret;
622   }
623
624   /**
625    * This method formats the exponent part of a double.
626    *
627    * @param exponent exponential value
628    * @param result formatted exponential part will be append to it
629    */

630   private void addExponentPart(int exponent, StringBuffer JavaDoc result) {
631     result.append(numberConstants.exponentialSymbol());
632
633     if (exponent < 0) {
634       exponent = -exponent;
635       result.append(numberConstants.minusSign());
636     }
637
638     String JavaDoc exponentDigits = String.valueOf(exponent);
639     for (int i = exponentDigits.length(); i < minExponentDigits; ++i) {
640       result.append(numberConstants.zeroDigit());
641     }
642     result.append(exponentDigits);
643   }
644
645   /**
646    * This method return the digit that represented by current character, it
647    * could be either '0' to '9', or a locale specific digit.
648    *
649    * @param ch character that represents a digit
650    * @return the digit value
651    */

652   private int getDigit(char ch) {
653     if ('0' <= ch && ch <= '0' + 9) {
654       return (ch - '0');
655     } else {
656       char zeroChar = numberConstants.zeroDigit().charAt(0);
657       return ((zeroChar <= ch && ch <= zeroChar + 9) ? (ch - zeroChar) : -1);
658     }
659   }
660
661   /**
662    * This method parses affix part of pattern.
663    *
664    * @param pattern pattern string that need to be parsed
665    * @param start start position to parse
666    * @param affix store the parsed result
667    * @return how many characters parsed
668    */

669   private int parseAffix(String JavaDoc pattern, int start, StringBuffer JavaDoc affix) {
670     affix.delete(0, affix.length());
671     boolean inQuote = false;
672     int len = pattern.length();
673
674     for (int pos = start; pos < len; ++pos) {
675       char ch = pattern.charAt(pos);
676       if (ch == QUOTE) {
677         if ((pos + 1) < len && pattern.charAt(pos + 1) == QUOTE) {
678           ++pos;
679           affix.append("'"); // 'don''t'
680
} else {
681           inQuote = !inQuote;
682         }
683         continue;
684       }
685
686       if (inQuote) {
687         affix.append(ch);
688       } else {
689         switch (ch) {
690           case PATTERN_DIGIT:
691           case PATTERN_ZERO_DIGIT:
692           case PATTERN_GROUPING_SEPARATOR:
693           case PATTERN_DECIMAL_SEPARATOR:
694           case PATTERN_SEPARATOR:
695             return pos - start;
696           case CURRENCY_SIGN:
697             isCurrencyFormat = true;
698             if ((pos + 1) < len && pattern.charAt(pos + 1) == CURRENCY_SIGN) {
699               ++pos;
700               affix.append(currencyCode);
701             } else {
702               affix.append(currencySymbol);
703             }
704             break;
705           case PATTERN_PERCENT:
706             if (multiplier != 1) {
707               throw new IllegalArgumentException JavaDoc(
708                   "Too many percent/per mille characters in pattern \""
709                       + pattern + '"');
710             }
711             multiplier = 100;
712             affix.append(numberConstants.percent());
713             break;
714           case PATTERN_PER_MILLE:
715             if (multiplier != 1) {
716               throw new IllegalArgumentException JavaDoc(
717                   "Too many percent/per mille characters in pattern \""
718                       + pattern + '"');
719             }
720             multiplier = 1000;
721             affix.append(numberConstants.perMill());
722             break;
723           case PATTERN_MINUS:
724             affix.append("-");
725             break;
726           default:
727             affix.append(ch);
728         }
729       }
730     }
731     return len - start;
732   }
733
734   /**
735    * This function parses a "localized" text into a <code>double</code>. It
736    * needs to handle locale specific decimal, grouping, exponent and digit.
737    *
738    * @param text the text that need to be parsed
739    * @param pos in/out parsing position. in case of failure, this shouldn't be
740    * changed
741    * @return double value, could be 0.0 if nothing can be parsed
742    */

743   private double parseNumber(String JavaDoc text, int[] pos) {
744     double ret;
745     boolean sawDecimal = false;
746     boolean sawExponent = false;
747     boolean sawDigit = false;
748     int scale = 1;
749     String JavaDoc decimal = isCurrencyFormat ? numberConstants.monetarySeparator()
750         : numberConstants.decimalSeparator();
751     String JavaDoc grouping = isCurrencyFormat
752         ? numberConstants.monetaryGroupingSeparator()
753         : numberConstants.groupingSeparator();
754     String JavaDoc exponentChar = numberConstants.exponentialSymbol();
755
756     StringBuffer JavaDoc normalizedText = new StringBuffer JavaDoc();
757     for (; pos[0] < text.length(); ++pos[0]) {
758       char ch = text.charAt(pos[0]);
759       int digit = getDigit(ch);
760       if (digit >= 0 && digit <= 9) {
761         normalizedText.append((char) (digit + '0'));
762         sawDigit = true;
763       } else if (ch == decimal.charAt(0)) {
764         if (sawDecimal || sawExponent) {
765           break;
766         }
767         normalizedText.append('.');
768         sawDecimal = true;
769       } else if (ch == grouping.charAt(0)) {
770         if (sawDecimal || sawExponent) {
771           break;
772         }
773         continue;
774       } else if (ch == exponentChar.charAt(0)) {
775         if (sawExponent) {
776           break;
777         }
778         normalizedText.append('E');
779         sawExponent = true;
780       } else if (ch == '+' || ch == '-') {
781         normalizedText.append(ch);
782       } else if (ch == numberConstants.percent().charAt(0)) {
783         if (scale != 1) {
784           break;
785         }
786         scale = 100;
787         if (sawDigit) {
788           ++pos[0];
789           break;
790         }
791       } else if (ch == numberConstants.perMill().charAt(0)) {
792         if (scale != 1) {
793           break;
794         }
795         scale = 1000;
796         if (sawDigit) {
797           ++pos[0];
798           break;
799         }
800       } else {
801         break;
802       }
803     }
804     
805     try {
806       ret = Double.parseDouble(normalizedText.toString());
807       ret = ret / scale;
808       return ret;
809     } catch (NumberFormatException JavaDoc e) {
810       return 0.0;
811     }
812   }
813
814   /**
815    * Method parses provided pattern, result is stored in member variables.
816    *
817    * @param pattern
818    */

819   private void parsePattern(String JavaDoc pattern) {
820     int pos = 0;
821     StringBuffer JavaDoc affix = new StringBuffer JavaDoc();
822
823     pos += parseAffix(pattern, pos, affix);
824     positivePrefix = affix.toString();
825     int posPartLen = parseTrunk(pattern, pos);
826     pos += posPartLen;
827     pos += parseAffix(pattern, pos, affix);
828     positiveSuffix = affix.toString();
829
830     if (pos < pattern.length() && pattern.charAt(pos) == PATTERN_SEPARATOR) {
831       ++pos;
832       pos += parseAffix(pattern, pos, affix);
833       negativePrefix = affix.toString();
834       // The assumption made here is that negative part is identical to
835
// positive part. User must make sure pattern is correctly constructed.
836
pos += posPartLen;
837       pos += parseAffix(pattern, pos, affix);
838       negativeSuffix = affix.toString();
839     }
840   }
841
842   /**
843    * This method parses the trunk part of a pattern.
844    *
845    * @param pattern pattern string that need to be parsed
846    * @param start where parse started
847    * @return how many characters parsed
848    */

849   private int parseTrunk(String JavaDoc pattern, int start) {
850     int decimalPos = -1;
851     int digitLeftCount = 0, zeroDigitCount = 0, digitRightCount = 0;
852     byte groupingCount = -1;
853
854     int len = pattern.length();
855     int pos = start;
856     boolean loop = true;
857     for (; (pos < len) && loop; ++pos) {
858       char ch = pattern.charAt(pos);
859       switch (ch) {
860         case PATTERN_DIGIT:
861           if (zeroDigitCount > 0) {
862             ++digitRightCount;
863           } else {
864             ++digitLeftCount;
865           }
866           if (groupingCount >= 0 && decimalPos < 0) {
867             ++groupingCount;
868           }
869           break;
870         case PATTERN_ZERO_DIGIT:
871           if (digitRightCount > 0) {
872             throw new IllegalArgumentException JavaDoc("Unexpected '0' in pattern \""
873                 + pattern + '"');
874           }
875           ++zeroDigitCount;
876           if (groupingCount >= 0 && decimalPos < 0) {
877             ++groupingCount;
878           }
879           break;
880         case PATTERN_GROUPING_SEPARATOR:
881           groupingCount = 0;
882           break;
883         case PATTERN_DECIMAL_SEPARATOR:
884           if (decimalPos >= 0) {
885             throw new IllegalArgumentException JavaDoc(
886                 "Multiple decimal separators in pattern \"" + pattern + '"');
887           }
888           decimalPos = digitLeftCount + zeroDigitCount + digitRightCount;
889           break;
890         case PATTERN_EXPONENT:
891           if (useExponentialNotation) {
892             throw new IllegalArgumentException JavaDoc("Multiple exponential "
893                 + "symbols in pattern \"" + pattern + '"');
894           }
895           useExponentialNotation = true;
896           minExponentDigits = 0;
897
898           // Use lookahead to parse out the exponential part
899
// of the pattern, then jump into phase 2.
900
while ((pos + 1) < len
901               && pattern.charAt(pos + 1) == numberConstants.zeroDigit().charAt(
902                   0)) {
903             ++pos;
904             ++minExponentDigits;
905           }
906
907           if ((digitLeftCount + zeroDigitCount) < 1 || minExponentDigits < 1) {
908             throw new IllegalArgumentException JavaDoc("Malformed exponential "
909                 + "pattern \"" + pattern + '"');
910           }
911           loop = false;
912           break;
913         default:
914           --pos;
915           loop = false;
916           break;
917       }
918     }
919
920     if (zeroDigitCount == 0 && digitLeftCount > 0 && decimalPos >= 0) {
921       // Handle "###.###" and "###." and ".###".
922
int n = decimalPos;
923       if (n == 0) { // Handle ".###"
924
++n;
925       }
926       digitRightCount = digitLeftCount - n;
927       digitLeftCount = n - 1;
928       zeroDigitCount = 1;
929     }
930
931     // Do syntax checking on the digits.
932
if ((decimalPos < 0 && digitRightCount > 0)
933         || (decimalPos >= 0 && (decimalPos < digitLeftCount || decimalPos > (digitLeftCount + zeroDigitCount)))
934         || groupingCount == 0) {
935       throw new IllegalArgumentException JavaDoc("Malformed pattern \"" + pattern + '"');
936     }
937     int totalDigits = digitLeftCount + zeroDigitCount + digitRightCount;
938
939     maximumFractionDigits = (decimalPos >= 0 ? (totalDigits - decimalPos) : 0);
940     if (decimalPos >= 0) {
941       minimumFractionDigits = digitLeftCount + zeroDigitCount - decimalPos;
942       if (minimumFractionDigits < 0) {
943         minimumFractionDigits = 0;
944       }
945     }
946
947     /*
948      * The effectiveDecimalPos is the position the decimal is at or would be at
949      * if there is no decimal. Note that if decimalPos<0, then digitTotalCount ==
950      * digitLeftCount + zeroDigitCount.
951      */

952     int effectiveDecimalPos = decimalPos >= 0 ? decimalPos : totalDigits;
953     minimumIntegerDigits = effectiveDecimalPos - digitLeftCount;
954     if (useExponentialNotation) {
955       maximumIntegerDigits = digitLeftCount + minimumIntegerDigits;
956
957       // In exponential display, integer part can't be empty.
958
if (maximumFractionDigits == 0 && minimumIntegerDigits == 0) {
959         minimumIntegerDigits = 1;
960       }
961     }
962
963     this.groupingSize = (groupingCount > 0) ? groupingCount : 0;
964     decimalSeparatorAlwaysShown = (decimalPos == 0 || decimalPos == totalDigits);
965
966     return pos - start;
967   }
968
969   /**
970    * This method formats a <code>double</code> in exponential format.
971    *
972    * @param number value need to be formated
973    * @param result where the formatted string goes
974    */

975   private void subformatExponential(double number, StringBuffer JavaDoc result) {
976     if (number == 0.0) {
977       subformatFixed(number, result, minimumIntegerDigits);
978       addExponentPart(0, result);
979       return;
980     }
981
982     int exponent = (int) Math.floor(Math.log(number) / Math.log(10));
983     number /= Math.pow(10, exponent);
984
985     int minIntDigits = minimumIntegerDigits;
986     if (maximumIntegerDigits > 1 && maximumIntegerDigits > minimumIntegerDigits) {
987       // A repeating range is defined; adjust to it as follows.
988
// If repeat == 3, we have 6,5,4=>3; 3,2,1=>0; 0,-1,-2=>-3;
989
// -3,-4,-5=>-6, etc. This takes into account that the
990
// exponent we have here is off by one from what we expect;
991
// it is for the format 0.MMMMMx10^n.
992
while ((exponent % maximumIntegerDigits) != 0) {
993         number *= 10;
994         exponent--;
995       }
996       minIntDigits = 1;
997     } else {
998       // No repeating range is defined; use minimum integer digits.
999
if (minimumIntegerDigits < 1) {
1000        exponent++;
1001        number /= 10;
1002      } else {
1003        for (int i = 1; i < minimumIntegerDigits; i++) {
1004          exponent--;
1005          number *= 10;
1006        }
1007      }
1008    }
1009
1010    subformatFixed(number, result, minIntDigits);
1011    addExponentPart(exponent, result);
1012  }
1013
1014  /**
1015   * This method formats a <code>double</code> into a fractional
1016   * representation.
1017   *
1018   * @param number value need to be formated
1019   * @param result result will be written here
1020   * @param minIntDigits minimum integer digits
1021   */

1022  private void subformatFixed(double number, StringBuffer JavaDoc result,
1023      int minIntDigits) {
1024    // Round the number.
1025
double power = Math.pow(10, maximumFractionDigits);
1026    number = Math.round(number * power);
1027    long intValue = (long) Math.floor(number / power);
1028    long fracValue = (long) Math.floor(number - intValue * power);
1029
1030    boolean fractionPresent = (minimumFractionDigits > 0) || (fracValue > 0);
1031
1032    String JavaDoc intPart = String.valueOf(intValue);
1033    String JavaDoc grouping = isCurrencyFormat
1034        ? numberConstants.monetaryGroupingSeparator()
1035        : numberConstants.groupingSeparator();
1036    String JavaDoc decimal = isCurrencyFormat ? numberConstants.monetarySeparator()
1037        : numberConstants.decimalSeparator();
1038
1039    int zeroDelta = numberConstants.zeroDigit().charAt(0) - '0';
1040    int digitLen = intPart.length();
1041
1042    if (intValue > 0 || minIntDigits > 0) {
1043      for (int i = digitLen; i < minIntDigits; i++) {
1044        result.append(numberConstants.zeroDigit());
1045      }
1046
1047      for (int i = 0; i < digitLen; i++) {
1048        result.append((char) (intPart.charAt(i) + zeroDelta));
1049
1050        if (digitLen - i > 1 && groupingSize > 0
1051            && ((digitLen - i) % groupingSize == 1)) {
1052          result.append(grouping);
1053        }
1054      }
1055    } else if (!fractionPresent) {
1056      // If there is no fraction present, and we haven't printed any
1057
// integer digits, then print a zero.
1058
result.append(numberConstants.zeroDigit());
1059    }
1060
1061    // Output the decimal separator if we always do so.
1062
if (decimalSeparatorAlwaysShown || fractionPresent) {
1063      result.append(decimal);
1064    }
1065
1066    // To make sure it lead zero will be kept.
1067
String JavaDoc fracPart = String.valueOf(fracValue + (long) power);
1068    int fracLen = fracPart.length();
1069    while (fracPart.charAt(fracLen - 1) == '0'
1070        && fracLen > minimumFractionDigits + 1) {
1071      fracLen--;
1072    }
1073
1074    for (int i = 1; i < fracLen; i++) {
1075      result.append((char) (fracPart.charAt(i) + zeroDelta));
1076    }
1077  }
1078}
1079
Popular Tags