0001: /*
0002: * Copyright 2007 Google Inc.
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
0005: * use this file except in compliance with the License. You may obtain a copy of
0006: * the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
0012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
0013: * License for the specific language governing permissions and limitations under
0014: * the License.
0015: */
0016: package com.google.gwt.i18n.client;
0017:
0018: import com.google.gwt.core.client.GWT;
0019: import com.google.gwt.i18n.client.constants.CurrencyCodeMapConstants;
0020: import com.google.gwt.i18n.client.constants.NumberConstants;
0021:
0022: import java.util.Map;
0023:
0024: /**
0025: * Formats and parses numbers using locale-sensitive patterns.
0026: *
0027: * This class provides comprehensive and flexible support for a wide variety of
0028: * localized formats, including
0029: * <ul>
0030: * <li><b>Locale-specific symbols</b> such as decimal point, group separator,
0031: * digit representation, currency symbol, percent, and permill</li>
0032: * <li><b>Numeric variations</b> including integers ("123"), fixed-point
0033: * numbers ("123.4"), scientific notation ("1.23E4"), percentages ("12%"), and
0034: * currency amounts ("$123")</li>
0035: * <li><b>Predefined standard patterns</b> that can be used both for parsing
0036: * and formatting, including {@link #getDecimalFormat() decimal},
0037: * {@link #getCurrencyFormat() currency},
0038: * {@link #getPercentFormat() percentages}, and
0039: * {@link #getScientificFormat() scientific}</li>
0040: * <li><b>Custom patterns</b> and supporting features designed to make it
0041: * possible to parse and format numbers in any locale, including support for
0042: * Western, Arabic, and Indic digits</li>
0043: * </ul>
0044: *
0045: * <h3>Patterns</h3>
0046: * <p>
0047: * Formatting and parsing are based on customizable patterns that can include a
0048: * combination of literal characters and special characters that act as
0049: * placeholders and are replaced by their localized counterparts. Many
0050: * characters in a pattern are taken literally; they are matched during parsing
0051: * and output unchanged during formatting. Special characters, on the other
0052: * hand, stand for other characters, strings, or classes of characters. For
0053: * example, the '<code>#</code>' character is replaced by a localized digit.
0054: * </p>
0055: *
0056: * <p>
0057: * Often the replacement character is the same as the pattern character. In the
0058: * U.S. locale, for example, the '<code>,</code>' grouping character is
0059: * replaced by the same character '<code>,</code>'. However, the replacement
0060: * is still actually happening, and in a different locale, the grouping
0061: * character may change to a different character, such as '<code>.</code>'.
0062: * Some special characters affect the behavior of the formatter by their
0063: * presence. For example, if the percent character is seen, then the value is
0064: * multiplied by 100 before being displayed.
0065: * </p>
0066: *
0067: * <p>
0068: * The characters listed below are used in patterns. Localized symbols use the
0069: * corresponding characters taken from corresponding locale symbol collection,
0070: * which can be found in the properties files residing in the
0071: * <code><nobr>com.google.gwt.i18n.client.constants</nobr></code>. To insert
0072: * a special character in a pattern as a literal (that is, without any special
0073: * meaning) the character must be quoted. There are some exceptions to this
0074: * which are noted below.
0075: * </p>
0076: *
0077: * <table>
0078: * <tr>
0079: * <th>Symbol</th>
0080: * <th>Location</th>
0081: * <th>Localized?</th>
0082: * <th>Meaning</th>
0083: * </tr>
0084: *
0085: * <tr>
0086: * <td><code>0</code></td>
0087: * <td>Number</td>
0088: * <td>Yes</td>
0089: * <td>Digit</td>
0090: * </tr>
0091: *
0092: * <tr>
0093: * <td><code>#</code></td>
0094: * <td>Number</td>
0095: * <td>Yes</td>
0096: * <td>Digit, zero shows as absent</td>
0097: * </tr>
0098: *
0099: * <tr>
0100: * <td><code>.</code></td>
0101: * <td>Number</td>
0102: * <td>Yes</td>
0103: * <td>Decimal separator or monetary decimal separator</td>
0104: * </tr>
0105: *
0106: * <tr>
0107: * <td><code>-</code></td>
0108: * <td>Number</td>
0109: * <td>Yes</td>
0110: * <td>Minus sign</td>
0111: * </tr>
0112: *
0113: * <tr>
0114: * <td><code>,</code></td>
0115: * <td>Number</td>
0116: * <td>Yes</td>
0117: * <td>Grouping separator</td>
0118: * </tr>
0119: *
0120: * <tr>
0121: * <td><code>E</code></td>
0122: * <td>Number</td>
0123: * <td>Yes</td>
0124: * <td>Separates mantissa and exponent in scientific notation; need not be
0125: * quoted in prefix or suffix</td>
0126: * </tr>
0127: *
0128: * <tr>
0129: * <td><code>E</code></td>
0130: * <td>Subpattern boundary</td>
0131: * <td>Yes</td>
0132: * <td>Separates positive and negative subpatterns</td>
0133: * </tr>
0134: *
0135: * <tr>
0136: * <td><code>%</code></td>
0137: * <td>Prefix or suffix</td>
0138: * <td>Yes</td>
0139: * <td>Multiply by 100 and show as percentage</td>
0140: * </tr>
0141: *
0142: * <tr>
0143: * <td><nobr><code>\u2030</code> (\u005Cu2030)</nobr></td>
0144: * <td>Prefix or suffix</td>
0145: * <td>Yes</td>
0146: * <td>Multiply by 1000 and show as per mille</td>
0147: * </tr>
0148: *
0149: * <tr>
0150: * <td><nobr><code>\u00A4</code> (\u005Cu00A4)</nobr></td>
0151: * <td>Prefix or suffix</td>
0152: * <td>No</td>
0153: * <td>Currency sign, replaced by currency symbol; if doubled, replaced by
0154: * international currency symbol; if present in a pattern, the monetary decimal
0155: * separator is used instead of the decimal separator</td>
0156: * </tr>
0157: *
0158: * <tr>
0159: * <td><code>'</code></td>
0160: * <td>Prefix or suffix</td>
0161: * <td>No</td>
0162: * <td>Used to quote special characters in a prefix or suffix; for example,
0163: * <code>"'#'#"</code> formats <code>123</code> to <code>"#123"</code>;
0164: * to create a single quote itself, use two in succession, such as
0165: * <code>"# o''clock"</code></td>
0166: * </tr>
0167: *
0168: * </table>
0169: *
0170: * <p>
0171: * A <code>NumberFormat</code> pattern contains a postive and negative
0172: * subpattern separated by a semicolon, such as
0173: * <code>"#,##0.00;(#,##0.00)"</code>. Each subpattern has a prefix, a
0174: * numeric part, and a suffix. If there is no explicit negative subpattern, the
0175: * negative subpattern is the localized minus sign prefixed to the positive
0176: * subpattern. That is, <code>"0.00"</code> alone is equivalent to
0177: * <code>"0.00;-0.00"</code>. If there is an explicit negative subpattern, it
0178: * serves only to specify the negative prefix and suffix; the number of digits,
0179: * minimal digits, and other characteristics are ignored in the negative
0180: * subpattern. That means that <code>"#,##0.0#;(#)"</code> has precisely the
0181: * same result as <code>"#,##0.0#;(#,##0.0#)"</code>.
0182: * </p>
0183: *
0184: * <p>
0185: * The prefixes, suffixes, and various symbols used for infinity, digits,
0186: * thousands separators, decimal separators, etc. may be set to arbitrary
0187: * values, and they will appear properly during formatting. However, care must
0188: * be taken that the symbols and strings do not conflict, or parsing will be
0189: * unreliable. For example, the decimal separator and thousands separator should
0190: * be distinct characters, or parsing will be impossible.
0191: * </p>
0192: *
0193: * <p>
0194: * The grouping separator is a character that separates clusters of integer
0195: * digits to make large numbers more legible. It commonly used for thousands,
0196: * but in some locales it separates ten-thousands. The grouping size is the
0197: * number of digits between the grouping separators, such as 3 for "100,000,000"
0198: * or 4 for "1 0000 0000".
0199: * </p>
0200: *
0201: * <h3>Pattern Grammar (BNF)</h3>
0202: * <p>
0203: * The pattern itself uses the following grammar:
0204: * </p>
0205: *
0206: * <table>
0207: * <tr>
0208: * <td>pattern</td>
0209: * <td>:=</td>
0210: * <td style="white-space: nowrap">subpattern ('<code>;</code>'
0211: * subpattern)?</td>
0212: * </tr>
0213: * <tr>
0214: * <td>subpattern</td>
0215: * <td>:=</td>
0216: * <td>prefix? number exponent? suffix?</td>
0217: * </tr>
0218: * <tr>
0219: * <td>number</td>
0220: * <td>:=</td>
0221: * <td style="white-space: nowrap">(integer ('<code>.</code>' fraction)?) |
0222: * sigDigits</td>
0223: * </tr>
0224: * <tr>
0225: * <td>prefix</td>
0226: * <td>:=</td>
0227: * <td style="white-space: nowrap">'<code>\u005Cu0000</code>'..'<code>\u005CuFFFD</code>' -
0228: * specialCharacters</td>
0229: * </tr>
0230: * <tr>
0231: * <td>suffix</td>
0232: * <td>:=</td>
0233: * <td style="white-space: nowrap">'<code>\u005Cu0000</code>'..'<code>\u005CuFFFD</code>' -
0234: * specialCharacters</td>
0235: * </tr>
0236: * <tr>
0237: * <td>integer</td>
0238: * <td>:=</td>
0239: * <td style="white-space: nowrap">'<code>#</code>'* '<code>0</code>'*'<code>0</code>'</td>
0240: * </tr>
0241: * <tr>
0242: * <td>fraction</td>
0243: * <td>:=</td>
0244: * <td style="white-space: nowrap">'<code>0</code>'* '<code>#</code>'*</td>
0245: * </tr>
0246: * <tr>
0247: * <td>sigDigits</td>
0248: * <td>:=</td>
0249: * <td style="white-space: nowrap">'<code>#</code>'* '<code>@</code>''<code>@</code>'* '<code>#</code>'*</td>
0250: * </tr>
0251: * <tr>
0252: * <td>exponent</td>
0253: * <td>:=</td>
0254: * <td style="white-space: nowrap">'<code>E</code>' '<code>+</code>'? '<code>0</code>'* '<code>0</code>'</td>
0255: * </tr>
0256: * <tr>
0257: * <td>padSpec</td>
0258: * <td>:=</td>
0259: * <td style="white-space: nowrap">'<code>*</code>' padChar</td>
0260: * </tr>
0261: * <tr>
0262: * <td>padChar</td>
0263: * <td>:=</td>
0264: * <td>'<code>\u005Cu0000</code>'..'<code>\u005CuFFFD</code>' - quote</td>
0265: * </tr>
0266: * </table>
0267: *
0268: * <p>
0269: * Notation:
0270: * </p>
0271: *
0272: * <table>
0273: * <tr>
0274: * <td>X*</td>
0275: * <td style="white-space: nowrap">0 or more instances of X</td>
0276: * </tr>
0277: *
0278: * <tr>
0279: * <td>X?</td>
0280: * <td style="white-space: nowrap">0 or 1 instances of X</td>
0281: * </tr>
0282: *
0283: * <tr>
0284: * <td>X|Y</td>
0285: * <td style="white-space: nowrap">either X or Y</td>
0286: * </tr>
0287: *
0288: * <tr>
0289: * <td>C..D</td>
0290: * <td style="white-space: nowrap">any character from C up to D, inclusive</td>
0291: * </tr>
0292: *
0293: * <tr>
0294: * <td>S-T</td>
0295: * <td style="white-space: nowrap">characters in S, except those in T</td>
0296: * </tr>
0297: * </table>
0298: *
0299: * <p>
0300: * The first subpattern is for positive numbers. The second (optional)
0301: * subpattern is for negative numbers.
0302: * </p>
0303: */
0304: public class NumberFormat {
0305:
0306: // Sets of constants as defined for the default locale.
0307: private static final NumberConstants defaultNumberConstants = (NumberConstants) GWT
0308: .create(NumberConstants.class);
0309: private static final CurrencyCodeMapConstants defaultCurrencyCodeMapConstants = (CurrencyCodeMapConstants) GWT
0310: .create(CurrencyCodeMapConstants.class);
0311:
0312: // Constants for characters used in programmatic (unlocalized) patterns.
0313: private static final char PATTERN_ZERO_DIGIT = '0';
0314: private static final char PATTERN_GROUPING_SEPARATOR = ',';
0315: private static final char PATTERN_DECIMAL_SEPARATOR = '.';
0316: private static final char PATTERN_PER_MILLE = '\u2030';
0317: private static final char PATTERN_PERCENT = '%';
0318: private static final char PATTERN_DIGIT = '#';
0319: private static final char PATTERN_SEPARATOR = ';';
0320: private static final char PATTERN_EXPONENT = 'E';
0321: private static final char PATTERN_MINUS = '-';
0322: private static final char CURRENCY_SIGN = '\u00A4';
0323: private static final char QUOTE = '\'';
0324:
0325: // Cached instances of standard formatters.
0326: private static NumberFormat cachedDecimalFormat;
0327: private static NumberFormat cachedScientificFormat;
0328: private static NumberFormat cachedPercentFormat;
0329: private static NumberFormat cachedCurrencyFormat;
0330:
0331: /**
0332: * Provides the standard currency format for the default locale.
0333: *
0334: * @return a <code>NumberFormat</code> capable of producing and consuming
0335: * currency format for the default locale
0336: */
0337: public static NumberFormat getCurrencyFormat() {
0338: if (cachedCurrencyFormat == null) {
0339: cachedCurrencyFormat = new NumberFormat(
0340: defaultNumberConstants.currencyPattern(),
0341: defaultNumberConstants.defCurrencyCode());
0342: }
0343: return cachedCurrencyFormat;
0344: }
0345:
0346: /**
0347: * Provides the standard decimal format for the default locale.
0348: *
0349: * @return a <code>NumberFormat</code> capable of producing and consuming
0350: * decimal format for the default locale
0351: */
0352: public static NumberFormat getDecimalFormat() {
0353: if (cachedDecimalFormat == null) {
0354: cachedDecimalFormat = new NumberFormat(
0355: defaultNumberConstants.decimalPattern(),
0356: defaultNumberConstants.defCurrencyCode());
0357: }
0358: return cachedDecimalFormat;
0359: }
0360:
0361: /**
0362: * Gets a <code>NumberFormat</code> instance for the default locale using
0363: * the specified pattern and the default currencyCode.
0364: *
0365: * @param pattern pattern for this formatter
0366: * @return a NumberFormat instance
0367: * @throws IllegalArgumentException if the specified pattern is invalid
0368: */
0369: public static NumberFormat getFormat(String pattern) {
0370: return new NumberFormat(pattern, defaultNumberConstants
0371: .defCurrencyCode());
0372: }
0373:
0374: /**
0375: * Gets a custom <code>NumberFormat</code> instance for the default locale
0376: * using the specified pattern and currency code.
0377: *
0378: * @param pattern pattern for this formatter
0379: * @param currencyCode international currency code
0380: * @return a NumberFormat instance
0381: * @throws IllegalArgumentException if the specified pattern is invalid
0382: */
0383: public static NumberFormat getFormat(String pattern,
0384: String currencyCode) {
0385: return new NumberFormat(pattern, currencyCode);
0386: }
0387:
0388: /**
0389: * Provides the standard percent format for the default locale.
0390: *
0391: * @return a <code>NumberFormat</code> capable of producing and consuming
0392: * percent format for the default locale
0393: */
0394: public static NumberFormat getPercentFormat() {
0395: if (cachedPercentFormat == null) {
0396: cachedPercentFormat = new NumberFormat(
0397: defaultNumberConstants.percentPattern(),
0398: defaultNumberConstants.defCurrencyCode());
0399: }
0400: return cachedPercentFormat;
0401: }
0402:
0403: /**
0404: * Provides the standard scientific format for the default locale.
0405: *
0406: * @return a <code>NumberFormat</code> capable of producing and consuming
0407: * scientific format for the default locale
0408: */
0409: public static NumberFormat getScientificFormat() {
0410: if (cachedScientificFormat == null) {
0411: cachedScientificFormat = new NumberFormat(
0412: defaultNumberConstants.scientificPattern(),
0413: defaultNumberConstants.defCurrencyCode());
0414: }
0415: return cachedScientificFormat;
0416: }
0417:
0418: // Locale specific symbol collection.
0419: private final NumberConstants numberConstants;
0420:
0421: private int maximumIntegerDigits = 40;
0422: private int minimumIntegerDigits = 1;
0423: private int maximumFractionDigits = 3; // invariant, >= minFractionDigits.
0424: private int minimumFractionDigits = 0;
0425: private int minExponentDigits;
0426:
0427: private String positivePrefix = "";
0428: private String positiveSuffix = "";
0429: private String negativePrefix = "-";
0430: private String negativeSuffix = "";
0431:
0432: // The multiplier for use in percent, per mille, etc.
0433: private int multiplier = 1;
0434:
0435: // The number of digits between grouping separators in the integer
0436: // portion of a number.
0437: private int groupingSize = 3;
0438:
0439: // Forces the decimal separator to always appear in a formatted number.
0440: private boolean decimalSeparatorAlwaysShown = false;
0441:
0442: private boolean isCurrencyFormat = false;
0443:
0444: // True to force the use of exponential (i.e. scientific) notation.
0445: private boolean useExponentialNotation = false;
0446:
0447: // Currency setting.
0448: private final String currencySymbol;
0449:
0450: // The currency code.
0451: private final String currencyCode;
0452:
0453: // The pattern to use for formatting and parsing.
0454: private final String pattern;
0455:
0456: /**
0457: * Constructs a format object based on the specified settings.
0458: *
0459: * @param numberConstants the locale-specific number constants to use for this
0460: * format
0461: * @param currencyCodeMapConstants the locale-specific currency code map to
0462: * use for this format
0463: * @param pattern pattern that specify how number should be formatted
0464: * @param currencyCode currency that should be used
0465: * @skip
0466: */
0467: protected NumberFormat(NumberConstants numberConstants,
0468: CurrencyCodeMapConstants currencyCodeMapConstants,
0469: String pattern, String currencyCode) {
0470: this .numberConstants = numberConstants;
0471: this .pattern = pattern;
0472: this .currencyCode = currencyCode;
0473:
0474: Map<String, String> currencyMap = currencyCodeMapConstants
0475: .currencyMap();
0476: currencySymbol = currencyMap.get(currencyCode);
0477:
0478: parsePattern(this .pattern);
0479: }
0480:
0481: /**
0482: * Constructs a format object for the default locale based on the specified
0483: * settings.
0484: *
0485: * @param pattern pattern that specify how number should be formatted
0486: * @param currencyCode currency that should be used
0487: * @skip
0488: */
0489: protected NumberFormat(String pattern, String currencyCode) {
0490: this (defaultNumberConstants, defaultCurrencyCodeMapConstants,
0491: pattern, currencyCode);
0492: }
0493:
0494: /**
0495: * This method formats a double to produce a string.
0496: *
0497: * @param number The double to format
0498: * @return the formatted number string
0499: */
0500: public String format(double number) {
0501: StringBuffer result = new StringBuffer();
0502:
0503: if (Double.isNaN(number)) {
0504: result.append(numberConstants.notANumber());
0505: return result.toString();
0506: }
0507:
0508: boolean isNegative = ((number < 0.0) || (number == 0.0 && 1 / number < 0.0));
0509:
0510: result.append(isNegative ? negativePrefix : positivePrefix);
0511: if (Double.isInfinite(number)) {
0512: result.append(numberConstants.infinity());
0513: } else {
0514: if (isNegative) {
0515: number = -number;
0516: }
0517: number *= multiplier;
0518: if (useExponentialNotation) {
0519: subformatExponential(number, result);
0520: } else {
0521: subformatFixed(number, result, minimumIntegerDigits);
0522: }
0523: }
0524:
0525: result.append(isNegative ? negativeSuffix : positiveSuffix);
0526:
0527: return result.toString();
0528: }
0529:
0530: /**
0531: * Returns the pattern used by this number format.
0532: */
0533: public String getPattern() {
0534: return pattern;
0535: }
0536:
0537: /**
0538: * Parses text to produce a numeric value. A {@link NumberFormatException} is
0539: * thrown if either the text is empty or if the parse does not consume all
0540: * characters of the text.
0541: *
0542: * @param text the string being parsed
0543: * @return a parsed number value
0544: * @throws NumberFormatException if the entire text could not be converted
0545: * into a number
0546: */
0547: public double parse(String text) {
0548: int[] pos = { 0 };
0549: double result = parse(text, pos);
0550: if (pos[0] == 0 || pos[0] != text.length()) {
0551: throw new NumberFormatException(text);
0552: }
0553: return result;
0554: }
0555:
0556: /**
0557: * Parses text to produce a numeric value.
0558: *
0559: * <p>
0560: * The method attempts to parse text starting at the index given by pos. If
0561: * parsing succeeds, then the index of <code>pos</code> is updated to the
0562: * index after the last character used (parsing does not necessarily use all
0563: * characters up to the end of the string), and the parsed number is returned.
0564: * The updated <code>pos</code> can be used to indicate the starting point
0565: * for the next call to this method. If an error occurs, then the index of
0566: * <code>pos</code> is not changed.
0567: * </p>
0568: *
0569: * @param text the string to be parsed
0570: * @param inOutPos position to pass in and get back
0571: * @return a double value representing the parsed number, or <code>0.0</code>
0572: * if the parse fails
0573: */
0574: public double parse(String text, int[] inOutPos) {
0575: int start = inOutPos[0];
0576: boolean gotPositive, gotNegative;
0577: double ret = 0.0;
0578:
0579: gotPositive = (text.indexOf(positivePrefix, inOutPos[0]) == inOutPos[0]);
0580: gotNegative = (text.indexOf(negativePrefix, inOutPos[0]) == inOutPos[0]);
0581:
0582: if (gotPositive && gotNegative) {
0583: if (positivePrefix.length() > negativePrefix.length()) {
0584: gotNegative = false;
0585: } else if (positivePrefix.length() < negativePrefix
0586: .length()) {
0587: gotPositive = false;
0588: }
0589: }
0590:
0591: if (gotPositive) {
0592: inOutPos[0] += positivePrefix.length();
0593: } else if (gotNegative) {
0594: inOutPos[0] += negativePrefix.length();
0595: }
0596:
0597: // Process digits or Inf, and find decimal position.
0598: if (text.indexOf(numberConstants.infinity(), inOutPos[0]) == inOutPos[0]) {
0599: inOutPos[0] += numberConstants.infinity().length();
0600: ret = Double.POSITIVE_INFINITY;
0601: } else if (text.indexOf(numberConstants.notANumber(),
0602: inOutPos[0]) == inOutPos[0]) {
0603: inOutPos[0] += numberConstants.notANumber().length();
0604: ret = Double.NaN;
0605: } else {
0606: ret = parseNumber(text, inOutPos);
0607: }
0608:
0609: // Check for suffix.
0610: if (gotPositive) {
0611: if (!(text.indexOf(positiveSuffix, inOutPos[0]) == inOutPos[0])) {
0612: inOutPos[0] = start;
0613: return 0.0;
0614: }
0615: inOutPos[0] += positiveSuffix.length();
0616: } else if (gotNegative) {
0617: if (!(text.indexOf(negativeSuffix, inOutPos[0]) == inOutPos[0])) {
0618: inOutPos[0] = start;
0619: return 0.0;
0620: }
0621: inOutPos[0] += negativeSuffix.length();
0622: }
0623:
0624: if (gotNegative) {
0625: ret = -ret;
0626: }
0627:
0628: return ret;
0629: }
0630:
0631: /**
0632: * This method formats the exponent part of a double.
0633: *
0634: * @param exponent exponential value
0635: * @param result formatted exponential part will be append to it
0636: */
0637: private void addExponentPart(int exponent, StringBuffer result) {
0638: result.append(numberConstants.exponentialSymbol());
0639:
0640: if (exponent < 0) {
0641: exponent = -exponent;
0642: result.append(numberConstants.minusSign());
0643: }
0644:
0645: String exponentDigits = String.valueOf(exponent);
0646: for (int i = exponentDigits.length(); i < minExponentDigits; ++i) {
0647: result.append(numberConstants.zeroDigit());
0648: }
0649: result.append(exponentDigits);
0650: }
0651:
0652: /**
0653: * This method return the digit that represented by current character, it
0654: * could be either '0' to '9', or a locale specific digit.
0655: *
0656: * @param ch character that represents a digit
0657: * @return the digit value
0658: */
0659: private int getDigit(char ch) {
0660: if ('0' <= ch && ch <= '0' + 9) {
0661: return (ch - '0');
0662: } else {
0663: char zeroChar = numberConstants.zeroDigit().charAt(0);
0664: return ((zeroChar <= ch && ch <= zeroChar + 9) ? (ch - zeroChar)
0665: : -1);
0666: }
0667: }
0668:
0669: /**
0670: * This method parses affix part of pattern.
0671: *
0672: * @param pattern pattern string that need to be parsed
0673: * @param start start position to parse
0674: * @param affix store the parsed result
0675: * @return how many characters parsed
0676: */
0677: private int parseAffix(String pattern, int start, StringBuffer affix) {
0678: affix.delete(0, affix.length());
0679: boolean inQuote = false;
0680: int len = pattern.length();
0681:
0682: for (int pos = start; pos < len; ++pos) {
0683: char ch = pattern.charAt(pos);
0684: if (ch == QUOTE) {
0685: if ((pos + 1) < len && pattern.charAt(pos + 1) == QUOTE) {
0686: ++pos;
0687: affix.append("'"); // 'don''t'
0688: } else {
0689: inQuote = !inQuote;
0690: }
0691: continue;
0692: }
0693:
0694: if (inQuote) {
0695: affix.append(ch);
0696: } else {
0697: switch (ch) {
0698: case PATTERN_DIGIT:
0699: case PATTERN_ZERO_DIGIT:
0700: case PATTERN_GROUPING_SEPARATOR:
0701: case PATTERN_DECIMAL_SEPARATOR:
0702: case PATTERN_SEPARATOR:
0703: return pos - start;
0704: case CURRENCY_SIGN:
0705: isCurrencyFormat = true;
0706: if ((pos + 1) < len
0707: && pattern.charAt(pos + 1) == CURRENCY_SIGN) {
0708: ++pos;
0709: affix.append(currencyCode);
0710: } else {
0711: affix.append(currencySymbol);
0712: }
0713: break;
0714: case PATTERN_PERCENT:
0715: if (multiplier != 1) {
0716: throw new IllegalArgumentException(
0717: "Too many percent/per mille characters in pattern \""
0718: + pattern + '"');
0719: }
0720: multiplier = 100;
0721: affix.append(numberConstants.percent());
0722: break;
0723: case PATTERN_PER_MILLE:
0724: if (multiplier != 1) {
0725: throw new IllegalArgumentException(
0726: "Too many percent/per mille characters in pattern \""
0727: + pattern + '"');
0728: }
0729: multiplier = 1000;
0730: affix.append(numberConstants.perMill());
0731: break;
0732: case PATTERN_MINUS:
0733: affix.append("-");
0734: break;
0735: default:
0736: affix.append(ch);
0737: }
0738: }
0739: }
0740: return len - start;
0741: }
0742:
0743: /**
0744: * This function parses a "localized" text into a <code>double</code>. It
0745: * needs to handle locale specific decimal, grouping, exponent and digit.
0746: *
0747: * @param text the text that need to be parsed
0748: * @param pos in/out parsing position. in case of failure, this shouldn't be
0749: * changed
0750: * @return double value, could be 0.0 if nothing can be parsed
0751: */
0752: private double parseNumber(String text, int[] pos) {
0753: double ret;
0754: boolean sawDecimal = false;
0755: boolean sawExponent = false;
0756: boolean sawDigit = false;
0757: int scale = 1;
0758: String decimal = isCurrencyFormat ? numberConstants
0759: .monetarySeparator() : numberConstants
0760: .decimalSeparator();
0761: String grouping = isCurrencyFormat ? numberConstants
0762: .monetaryGroupingSeparator() : numberConstants
0763: .groupingSeparator();
0764: String exponentChar = numberConstants.exponentialSymbol();
0765:
0766: StringBuffer normalizedText = new StringBuffer();
0767: for (; pos[0] < text.length(); ++pos[0]) {
0768: char ch = text.charAt(pos[0]);
0769: int digit = getDigit(ch);
0770: if (digit >= 0 && digit <= 9) {
0771: normalizedText.append((char) (digit + '0'));
0772: sawDigit = true;
0773: } else if (ch == decimal.charAt(0)) {
0774: if (sawDecimal || sawExponent) {
0775: break;
0776: }
0777: normalizedText.append('.');
0778: sawDecimal = true;
0779: } else if (ch == grouping.charAt(0)) {
0780: if (sawDecimal || sawExponent) {
0781: break;
0782: }
0783: continue;
0784: } else if (ch == exponentChar.charAt(0)) {
0785: if (sawExponent) {
0786: break;
0787: }
0788: normalizedText.append('E');
0789: sawExponent = true;
0790: } else if (ch == '+' || ch == '-') {
0791: normalizedText.append(ch);
0792: } else if (ch == numberConstants.percent().charAt(0)) {
0793: if (scale != 1) {
0794: break;
0795: }
0796: scale = 100;
0797: if (sawDigit) {
0798: ++pos[0];
0799: break;
0800: }
0801: } else if (ch == numberConstants.perMill().charAt(0)) {
0802: if (scale != 1) {
0803: break;
0804: }
0805: scale = 1000;
0806: if (sawDigit) {
0807: ++pos[0];
0808: break;
0809: }
0810: } else {
0811: break;
0812: }
0813: }
0814:
0815: try {
0816: ret = Double.parseDouble(normalizedText.toString());
0817: ret = ret / scale;
0818: return ret;
0819: } catch (NumberFormatException e) {
0820: return 0.0;
0821: }
0822: }
0823:
0824: /**
0825: * Method parses provided pattern, result is stored in member variables.
0826: *
0827: * @param pattern
0828: */
0829: private void parsePattern(String pattern) {
0830: int pos = 0;
0831: StringBuffer affix = new StringBuffer();
0832:
0833: pos += parseAffix(pattern, pos, affix);
0834: positivePrefix = affix.toString();
0835: int posPartLen = parseTrunk(pattern, pos);
0836: pos += posPartLen;
0837: pos += parseAffix(pattern, pos, affix);
0838: positiveSuffix = affix.toString();
0839:
0840: if (pos < pattern.length()
0841: && pattern.charAt(pos) == PATTERN_SEPARATOR) {
0842: ++pos;
0843: pos += parseAffix(pattern, pos, affix);
0844: negativePrefix = affix.toString();
0845: // The assumption made here is that negative part is identical to
0846: // positive part. User must make sure pattern is correctly constructed.
0847: pos += posPartLen;
0848: pos += parseAffix(pattern, pos, affix);
0849: negativeSuffix = affix.toString();
0850: }
0851: }
0852:
0853: /**
0854: * This method parses the trunk part of a pattern.
0855: *
0856: * @param pattern pattern string that need to be parsed
0857: * @param start where parse started
0858: * @return how many characters parsed
0859: */
0860: private int parseTrunk(String pattern, int start) {
0861: int decimalPos = -1;
0862: int digitLeftCount = 0, zeroDigitCount = 0, digitRightCount = 0;
0863: byte groupingCount = -1;
0864:
0865: int len = pattern.length();
0866: int pos = start;
0867: boolean loop = true;
0868: for (; (pos < len) && loop; ++pos) {
0869: char ch = pattern.charAt(pos);
0870: switch (ch) {
0871: case PATTERN_DIGIT:
0872: if (zeroDigitCount > 0) {
0873: ++digitRightCount;
0874: } else {
0875: ++digitLeftCount;
0876: }
0877: if (groupingCount >= 0 && decimalPos < 0) {
0878: ++groupingCount;
0879: }
0880: break;
0881: case PATTERN_ZERO_DIGIT:
0882: if (digitRightCount > 0) {
0883: throw new IllegalArgumentException(
0884: "Unexpected '0' in pattern \"" + pattern
0885: + '"');
0886: }
0887: ++zeroDigitCount;
0888: if (groupingCount >= 0 && decimalPos < 0) {
0889: ++groupingCount;
0890: }
0891: break;
0892: case PATTERN_GROUPING_SEPARATOR:
0893: groupingCount = 0;
0894: break;
0895: case PATTERN_DECIMAL_SEPARATOR:
0896: if (decimalPos >= 0) {
0897: throw new IllegalArgumentException(
0898: "Multiple decimal separators in pattern \""
0899: + pattern + '"');
0900: }
0901: decimalPos = digitLeftCount + zeroDigitCount
0902: + digitRightCount;
0903: break;
0904: case PATTERN_EXPONENT:
0905: if (useExponentialNotation) {
0906: throw new IllegalArgumentException(
0907: "Multiple exponential "
0908: + "symbols in pattern \"" + pattern
0909: + '"');
0910: }
0911: useExponentialNotation = true;
0912: minExponentDigits = 0;
0913:
0914: // Use lookahead to parse out the exponential part
0915: // of the pattern, then jump into phase 2.
0916: while ((pos + 1) < len
0917: && pattern.charAt(pos + 1) == numberConstants
0918: .zeroDigit().charAt(0)) {
0919: ++pos;
0920: ++minExponentDigits;
0921: }
0922:
0923: if ((digitLeftCount + zeroDigitCount) < 1
0924: || minExponentDigits < 1) {
0925: throw new IllegalArgumentException(
0926: "Malformed exponential " + "pattern \""
0927: + pattern + '"');
0928: }
0929: loop = false;
0930: break;
0931: default:
0932: --pos;
0933: loop = false;
0934: break;
0935: }
0936: }
0937:
0938: if (zeroDigitCount == 0 && digitLeftCount > 0
0939: && decimalPos >= 0) {
0940: // Handle "###.###" and "###." and ".###".
0941: int n = decimalPos;
0942: if (n == 0) { // Handle ".###"
0943: ++n;
0944: }
0945: digitRightCount = digitLeftCount - n;
0946: digitLeftCount = n - 1;
0947: zeroDigitCount = 1;
0948: }
0949:
0950: // Do syntax checking on the digits.
0951: if ((decimalPos < 0 && digitRightCount > 0)
0952: || (decimalPos >= 0 && (decimalPos < digitLeftCount || decimalPos > (digitLeftCount + zeroDigitCount)))
0953: || groupingCount == 0) {
0954: throw new IllegalArgumentException("Malformed pattern \""
0955: + pattern + '"');
0956: }
0957: int totalDigits = digitLeftCount + zeroDigitCount
0958: + digitRightCount;
0959:
0960: maximumFractionDigits = (decimalPos >= 0 ? (totalDigits - decimalPos)
0961: : 0);
0962: if (decimalPos >= 0) {
0963: minimumFractionDigits = digitLeftCount + zeroDigitCount
0964: - decimalPos;
0965: if (minimumFractionDigits < 0) {
0966: minimumFractionDigits = 0;
0967: }
0968: }
0969:
0970: /*
0971: * The effectiveDecimalPos is the position the decimal is at or would be at
0972: * if there is no decimal. Note that if decimalPos<0, then digitTotalCount ==
0973: * digitLeftCount + zeroDigitCount.
0974: */
0975: int effectiveDecimalPos = decimalPos >= 0 ? decimalPos
0976: : totalDigits;
0977: minimumIntegerDigits = effectiveDecimalPos - digitLeftCount;
0978: if (useExponentialNotation) {
0979: maximumIntegerDigits = digitLeftCount
0980: + minimumIntegerDigits;
0981:
0982: // In exponential display, integer part can't be empty.
0983: if (maximumFractionDigits == 0 && minimumIntegerDigits == 0) {
0984: minimumIntegerDigits = 1;
0985: }
0986: }
0987:
0988: this .groupingSize = (groupingCount > 0) ? groupingCount : 0;
0989: decimalSeparatorAlwaysShown = (decimalPos == 0 || decimalPos == totalDigits);
0990:
0991: return pos - start;
0992: }
0993:
0994: /**
0995: * This method formats a <code>double</code> in exponential format.
0996: *
0997: * @param number value need to be formated
0998: * @param result where the formatted string goes
0999: */
1000: private void subformatExponential(double number, StringBuffer result) {
1001: if (number == 0.0) {
1002: subformatFixed(number, result, minimumIntegerDigits);
1003: addExponentPart(0, result);
1004: return;
1005: }
1006:
1007: int exponent = (int) Math
1008: .floor(Math.log(number) / Math.log(10));
1009: number /= Math.pow(10, exponent);
1010:
1011: int minIntDigits = minimumIntegerDigits;
1012: if (maximumIntegerDigits > 1
1013: && maximumIntegerDigits > minimumIntegerDigits) {
1014: // A repeating range is defined; adjust to it as follows.
1015: // If repeat == 3, we have 6,5,4=>3; 3,2,1=>0; 0,-1,-2=>-3;
1016: // -3,-4,-5=>-6, etc. This takes into account that the
1017: // exponent we have here is off by one from what we expect;
1018: // it is for the format 0.MMMMMx10^n.
1019: while ((exponent % maximumIntegerDigits) != 0) {
1020: number *= 10;
1021: exponent--;
1022: }
1023: minIntDigits = 1;
1024: } else {
1025: // No repeating range is defined; use minimum integer digits.
1026: if (minimumIntegerDigits < 1) {
1027: exponent++;
1028: number /= 10;
1029: } else {
1030: for (int i = 1; i < minimumIntegerDigits; i++) {
1031: exponent--;
1032: number *= 10;
1033: }
1034: }
1035: }
1036:
1037: subformatFixed(number, result, minIntDigits);
1038: addExponentPart(exponent, result);
1039: }
1040:
1041: /**
1042: * This method formats a <code>double</code> into a fractional
1043: * representation.
1044: *
1045: * @param number value need to be formated
1046: * @param result result will be written here
1047: * @param minIntDigits minimum integer digits
1048: */
1049: private void subformatFixed(double number, StringBuffer result,
1050: int minIntDigits) {
1051: // Round the number.
1052: double power = Math.pow(10, maximumFractionDigits);
1053: number = Math.round(number * power);
1054: long intValue = (long) Math.floor(number / power);
1055: long fracValue = (long) Math.floor(number - intValue * power);
1056:
1057: boolean fractionPresent = (minimumFractionDigits > 0)
1058: || (fracValue > 0);
1059:
1060: String intPart = String.valueOf(intValue);
1061: String grouping = isCurrencyFormat ? numberConstants
1062: .monetaryGroupingSeparator() : numberConstants
1063: .groupingSeparator();
1064: String decimal = isCurrencyFormat ? numberConstants
1065: .monetarySeparator() : numberConstants
1066: .decimalSeparator();
1067:
1068: int zeroDelta = numberConstants.zeroDigit().charAt(0) - '0';
1069: int digitLen = intPart.length();
1070:
1071: if (intValue > 0 || minIntDigits > 0) {
1072: for (int i = digitLen; i < minIntDigits; i++) {
1073: result.append(numberConstants.zeroDigit());
1074: }
1075:
1076: for (int i = 0; i < digitLen; i++) {
1077: result.append((char) (intPart.charAt(i) + zeroDelta));
1078:
1079: if (digitLen - i > 1 && groupingSize > 0
1080: && ((digitLen - i) % groupingSize == 1)) {
1081: result.append(grouping);
1082: }
1083: }
1084: } else if (!fractionPresent) {
1085: // If there is no fraction present, and we haven't printed any
1086: // integer digits, then print a zero.
1087: result.append(numberConstants.zeroDigit());
1088: }
1089:
1090: // Output the decimal separator if we always do so.
1091: if (decimalSeparatorAlwaysShown || fractionPresent) {
1092: result.append(decimal);
1093: }
1094:
1095: // To make sure it lead zero will be kept.
1096: String fracPart = String.valueOf(fracValue + (long) power);
1097: int fracLen = fracPart.length();
1098: while (fracPart.charAt(fracLen - 1) == '0'
1099: && fracLen > minimumFractionDigits + 1) {
1100: fracLen--;
1101: }
1102:
1103: for (int i = 1; i < fracLen; i++) {
1104: result.append((char) (fracPart.charAt(i) + zeroDelta));
1105: }
1106: }
1107: }
|