diff options
Diffstat (limited to 'libjava/classpath/java/text/DecimalFormat.java')
-rw-r--r-- | libjava/classpath/java/text/DecimalFormat.java | 1435 |
1 files changed, 1435 insertions, 0 deletions
diff --git a/libjava/classpath/java/text/DecimalFormat.java b/libjava/classpath/java/text/DecimalFormat.java new file mode 100644 index 0000000..6dadb0ce --- /dev/null +++ b/libjava/classpath/java/text/DecimalFormat.java @@ -0,0 +1,1435 @@ +/* DecimalFormat.java -- Formats and parses numbers + Copyright (C) 1999, 2000, 2001, 2003, 2004, 2005 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package java.text; + +import gnu.java.text.AttributedFormatBuffer; +import gnu.java.text.FormatBuffer; +import gnu.java.text.FormatCharacterIterator; +import gnu.java.text.StringFormatBuffer; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.util.Currency; +import java.util.HashMap; +import java.util.Locale; + +/** + * @author Tom Tromey (tromey@cygnus.com) + * @author Andrew John Hughes (gnu_andrew@member.fsf.org) + * @date March 4, 1999 + */ +/* Written using "Java Class Libraries", 2nd edition, plus online + * API docs for JDK 1.2 from http://www.javasoft.com. + * Status: Believed complete and correct to 1.2. + * Note however that the docs are very unclear about how format parsing + * should work. No doubt there are problems here. + */ +public class DecimalFormat extends NumberFormat +{ + // This is a helper for applyPatternWithSymbols. It reads a prefix + // or a suffix. It can cause some side-effects. + private int scanFix (String pattern, int index, FormatBuffer buf, + String patChars, DecimalFormatSymbols syms, + boolean is_suffix) + { + int len = pattern.length(); + boolean quoteStarted = false; + buf.clear(); + + boolean multiplierSet = false; + while (index < len) + { + char c = pattern.charAt(index); + + if (quoteStarted) + { + if (c == '\'') + quoteStarted = false; + else + buf.append(c); + index++; + continue; + } + + if (c == '\'' && index + 1 < len + && pattern.charAt(index + 1) == '\'') + { + buf.append(c); + index++; + } + else if (c == '\'') + { + quoteStarted = true; + } + else if (c == '\u00a4') + { + /* Currency interpreted later */ + buf.append(c); + } + else if (c == syms.getPercent()) + { + if (multiplierSet) + throw new IllegalArgumentException ("multiplier already set " + + "- index: " + index); + multiplierSet = true; + multiplier = 100; + buf.append(c, NumberFormat.Field.PERCENT); + } + else if (c == syms.getPerMill()) + { + if (multiplierSet) + throw new IllegalArgumentException ("multiplier already set " + + "- index: " + index); + multiplierSet = true; + multiplier = 1000; + buf.append(c, NumberFormat.Field.PERMILLE); + } + else if (patChars.indexOf(c) != -1) + { + // This is a pattern character. + break; + } + else + { + buf.append(c); + } + index++; + } + + if (quoteStarted) + throw new IllegalArgumentException ("pattern is lacking a closing quote"); + + return index; + } + + // A helper which reads a number format. + private int scanFormat (String pattern, int index, String patChars, + DecimalFormatSymbols syms, boolean is_positive) + { + int max = pattern.length(); + + int countSinceGroup = 0; + int zeroCount = 0; + boolean saw_group = false; + + // + // Scan integer part. + // + while (index < max) + { + char c = pattern.charAt(index); + + if (c == syms.getDigit()) + { + if (zeroCount > 0) + throw new IllegalArgumentException ("digit mark following " + + "zero - index: " + index); + ++countSinceGroup; + } + else if (c == syms.getZeroDigit()) + { + ++zeroCount; + ++countSinceGroup; + } + else if (c == syms.getGroupingSeparator()) + { + countSinceGroup = 0; + saw_group = true; + } + else + break; + + ++index; + } + + // We can only side-effect when parsing the positive format. + if (is_positive) + { + groupingUsed = saw_group; + groupingSize = (byte) countSinceGroup; + minimumIntegerDigits = zeroCount; + } + + // Early termination. + if (index == max || pattern.charAt(index) == syms.getGroupingSeparator()) + { + if (is_positive) + decimalSeparatorAlwaysShown = false; + return index; + } + + if (pattern.charAt(index) == syms.getDecimalSeparator()) + { + ++index; + + // + // Scan fractional part. + // + int hashCount = 0; + zeroCount = 0; + while (index < max) + { + char c = pattern.charAt(index); + if (c == syms.getZeroDigit()) + { + if (hashCount > 0) + throw new IllegalArgumentException ("zero mark " + + "following digit - index: " + index); + ++zeroCount; + } + else if (c == syms.getDigit()) + { + ++hashCount; + } + else if (c != syms.getExponential() + && c != syms.getPatternSeparator() + && c != syms.getPercent() + && c != syms.getPerMill() + && patChars.indexOf(c) != -1) + throw new IllegalArgumentException ("unexpected special " + + "character - index: " + index); + else + break; + + ++index; + } + + if (is_positive) + { + maximumFractionDigits = hashCount + zeroCount; + minimumFractionDigits = zeroCount; + } + + if (index == max) + return index; + } + + if (pattern.charAt(index) == syms.getExponential()) + { + // + // Scan exponential format. + // + zeroCount = 0; + ++index; + while (index < max) + { + char c = pattern.charAt(index); + if (c == syms.getZeroDigit()) + ++zeroCount; + else if (c == syms.getDigit()) + { + if (zeroCount > 0) + throw new + IllegalArgumentException ("digit mark following zero " + + "in exponent - index: " + + index); + } + else if (patChars.indexOf(c) != -1) + throw new IllegalArgumentException ("unexpected special " + + "character - index: " + + index); + else + break; + + ++index; + } + + if (is_positive) + { + useExponentialNotation = true; + minExponentDigits = (byte) zeroCount; + } + + maximumIntegerDigits = groupingSize; + groupingSize = 0; + if (maximumIntegerDigits > minimumIntegerDigits && maximumIntegerDigits > 0) + { + minimumIntegerDigits = 1; + exponentRound = maximumIntegerDigits; + } + else + exponentRound = 1; + } + + return index; + } + + // This helper function creates a string consisting of all the + // characters which can appear in a pattern and must be quoted. + private String patternChars (DecimalFormatSymbols syms) + { + StringBuffer buf = new StringBuffer (); + buf.append(syms.getDecimalSeparator()); + buf.append(syms.getDigit()); + buf.append(syms.getExponential()); + buf.append(syms.getGroupingSeparator()); + // Adding this one causes pattern application to fail. + // Of course, omitting is causes toPattern to fail. + // ... but we already have bugs there. FIXME. + // buf.append(syms.getMinusSign()); + buf.append(syms.getPatternSeparator()); + buf.append(syms.getPercent()); + buf.append(syms.getPerMill()); + buf.append(syms.getZeroDigit()); + buf.append('\u00a4'); + return buf.toString(); + } + + private void applyPatternWithSymbols(String pattern, DecimalFormatSymbols syms) + { + // Initialize to the state the parser expects. + negativePrefix = ""; + negativeSuffix = ""; + positivePrefix = ""; + positiveSuffix = ""; + decimalSeparatorAlwaysShown = false; + groupingSize = 0; + minExponentDigits = 0; + multiplier = 1; + useExponentialNotation = false; + groupingUsed = false; + maximumFractionDigits = 0; + maximumIntegerDigits = MAXIMUM_INTEGER_DIGITS; + minimumFractionDigits = 0; + minimumIntegerDigits = 1; + + AttributedFormatBuffer buf = new AttributedFormatBuffer (); + String patChars = patternChars (syms); + + int max = pattern.length(); + int index = scanFix (pattern, 0, buf, patChars, syms, false); + buf.sync(); + positivePrefix = buf.getBuffer().toString(); + positivePrefixRanges = buf.getRanges(); + positivePrefixAttrs = buf.getAttributes(); + + index = scanFormat (pattern, index, patChars, syms, true); + + index = scanFix (pattern, index, buf, patChars, syms, true); + buf.sync(); + positiveSuffix = buf.getBuffer().toString(); + positiveSuffixRanges = buf.getRanges(); + positiveSuffixAttrs = buf.getAttributes(); + + if (index == pattern.length()) + { + // No negative info. + negativePrefix = null; + negativeSuffix = null; + } + else + { + if (pattern.charAt(index) != syms.getPatternSeparator()) + throw new IllegalArgumentException ("separator character " + + "expected - index: " + index); + + index = scanFix (pattern, index + 1, buf, patChars, syms, false); + buf.sync(); + negativePrefix = buf.getBuffer().toString(); + negativePrefixRanges = buf.getRanges(); + negativePrefixAttrs = buf.getAttributes(); + + // We parse the negative format for errors but we don't let + // it side-effect this object. + index = scanFormat (pattern, index, patChars, syms, false); + + index = scanFix (pattern, index, buf, patChars, syms, true); + buf.sync(); + negativeSuffix = buf.getBuffer().toString(); + negativeSuffixRanges = buf.getRanges(); + negativeSuffixAttrs = buf.getAttributes(); + + if (index != pattern.length()) + throw new IllegalArgumentException ("end of pattern expected " + + "- index: " + index); + } + } + + public void applyLocalizedPattern (String pattern) + { + // JCL p. 638 claims this throws a ParseException but p. 629 + // contradicts this. Empirical tests with patterns of "0,###.0" + // and "#.#.#" corroborate the p. 629 statement that an + // IllegalArgumentException is thrown. + applyPatternWithSymbols (pattern, symbols); + } + + public void applyPattern (String pattern) + { + // JCL p. 638 claims this throws a ParseException but p. 629 + // contradicts this. Empirical tests with patterns of "0,###.0" + // and "#.#.#" corroborate the p. 629 statement that an + // IllegalArgumentException is thrown. + applyPatternWithSymbols (pattern, nonLocalizedSymbols); + } + + public Object clone () + { + DecimalFormat c = (DecimalFormat) super.clone (); + c.symbols = (DecimalFormatSymbols) symbols.clone (); + return c; + } + + /** + * Constructs a <code>DecimalFormat</code> which uses the default + * pattern and symbols. + */ + public DecimalFormat () + { + this ("#,##0.###"); + } + + /** + * Constructs a <code>DecimalFormat</code> which uses the given + * pattern and the default symbols for formatting and parsing. + * + * @param pattern the non-localized pattern to use. + * @throws NullPointerException if any argument is null. + * @throws IllegalArgumentException if the pattern is invalid. + */ + public DecimalFormat (String pattern) + { + this (pattern, new DecimalFormatSymbols ()); + } + + /** + * Constructs a <code>DecimalFormat</code> using the given pattern + * and formatting symbols. This construction method is used to give + * complete control over the formatting process. + * + * @param pattern the non-localized pattern to use. + * @param symbols the set of symbols used for parsing and formatting. + * @throws NullPointerException if any argument is null. + * @throws IllegalArgumentException if the pattern is invalid. + */ + public DecimalFormat(String pattern, DecimalFormatSymbols symbols) + { + this.symbols = (DecimalFormatSymbols) symbols.clone(); + applyPattern(pattern); + } + + private boolean equals(String s1, String s2) + { + if (s1 == null || s2 == null) + return s1 == s2; + return s1.equals(s2); + } + + /** + * Tests this instance for equality with an arbitrary object. This method + * returns <code>true</code> if: + * <ul> + * <li><code>obj</code> is not <code>null</code>;</li> + * <li><code>obj</code> is an instance of <code>DecimalFormat</code>;</li> + * <li>this instance and <code>obj</code> have the same attributes;</li> + * </ul> + * + * @param obj the object (<code>null</code> permitted). + * + * @return A boolean. + */ + public boolean equals(Object obj) + { + if (! (obj instanceof DecimalFormat)) + return false; + DecimalFormat dup = (DecimalFormat) obj; + return (decimalSeparatorAlwaysShown == dup.decimalSeparatorAlwaysShown + && groupingUsed == dup.groupingUsed + && groupingSize == dup.groupingSize + && multiplier == dup.multiplier + && useExponentialNotation == dup.useExponentialNotation + && minExponentDigits == dup.minExponentDigits + && minimumIntegerDigits == dup.minimumIntegerDigits + && maximumIntegerDigits == dup.maximumIntegerDigits + && minimumFractionDigits == dup.minimumFractionDigits + && maximumFractionDigits == dup.maximumFractionDigits + && equals(negativePrefix, dup.negativePrefix) + && equals(negativeSuffix, dup.negativeSuffix) + && equals(positivePrefix, dup.positivePrefix) + && equals(positiveSuffix, dup.positiveSuffix) + && symbols.equals(dup.symbols)); + } + + private void formatInternal (double number, FormatBuffer dest, + FieldPosition fieldPos) + { + // A very special case. + if (Double.isNaN(number)) + { + dest.append(symbols.getNaN()); + if (fieldPos != null && + (fieldPos.getField() == INTEGER_FIELD || + fieldPos.getFieldAttribute() == NumberFormat.Field.INTEGER)) + { + int index = dest.length(); + fieldPos.setBeginIndex(index - symbols.getNaN().length()); + fieldPos.setEndIndex(index); + } + return; + } + + boolean is_neg = number < 0; + if (is_neg) + { + if (negativePrefix != null) + { + dest.append(substituteCurrency(negativePrefix, number), + negativePrefixRanges, negativePrefixAttrs); + } + else + { + dest.append(symbols.getMinusSign(), NumberFormat.Field.SIGN); + dest.append(substituteCurrency(positivePrefix, number), + positivePrefixRanges, positivePrefixAttrs); + } + number = - number; + } + else + { + dest.append(substituteCurrency(positivePrefix, number), + positivePrefixRanges, positivePrefixAttrs); + } + int integerBeginIndex = dest.length(); + int integerEndIndex = 0; + int zeroStart = symbols.getZeroDigit() - '0'; + + if (Double.isInfinite (number)) + { + dest.append(symbols.getInfinity()); + integerEndIndex = dest.length(); + } + else + { + number *= multiplier; + + // Compute exponent. + long exponent = 0; + double baseNumber; + if (useExponentialNotation) + { + exponent = (long) Math.floor (Math.log(number) / Math.log(10)); + exponent = exponent - (exponent % exponentRound); + if (minimumIntegerDigits > 0) + exponent -= minimumIntegerDigits - 1; + baseNumber = (number / Math.pow(10.0, exponent)); + } + else + baseNumber = number; + + // Round to the correct number of digits. + baseNumber += 5 * Math.pow(10.0, - maximumFractionDigits - 1); + + int index = dest.length(); + //double intPart = Math.floor(baseNumber); + String intPart = Long.toString((long)Math.floor(baseNumber)); + int count, groupPosition = intPart.length(); + + dest.setDefaultAttribute(NumberFormat.Field.INTEGER); + + for (count = 0; count < minimumIntegerDigits-intPart.length(); count++) + dest.append(symbols.getZeroDigit()); + + for (count = 0; + count < maximumIntegerDigits && count < intPart.length(); + count++) + { + int dig = intPart.charAt(count); + + // Append group separator if required. + if (groupingUsed && count > 0 && groupingSize != 0 && groupPosition % groupingSize == 0) + { + dest.append(symbols.getGroupingSeparator(), NumberFormat.Field.GROUPING_SEPARATOR); + dest.setDefaultAttribute(NumberFormat.Field.INTEGER); + } + dest.append((char) (zeroStart + dig)); + + groupPosition--; + } + dest.setDefaultAttribute(null); + + integerEndIndex = dest.length(); + + int decimal_index = integerEndIndex; + int consecutive_zeros = 0; + int total_digits = 0; + + int localMaximumFractionDigits = maximumFractionDigits; + + if (useExponentialNotation) + localMaximumFractionDigits += minimumIntegerDigits - count; + + // Strip integer part from NUMBER. + double fracPart = baseNumber - Math.floor(baseNumber); + + if ( ((fracPart != 0 || minimumFractionDigits > 0) && localMaximumFractionDigits > 0) + || decimalSeparatorAlwaysShown) + { + dest.append (symbols.getDecimalSeparator(), NumberFormat.Field.DECIMAL_SEPARATOR); + } + + int fraction_begin = dest.length(); + dest.setDefaultAttribute(NumberFormat.Field.FRACTION); + for (count = 0; + count < localMaximumFractionDigits + && (fracPart != 0 || count < minimumFractionDigits); + ++count) + { + ++total_digits; + fracPart *= 10; + long dig = (long) fracPart; + if (dig == 0) + ++consecutive_zeros; + else + consecutive_zeros = 0; + dest.append((char) (symbols.getZeroDigit() + dig)); + + // Strip integer part from FRACPART. + fracPart = fracPart - Math.floor (fracPart); + } + + // Strip extraneous trailing `0's. We can't always detect + // these in the loop. + int extra_zeros = Math.min (consecutive_zeros, + total_digits - minimumFractionDigits); + if (extra_zeros > 0) + { + dest.cutTail(extra_zeros); + total_digits -= extra_zeros; + if (total_digits == 0 && !decimalSeparatorAlwaysShown) + dest.cutTail(1); + } + + if (fieldPos != null && fieldPos.getField() == FRACTION_FIELD) + { + fieldPos.setBeginIndex(fraction_begin); + fieldPos.setEndIndex(dest.length()); + } + + // Finally, print the exponent. + if (useExponentialNotation) + { + dest.append(symbols.getExponential(), NumberFormat.Field.EXPONENT_SYMBOL); + if (exponent < 0) + { + dest.append (symbols.getMinusSign (), NumberFormat.Field.EXPONENT_SIGN); + exponent = - exponent; + } + index = dest.length(); + dest.setDefaultAttribute(NumberFormat.Field.EXPONENT); + String exponentString = Long.toString ((long) exponent); + + for (count = 0; count < minExponentDigits-exponentString.length(); + count++) + dest.append((char) symbols.getZeroDigit()); + + for (count = 0; + count < exponentString.length(); + ++count) + { + int dig = exponentString.charAt(count); + dest.append((char) (zeroStart + dig)); + } + } + } + + if (fieldPos != null && + (fieldPos.getField() == INTEGER_FIELD || + fieldPos.getFieldAttribute() == NumberFormat.Field.INTEGER)) + { + fieldPos.setBeginIndex(integerBeginIndex); + fieldPos.setEndIndex(integerEndIndex); + } + + if (is_neg && negativeSuffix != null) + { + dest.append(substituteCurrency(negativeSuffix, number), + negativeSuffixRanges, negativeSuffixAttrs); + } + else + { + dest.append(substituteCurrency(positiveSuffix, number), + positiveSuffixRanges, positiveSuffixAttrs); + } + } + + public StringBuffer format (double number, StringBuffer dest, + FieldPosition fieldPos) + { + formatInternal (number, new StringFormatBuffer(dest), fieldPos); + return dest; + } + + public AttributedCharacterIterator formatToCharacterIterator (Object value) + { + AttributedFormatBuffer sbuf = new AttributedFormatBuffer(); + + if (value instanceof Number) + formatInternal(((Number) value).doubleValue(), sbuf, null); + else + throw new IllegalArgumentException + ("Cannot format given Object as a Number"); + + sbuf.sync(); + return new FormatCharacterIterator(sbuf.getBuffer().toString(), + sbuf.getRanges(), + sbuf.getAttributes()); + } + + public StringBuffer format (long number, StringBuffer dest, + FieldPosition fieldPos) + { + // If using exponential notation, we just format as a double. + if (useExponentialNotation) + return format ((double) number, dest, fieldPos); + + boolean is_neg = number < 0; + if (is_neg) + { + if (negativePrefix != null) + dest.append(substituteCurrency(negativePrefix, number)); + else + { + dest.append(symbols.getMinusSign()); + dest.append(substituteCurrency(positivePrefix, number)); + } + number = - number; + } + else + dest.append(substituteCurrency(positivePrefix, number)); + + int integerBeginIndex = dest.length(); + int index = dest.length(); + int count = 0; + + /* Handle percentages, etc. */ + number *= multiplier; + while (count < maximumIntegerDigits + && (number > 0 || count < minimumIntegerDigits)) + { + long dig = number % 10; + number /= 10; + // NUMBER and DIG will be less than 0 if the original number + // was the most negative long. + if (dig < 0) + { + dig = - dig; + number = - number; + } + + // Append group separator if required. + if (groupingUsed && count > 0 && groupingSize != 0 && count % groupingSize == 0) + dest.insert(index, symbols.getGroupingSeparator()); + + dest.insert(index, (char) (symbols.getZeroDigit() + dig)); + + ++count; + } + + if (fieldPos != null && fieldPos.getField() == INTEGER_FIELD) + { + fieldPos.setBeginIndex(integerBeginIndex); + fieldPos.setEndIndex(dest.length()); + } + + if (decimalSeparatorAlwaysShown || minimumFractionDigits > 0) + { + dest.append(symbols.getDecimalSeparator()); + if (fieldPos != null && fieldPos.getField() == FRACTION_FIELD) + { + fieldPos.setBeginIndex(dest.length()); + fieldPos.setEndIndex(dest.length() + minimumFractionDigits); + } + } + + for (count = 0; count < minimumFractionDigits; ++count) + dest.append(symbols.getZeroDigit()); + + dest.append((is_neg && negativeSuffix != null) + ? substituteCurrency(negativeSuffix, number) + : substituteCurrency(positiveSuffix, number)); + return dest; + } + + /** + * Returns the currency corresponding to the currency symbol stored + * in the instance of <code>DecimalFormatSymbols</code> used by this + * <code>DecimalFormat</code>. + * + * @return A new instance of <code>Currency</code> if + * the currency code matches a known one, null otherwise. + */ + public Currency getCurrency() + { + return symbols.getCurrency(); + } + + /** + * Returns a copy of the symbols used by this instance. + * + * @return A copy of the symbols. + */ + public DecimalFormatSymbols getDecimalFormatSymbols() + { + return (DecimalFormatSymbols) symbols.clone(); + } + + public int getGroupingSize () + { + return groupingSize; + } + + public int getMultiplier () + { + return multiplier; + } + + public String getNegativePrefix () + { + return negativePrefix; + } + + public String getNegativeSuffix () + { + return negativeSuffix; + } + + public String getPositivePrefix () + { + return positivePrefix; + } + + public String getPositiveSuffix () + { + return positiveSuffix; + } + + /** + * Returns a hash code for this object. + * + * @return A hash code. + */ + public int hashCode() + { + return toPattern().hashCode(); + } + + public boolean isDecimalSeparatorAlwaysShown () + { + return decimalSeparatorAlwaysShown; + } + + public Number parse (String str, ParsePosition pos) + { + /* + * Our strategy is simple: copy the text into separate buffers: one for the int part, + * one for the fraction part and for the exponential part. + * We translate or omit locale-specific information. + * If exponential is sufficiently big we merge the fraction and int part and + * remove the '.' and then we use Long to convert the number. In the other + * case, we use Double to convert the full number. + */ + + boolean is_neg = false; + int index = pos.getIndex(); + StringBuffer int_buf = new StringBuffer (); + + // We have to check both prefixes, because one might be empty. We + // want to pick the longest prefix that matches. + boolean got_pos = str.startsWith(positivePrefix, index); + String np = (negativePrefix != null + ? negativePrefix + : positivePrefix + symbols.getMinusSign()); + boolean got_neg = str.startsWith(np, index); + + if (got_pos && got_neg) + { + // By checking this way, we preserve ambiguity in the case + // where the negative format differs only in suffix. We + // check this again later. + if (np.length() > positivePrefix.length()) + { + is_neg = true; + index += np.length(); + } + else + index += positivePrefix.length(); + } + else if (got_neg) + { + is_neg = true; + index += np.length(); + } + else if (got_pos) + index += positivePrefix.length(); + else + { + pos.setErrorIndex (index); + return null; + } + + // FIXME: handle Inf and NaN. + + // FIXME: do we have to respect minimum digits? + // What about multiplier? + + StringBuffer buf = int_buf; + StringBuffer frac_buf = null; + StringBuffer exp_buf = null; + int start_index = index; + int max = str.length(); + int exp_index = -1; + int last = index + maximumIntegerDigits; + + if (maximumFractionDigits > 0) + last += maximumFractionDigits + 1; + + if (useExponentialNotation) + last += minExponentDigits + 1; + + if (last > 0 && max > last) + max = last; + + char zero = symbols.getZeroDigit(); + int last_group = -1; + boolean int_part = true; + boolean exp_part = false; + for (; index < max; ++index) + { + char c = str.charAt(index); + + // FIXME: what about grouping size? + if (groupingUsed && c == symbols.getGroupingSeparator()) + { + if (last_group != -1 + && groupingSize != 0 + && (index - last_group) % groupingSize != 0) + { + pos.setErrorIndex(index); + return null; + } + last_group = index+1; + } + else if (c >= zero && c <= zero + 9) + { + buf.append((char) (c - zero + '0')); + } + else if (parseIntegerOnly) + break; + else if (c == symbols.getDecimalSeparator()) + { + if (last_group != -1 + && groupingSize != 0 + && (index - last_group) % groupingSize != 0) + { + pos.setErrorIndex(index); + return null; + } + buf = frac_buf = new StringBuffer(); + frac_buf.append('.'); + int_part = false; + } + else if (c == symbols.getExponential()) + { + buf = exp_buf = new StringBuffer(); + int_part = false; + exp_part = true; + exp_index = index+1; + } + else if (exp_part + && (c == '+' || c == '-' || c == symbols.getMinusSign())) + { + // For exponential notation. + buf.append(c); + } + else + break; + } + + if (index == start_index) + { + // Didn't see any digits. + pos.setErrorIndex(index); + return null; + } + + // Check the suffix. We must do this before converting the + // buffer to a number to handle the case of a number which is + // the most negative Long. + boolean got_pos_suf = str.startsWith(positiveSuffix, index); + String ns = (negativePrefix == null ? positiveSuffix : negativeSuffix); + boolean got_neg_suf = str.startsWith(ns, index); + if (is_neg) + { + if (! got_neg_suf) + { + pos.setErrorIndex(index); + return null; + } + } + else if (got_pos && got_neg && got_neg_suf) + { + is_neg = true; + } + else if (got_pos != got_pos_suf && got_neg != got_neg_suf) + { + pos.setErrorIndex(index); + return null; + } + else if (! got_pos_suf) + { + pos.setErrorIndex(index); + return null; + } + + String suffix = is_neg ? ns : positiveSuffix; + long parsedMultiplier = 1; + boolean use_long; + + if (is_neg) + int_buf.insert(0, '-'); + + // Now handle the exponential part if there is one. + if (exp_buf != null) + { + int exponent_value; + + try + { + exponent_value = Integer.parseInt(exp_buf.toString()); + } + catch (NumberFormatException x1) + { + pos.setErrorIndex(exp_index); + return null; + } + + if (frac_buf == null) + { + // We only have to add some zeros to the int part. + // Build a multiplier. + for (int i = 0; i < exponent_value; i++) + int_buf.append('0'); + + use_long = true; + } + else + { + boolean long_sufficient; + + if (exponent_value < frac_buf.length()-1) + { + int lastNonNull = -1; + /* We have to check the fraction buffer: it may only be full of '0' + * or be sufficiently filled with it to convert the number into Long. + */ + for (int i = 1; i < frac_buf.length(); i++) + if (frac_buf.charAt(i) != '0') + lastNonNull = i; + + long_sufficient = (lastNonNull < 0 || lastNonNull <= exponent_value); + } + else + long_sufficient = true; + + if (long_sufficient) + { + for (int i = 1; i < frac_buf.length() && i < exponent_value; i++) + int_buf.append(frac_buf.charAt(i)); + for (int i = frac_buf.length()-1; i < exponent_value; i++) + int_buf.append('0'); + use_long = true; + } + else + { + /* + * A long type is not sufficient, we build the full buffer to + * be parsed by Double. + */ + int_buf.append(frac_buf); + int_buf.append('E'); + int_buf.append(exp_buf); + use_long = false; + } + } + } + else + { + if (frac_buf != null) + { + /* Check whether the fraction buffer contains only '0' */ + int i; + for (i = 1; i < frac_buf.length(); i++) + if (frac_buf.charAt(i) != '0') + break; + + if (i != frac_buf.length()) + { + use_long = false; + int_buf.append(frac_buf); + } + else + use_long = true; + } + else + use_long = true; + } + + String t = int_buf.toString(); + Number result = null; + if (use_long) + { + try + { + result = new Long (t); + } + catch (NumberFormatException x1) + { + } + } + else + { + try + { + result = new Double (t); + } + catch (NumberFormatException x2) + { + } + } + if (result == null) + { + pos.setErrorIndex(index); + return null; + } + + pos.setIndex(index + suffix.length()); + + return result; + } + + /** + * Sets the <code>Currency</code> on the + * <code>DecimalFormatSymbols</code> used, which also sets the + * currency symbols on those symbols. + */ + public void setCurrency(Currency currency) + { + symbols.setCurrency(currency); + } + + /** + * Sets the symbols used by this instance. This method makes a copy of + * the supplied symbols. + * + * @param newSymbols the symbols (<code>null</code> not permitted). + */ + public void setDecimalFormatSymbols(DecimalFormatSymbols newSymbols) + { + symbols = (DecimalFormatSymbols) newSymbols.clone(); + } + + public void setDecimalSeparatorAlwaysShown (boolean newValue) + { + decimalSeparatorAlwaysShown = newValue; + } + + public void setGroupingSize (int groupSize) + { + groupingSize = (byte) groupSize; + } + + public void setMaximumFractionDigits (int newValue) + { + super.setMaximumFractionDigits(Math.min(newValue, 340)); + } + + public void setMaximumIntegerDigits (int newValue) + { + super.setMaximumIntegerDigits(Math.min(newValue, 309)); + } + + public void setMinimumFractionDigits (int newValue) + { + super.setMinimumFractionDigits(Math.min(newValue, 340)); + } + + public void setMinimumIntegerDigits (int newValue) + { + super.setMinimumIntegerDigits(Math.min(newValue, 309)); + } + + public void setMultiplier (int newValue) + { + multiplier = newValue; + } + + public void setNegativePrefix (String newValue) + { + negativePrefix = newValue; + } + + public void setNegativeSuffix (String newValue) + { + negativeSuffix = newValue; + } + + public void setPositivePrefix (String newValue) + { + positivePrefix = newValue; + } + + public void setPositiveSuffix (String newValue) + { + positiveSuffix = newValue; + } + + private void quoteFix(StringBuffer buf, String text, String patChars) + { + int len = text.length(); + for (int index = 0; index < len; ++index) + { + char c = text.charAt(index); + if (patChars.indexOf(c) != -1) + { + buf.append('\''); + buf.append(c); + buf.append('\''); + } + else + buf.append(c); + } + } + + private String computePattern(DecimalFormatSymbols syms) + { + StringBuffer mainPattern = new StringBuffer (); + // We have to at least emit a zero for the minimum number of + // digits. Past that we need hash marks up to the grouping + // separator (and one beyond). + int total_digits = Math.max(minimumIntegerDigits, + groupingUsed ? groupingSize + 1: groupingSize); + for (int i = 0; i < total_digits - minimumIntegerDigits; ++i) + mainPattern.append(syms.getDigit()); + for (int i = total_digits - minimumIntegerDigits; i < total_digits; ++i) + mainPattern.append(syms.getZeroDigit()); + // Inserting the gropuing operator afterwards is easier. + if (groupingUsed) + mainPattern.insert(mainPattern.length() - groupingSize, + syms.getGroupingSeparator()); + // See if we need decimal info. + if (minimumFractionDigits > 0 || maximumFractionDigits > 0 + || decimalSeparatorAlwaysShown) + mainPattern.append(syms.getDecimalSeparator()); + for (int i = 0; i < minimumFractionDigits; ++i) + mainPattern.append(syms.getZeroDigit()); + for (int i = minimumFractionDigits; i < maximumFractionDigits; ++i) + mainPattern.append(syms.getDigit()); + if (useExponentialNotation) + { + mainPattern.append(syms.getExponential()); + for (int i = 0; i < minExponentDigits; ++i) + mainPattern.append(syms.getZeroDigit()); + if (minExponentDigits == 0) + mainPattern.append(syms.getDigit()); + } + + String main = mainPattern.toString(); + String patChars = patternChars (syms); + mainPattern.setLength(0); + + quoteFix (mainPattern, positivePrefix, patChars); + mainPattern.append(main); + quoteFix (mainPattern, positiveSuffix, patChars); + + if (negativePrefix != null) + { + quoteFix (mainPattern, negativePrefix, patChars); + mainPattern.append(main); + quoteFix (mainPattern, negativeSuffix, patChars); + } + + return mainPattern.toString(); + } + + public String toLocalizedPattern () + { + return computePattern (symbols); + } + + public String toPattern () + { + return computePattern (nonLocalizedSymbols); + } + + private static final int MAXIMUM_INTEGER_DIGITS = 309; + + // These names are fixed by the serialization spec. + private boolean decimalSeparatorAlwaysShown; + private byte groupingSize; + private byte minExponentDigits; + private int exponentRound; + private int multiplier; + private String negativePrefix; + private String negativeSuffix; + private String positivePrefix; + private String positiveSuffix; + private int[] negativePrefixRanges, positivePrefixRanges; + private HashMap[] negativePrefixAttrs, positivePrefixAttrs; + private int[] negativeSuffixRanges, positiveSuffixRanges; + private HashMap[] negativeSuffixAttrs, positiveSuffixAttrs; + private int serialVersionOnStream = 1; + private DecimalFormatSymbols symbols; + private boolean useExponentialNotation; + private static final long serialVersionUID = 864413376551465018L; + + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException + { + stream.defaultReadObject(); + if (serialVersionOnStream < 1) + { + useExponentialNotation = false; + serialVersionOnStream = 1; + } + } + + // The locale-independent pattern symbols happen to be the same as + // the US symbols. + private static final DecimalFormatSymbols nonLocalizedSymbols + = new DecimalFormatSymbols (Locale.US); + + /** + * <p> + * Substitutes the currency symbol into the given string, + * based on the value used. Currency symbols can either + * be a simple series of characters (e.g. '$'), which are + * simply used as is, or they can be of a more complex + * form: + * </p> + * <p> + * (lower bound)|(mid value)|(upper bound) + * </p> + * <p> + * where each bound has the syntax '(value)(# or <)(symbol)', + * to indicate the bounding value and the symbol used. + * </p> + * <p> + * The currency symbol replaces the currency specifier, '\u00a4', + * an unlocalised character, which thus is used as such in all formats. + * If this symbol occurs twice, the international currency code is used + * instead. + * </p> + * + * @param string The string containing the currency specifier, '\u00a4'. + * @param number the number being formatted. + * @return a string formatted for the correct currency. + */ + private String substituteCurrency(String string, double number) + { + int index; + int length; + char currentChar; + StringBuffer buf; + + index = 0; + length = string.length(); + buf = new StringBuffer(); + + while (index < length) + { + currentChar = string.charAt(index); + if (string.charAt(index) == '\u00a4') + { + if ((index + 1) < length && string.charAt(index + 1) == '\u00a4') + { + buf.append(symbols.getInternationalCurrencySymbol()); + index += 2; + } + else + { + String symbol; + + symbol = symbols.getCurrencySymbol(); + if (symbol.startsWith("=")) + { + String[] bounds; + int[] boundValues; + String[] boundSymbols; + + bounds = symbol.substring(1).split("\\|"); + boundValues = new int[3]; + boundSymbols = new String[3]; + for (int a = 0; a < 3; ++a) + { + String[] bound; + + bound = bounds[a].split("[#<]"); + boundValues[a] = Integer.parseInt(bound[0]); + boundSymbols[a] = bound[1]; + } + if (number <= boundValues[0]) + { + buf.append(boundSymbols[0]); + } + else if (number >= boundValues[2]) + { + buf.append(boundSymbols[2]); + } + else + { + buf.append(boundSymbols[1]); + } + ++index; + } + else + { + buf.append(symbol); + ++index; + } + } + } + else + { + buf.append(string.charAt(index)); + ++index; + } + } + return buf.toString(); + } + +} |