summaryrefslogtreecommitdiff
path: root/java/text/DecimalFormat.java
diff options
context:
space:
mode:
authorMario Torre <neugens@limasoftware.net>2006-11-25 18:20:54 +0000
committerMario Torre <neugens@limasoftware.net>2006-11-25 18:20:54 +0000
commitc63ede2936b55b926367bc7f1160b6f1bee591bc (patch)
tree8d2bc1fe597b4d57b2cbea705c337d60e7034018 /java/text/DecimalFormat.java
parent84e51cd1b15042a78264f25c66e8da3ab9915da6 (diff)
downloadclasspath-c63ede2936b55b926367bc7f1160b6f1bee591bc.tar.gz
2006-11-25 Mario Torre <neugens@nirvana.limasoftware.net>
PR28462 * java/text/DecimalFormat.java: Almost new rewrite, and update to 1.5. * java/text/NumberFormat.java (format): all format methods, fixed FieldPosition argument should never be null. (format(Object, StringBuffer, FieldPosition)): fixed signature, method is not final. * java/text/DecimalFormatSymbols.java (clone): fixed to also clone locale. * AUTHORS: added my name to the file.
Diffstat (limited to 'java/text/DecimalFormat.java')
-rw-r--r--java/text/DecimalFormat.java2971
1 files changed, 1854 insertions, 1117 deletions
diff --git a/java/text/DecimalFormat.java b/java/text/DecimalFormat.java
index f64249b67..98f8c284b 100644
--- a/java/text/DecimalFormat.java
+++ b/java/text/DecimalFormat.java
@@ -35,387 +35,157 @@ 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. */
+/*
+ * This class contains few bits from ICU4J (http://icu.sourceforge.net/),
+ * Copyright by IBM and others and distributed under the
+ * distributed under MIT/X.
+ */
+
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.math.BigDecimal;
+import java.math.BigInteger;
-import java.io.IOException;
-import java.io.ObjectInputStream;
+import java.util.ArrayList;
import java.util.Currency;
-import java.util.HashMap;
import java.util.Locale;
-/**
+/*
+ * This note is here for historical reasons and because I had not the courage
+ * to remove it :)
+ *
* @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
+ *
+ * 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.
*/
+
+/**
+ * This class is a concrete implementation of NumberFormat used to format
+ * decimal numbers. The class can format numbers given a specific locale.
+ * Generally, to get an instance of DecimalFormat you should call the factory
+ * methods in the <code>NumberFormat</code> base class.
+ *
+ * @author Mario Torre <neugens@limasoftware.net>
+ * @author Tom Tromey (tromey@cygnus.com)
+ * @author Andrew John Hughes (gnu_andrew@member.fsf.org)
+ */
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;
- // Checking "zeroCount > 0" avoids 0 being formatted into "" with "#".
- if (zeroCount > 0)
- 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;
- }
+ /** serialVersionUID for serializartion. */
+ private static final long serialVersionUID = 864413376551465018L;
+
+ /** Defines the default number of digits allowed while formatting integers. */
+ private static final int DEFAULT_INTEGER_DIGITS = 309;
/**
+ * Defines the default number of digits allowed while formatting
+ * fractions.
+ */
+ private static final int DEFAULT_FRACTION_DIGITS = 340;
+
+ /**
+ * Locale-independent pattern symbols.
+ */
+ // Happen to be the same as the US symbols.
+ private static final DecimalFormatSymbols nonLocalizedSymbols
+ = new DecimalFormatSymbols (Locale.US);
+
+ /**
+ * Defines if parse should return a BigDecimal or not.
+ */
+ private boolean parseBigDecimal;
+
+ /**
+ * Defines if we have to use the monetary decimal separator or
+ * the decimal separator while formatting numbers.
+ */
+ private boolean useCurrencySeparator;
+
+ /** Defines if the decimal separator is always shown or not. */
+ private boolean decimalSeparatorAlwaysShown;
+
+ /**
+ * Defines if the decimal separator has to be shown.
+ *
+ * This is different then <code>decimalSeparatorAlwaysShown</code>,
+ * as it defines if the format string contains a decimal separator or no.
+ */
+ private boolean showDecimalSeparator;
+
+ /**
+ * This field is used to determine if the grouping
+ * separator is included in the format string or not.
+ * This is only needed to match the behaviour of the RI.
+ */
+ private boolean groupingSeparatorInPattern;
+
+ /** Defines the size of grouping groups when grouping is used. */
+ private byte groupingSize;
+
+ /**
+ * This is an internal parameter used to keep track of the number
+ * of digits the form the exponent, when exponential notation is used.
+ * It is used with <code>exponentRound</code>
+ */
+ private byte minExponentDigits;
+
+ /** This field is used to set the exponent in the engineering notation. */
+ private int exponentRound;
+
+ /** Multiplier used in percent style formats. */
+ private int multiplier;
+
+ /** Multiplier used in percent style formats. */
+ private int negativePatternMultiplier;
+
+ /** The negative prefix. */
+ private String negativePrefix;
+
+ /** The negative suffix. */
+ private String negativeSuffix;
+
+ /** The positive prefix. */
+ private String positivePrefix;
+
+ /** The positive suffix. */
+ private String positiveSuffix;
+
+ /** Decimal Format Symbols for the given locale. */
+ private DecimalFormatSymbols symbols;
+
+ /** Determine if we have to use exponential notation or not. */
+ private boolean useExponentialNotation;
+
+ /**
+ * Defines the maximum number of integer digits to show when we use
+ * the exponential notation.
+ */
+ private int maxIntegerDigitsExponent;
+
+ /** Defines if the format string has a negative prefix or not. */
+ private boolean hasNegativePrefix;
+
+ /** Defines if the format string has a fractional pattern or not. */
+ private boolean hasFractionalPattern;
+
+ /** Stores a list of attributes for use by formatToCharacterIterator. */
+ private ArrayList attributes = new ArrayList();
+
+ /**
* Constructs a <code>DecimalFormat</code> which uses the default
* pattern and symbols.
*/
- public DecimalFormat ()
+ public DecimalFormat()
{
this ("#,##0.###");
}
-
+
/**
* Constructs a <code>DecimalFormat</code> which uses the given
* pattern and the default symbols for formatting and parsing.
@@ -424,9 +194,9 @@ public class DecimalFormat extends NumberFormat
* @throws NullPointerException if any argument is null.
* @throws IllegalArgumentException if the pattern is invalid.
*/
- public DecimalFormat (String pattern)
+ public DecimalFormat(String pattern)
{
- this (pattern, new DecimalFormatSymbols ());
+ this (pattern, new DecimalFormatSymbols());
}
/**
@@ -442,14 +212,38 @@ public class DecimalFormat extends NumberFormat
public DecimalFormat(String pattern, DecimalFormatSymbols symbols)
{
this.symbols = (DecimalFormatSymbols) symbols.clone();
- applyPattern(pattern);
+ applyPatternWithSymbols(pattern, nonLocalizedSymbols);
+ }
+
+ /**
+ * Apply the given localized patern to the current DecimalFormat object.
+ *
+ * @param pattern The localized pattern to apply.
+ * @throws IllegalArgumentException if the given pattern is invalid.
+ * @throws NullPointerException if the input pattern is null.
+ */
+ public void applyLocalizedPattern (String pattern)
+ {
+ applyPatternWithSymbols(pattern, this.symbols);
}
- private boolean equals(String s1, String s2)
+ /**
+ * Apply the given localized patern to the current DecimalFormat object.
+ *
+ * @param pattern The localized pattern to apply.
+ * @throws IllegalArgumentException if the given pattern is invalid.
+ * @throws NullPointerException if the input pattern is null.
+ */
+ public void applyPattern(String pattern)
{
- if (s1 == null || s2 == null)
- return s1 == s2;
- return s1.equals(s2);
+ applyPatternWithSymbols(pattern, nonLocalizedSymbols);
+ }
+
+ public Object clone()
+ {
+ DecimalFormat c = (DecimalFormat) super.clone();
+ c.symbols = (DecimalFormatSymbols) symbols.clone();
+ return c;
}
/**
@@ -471,8 +265,9 @@ public class DecimalFormat extends NumberFormat
return false;
DecimalFormat dup = (DecimalFormat) obj;
return (decimalSeparatorAlwaysShown == dup.decimalSeparatorAlwaysShown
- && groupingUsed == dup.groupingUsed
- && groupingSize == dup.groupingSize
+ && groupingUsed == dup.groupingUsed
+ && groupingSeparatorInPattern == dup.groupingSeparatorInPattern
+ && groupingSize == dup.groupingSize
&& multiplier == dup.multiplier
&& useExponentialNotation == dup.useExponentialNotation
&& minExponentDigits == dup.minExponentDigits
@@ -480,6 +275,14 @@ public class DecimalFormat extends NumberFormat
&& maximumIntegerDigits == dup.maximumIntegerDigits
&& minimumFractionDigits == dup.minimumFractionDigits
&& maximumFractionDigits == dup.maximumFractionDigits
+ && parseBigDecimal == dup.parseBigDecimal
+ && useCurrencySeparator == dup.useCurrencySeparator
+ && showDecimalSeparator == dup.showDecimalSeparator
+ && exponentRound == dup.exponentRound
+ && negativePatternMultiplier == dup.negativePatternMultiplier
+ && maxIntegerDigitsExponent == dup.maxIntegerDigitsExponent
+ // XXX: causes equivalent patterns to fail
+ // && hasNegativePrefix == dup.hasNegativePrefix
&& equals(negativePrefix, dup.negativePrefix)
&& equals(negativeSuffix, dup.negativeSuffix)
&& equals(positivePrefix, dup.positivePrefix)
@@ -487,306 +290,163 @@ public class DecimalFormat extends NumberFormat
&& symbols.equals(dup.symbols));
}
- private void formatInternal (double number, FormatBuffer dest,
- FieldPosition fieldPos)
+ /**
+ * Returns a hash code for this object.
+ *
+ * @return A hash code.
+ */
+ public int hashCode()
{
- // 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
+ return toPattern().hashCode();
+ }
+
+ /**
+ * Produce a formatted {@link String} representation of this object.
+ * The passed object must be of type number.
+ *
+ * @param obj The {@link Number} to format.
+ * @param sbuf The destination String; text will be appended to this String.
+ * @param pos If used on input can be used to define an alignment
+ * field. If used on output defines the offsets of the alignment field.
+ * @return The String representation of this long.
+ */
+ public StringBuffer format(Object obj, StringBuffer sbuf, FieldPosition pos)
+ {
+ if (obj instanceof BigInteger)
{
- dest.append(substituteCurrency(positivePrefix, number),
- positivePrefixRanges, positivePrefixAttrs);
+ BigDecimal decimal = new BigDecimal((BigInteger) obj);
+ formatInternal(decimal, true, sbuf, pos);
+ return sbuf;
}
- int integerBeginIndex = dest.length();
- int integerEndIndex = 0;
- int zeroStart = symbols.getZeroDigit() - '0';
-
- if (Double.isInfinite (number))
+ else if (obj instanceof BigDecimal)
{
- dest.append(symbols.getInfinity());
- integerEndIndex = dest.length();
+ formatInternal((BigDecimal) obj, true, sbuf, pos);
+ return sbuf;
}
- else
- {
- number *= multiplier;
-
- // Compute exponent.
- long exponent = 0;
- double baseNumber;
- if (useExponentialNotation && number > 0)
- {
- exponent = (long) Math.floor (Math.log10(number));
- 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))
+
+ return super.format(obj, sbuf, pos);
+ }
+
+ /**
+ * Produce a formatted {@link String} representation of this double.
+ *
+ * @param number The double to format.
+ * @param dest The destination String; text will be appended to this String.
+ * @param fieldPos If used on input can be used to define an alignment
+ * field. If used on output defines the offsets of the alignment field.
+ * @return The String representation of this long.
+ * @throws NullPointerException if <code>dest</code> or fieldPos are null
+ */
+ public StringBuffer format(double number, StringBuffer dest,
+ FieldPosition fieldPos)
+ {
+ // special cases for double: NaN and negative or positive infinity
+ if (Double.isNaN(number))
{
- fieldPos.setBeginIndex(integerBeginIndex);
- fieldPos.setEndIndex(integerEndIndex);
+ // 1. NaN
+ String nan = symbols.getNaN();
+ dest.append(nan);
+
+ // update field position if required
+ if ((fieldPos.getField() == INTEGER_FIELD ||
+ fieldPos.getFieldAttribute() == NumberFormat.Field.INTEGER))
+ {
+ int index = dest.length();
+ fieldPos.setBeginIndex(index - nan.length());
+ fieldPos.setEndIndex(index);
+ }
}
-
- if (is_neg && negativeSuffix != null)
+ else if (Double.isInfinite(number))
{
- dest.append(substituteCurrency(negativeSuffix, number),
- negativeSuffixRanges, negativeSuffixAttrs);
+ // 2. Infinity
+ if (number < 0)
+ dest.append(this.negativePrefix);
+ else
+ dest.append(this.positivePrefix);
+
+ dest.append(symbols.getInfinity());
+
+ if (number < 0)
+ dest.append(this.negativeSuffix);
+ else
+ dest.append(this.positiveSuffix);
+
+ if ((fieldPos.getField() == INTEGER_FIELD ||
+ fieldPos.getFieldAttribute() == NumberFormat.Field.INTEGER))
+ {
+ fieldPos.setBeginIndex(dest.length());
+ fieldPos.setEndIndex(0);
+ }
}
else
{
- dest.append(substituteCurrency(positiveSuffix, number),
- positiveSuffixRanges, positiveSuffixAttrs);
+ // get the number as a BigDecimal
+ BigDecimal bigDecimal = new BigDecimal(String.valueOf(number));
+ formatInternal(bigDecimal, false, dest, fieldPos);
}
+
+ return dest;
}
- public StringBuffer format (double number, StringBuffer dest,
- FieldPosition fieldPos)
+ /**
+ * Produce a formatted {@link String} representation of this long.
+ *
+ * @param number The long to format.
+ * @param dest The destination String; text will be appended to this String.
+ * @param fieldPos If used on input can be used to define an alignment
+ * field. If used on output defines the offsets of the alignment field.
+ * @return The String representation of this long.
+ */
+ public StringBuffer format(long number, StringBuffer dest,
+ FieldPosition fieldPos)
{
- formatInternal (number, new StringFormatBuffer(dest), fieldPos);
+ BigDecimal bigDecimal = new BigDecimal(String.valueOf(number));
+ formatInternal(bigDecimal, true, dest, fieldPos);
return dest;
}
-
- public AttributedCharacterIterator formatToCharacterIterator (Object value)
+
+ /**
+ * Return an <code>AttributedCharacterIterator</code> as a result of
+ * the formatting of the passed {@link Object}.
+ *
+ * @return An {@link AttributedCharacterIterator}.
+ * @throws NullPointerException if value is <code>null</code>.
+ * @throws IllegalArgumentException if value is not an instance of
+ * {@link Number}.
+ */
+ 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");
+ /*
+ * This method implementation derives directly from the
+ * ICU4J (http://icu.sourceforge.net/) library, distributed under MIT/X.
+ */
- 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));
+ if (value == null)
+ throw new NullPointerException("Passed Object is null");
+
+ if (!(value instanceof Number)) throw new
+ IllegalArgumentException("Cannot format given Object as a Number");
+
+ StringBuffer text = new StringBuffer();
+ attributes.clear();
+ super.format(value, text, new FieldPosition(0));
- ++count;
- }
+ AttributedString as = new AttributedString(text.toString());
- if (fieldPos != null && fieldPos.getField() == INTEGER_FIELD)
+ // add NumberFormat field attributes to the AttributedString
+ for (int i = 0; i < attributes.size(); i++)
{
- 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);
- }
+ FieldPosition pos = (FieldPosition) attributes.get(i);
+ Format.Field attribute = pos.getFieldAttribute();
+
+ as.addAttribute(attribute, attribute, pos.getBeginIndex(), pos
+ .getEndIndex());
}
-
- for (count = 0; count < minimumFractionDigits; ++count)
- dest.append(symbols.getZeroDigit());
-
- dest.append((is_neg && negativeSuffix != null)
- ? substituteCurrency(negativeSuffix, number)
- : substituteCurrency(positiveSuffix, number));
- return dest;
+
+ // return the CharacterIterator from AttributedString
+ return as.getIterator();
}
-
+
/**
* Returns the currency corresponding to the currency symbol stored
* in the instance of <code>DecimalFormatSymbols</code> used by this
@@ -799,7 +459,7 @@ public class DecimalFormat extends NumberFormat
{
return symbols.getCurrency();
}
-
+
/**
* Returns a copy of the symbols used by this instance.
*
@@ -809,351 +469,326 @@ public class DecimalFormat extends NumberFormat
{
return (DecimalFormatSymbols) symbols.clone();
}
-
- public int getGroupingSize ()
+
+ /**
+ * Gets the interval used between a grouping separator and the next.
+ * For example, a grouping size of 3 means that the number 1234 is
+ * formatted as 1,234.
+ *
+ * The actual character used as grouping separator depends on the
+ * locale and is defined by {@link DecimalFormatSymbols#getDecimalSeparator()}
+ *
+ * @return The interval used between a grouping separator and the next.
+ */
+ public int getGroupingSize()
{
return groupingSize;
}
- public int getMultiplier ()
+ /**
+ * Gets the multiplier used in percent and similar formats.
+ *
+ * @return The multiplier used in percent and similar formats.
+ */
+ public int getMultiplier()
{
return multiplier;
}
-
- public String getNegativePrefix ()
+
+ /**
+ * Gets the negative prefix.
+ *
+ * @return The negative prefix.
+ */
+ public String getNegativePrefix()
{
return negativePrefix;
}
- public String getNegativeSuffix ()
+ /**
+ * Gets the negative suffix.
+ *
+ * @return The negative suffix.
+ */
+ public String getNegativeSuffix()
{
return negativeSuffix;
}
-
- public String getPositivePrefix ()
+
+ /**
+ * Gets the positive prefix.
+ *
+ * @return The positive prefix.
+ */
+ public String getPositivePrefix()
{
return positivePrefix;
}
-
- public String getPositiveSuffix ()
+
+ /**
+ * Gets the positive suffix.
+ *
+ * @return The positive suffix.
+ */
+ public String getPositiveSuffix()
{
return positiveSuffix;
}
-
+
+ public boolean isDecimalSeparatorAlwaysShown()
+ {
+ return decimalSeparatorAlwaysShown;
+ }
+
/**
- * Returns a hash code for this object.
- *
- * @return A hash code.
+ * Define if <code>parse(java.lang.String, java.text.ParsePosition)</code>
+ * should return a {@link BigDecimal} or not.
+ *
+ * @param newValue
*/
- public int hashCode()
+ public void setParseBigDecimal(boolean newValue)
{
- return toPattern().hashCode();
+ this.parseBigDecimal = newValue;
}
-
- public boolean isDecimalSeparatorAlwaysShown ()
+
+ /**
+ * Returns <code>true</code> if
+ * <code>parse(java.lang.String, java.text.ParsePosition)</code> returns
+ * a <code>BigDecimal</code>, <code>false</code> otherwise.
+ * The default return value for this method is <code>false</code>.
+ *
+ * @return <code>true</code> if the parse method returns a {@link BigDecimal},
+ * <code>false</code> otherwise.
+ * @since 1.5
+ * @see #setParseBigDecimal(boolean)
+ */
+ public boolean isParseBigDecimal()
{
- return decimalSeparatorAlwaysShown;
+ return this.parseBigDecimal;
}
-
- public Number parse (String str, ParsePosition pos)
+
+ /**
+ * This method parses the specified string into a <code>Number</code>.
+ *
+ * The parsing starts at <code>pos</code>, which is updated as the parser
+ * consume characters in the passed string.
+ * On error, the <code>Position</code> object index is not updated, while
+ * error position is set appropriately, an <code>null</code> is returned.
+ *
+ * @param str The string to parse.
+ * @param pos The desired <code>ParsePosition</code>.
+ *
+ * @return The parsed <code>Number</code>
+ */
+ 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)
+ // a special values before anything else
+ // NaN
+ if (str.contains(this.symbols.getNaN()))
+ return Double.valueOf(Double.NaN);
+
+ // this will be our final number
+ StringBuffer number = new StringBuffer();
+
+ // special character
+ char minus = symbols.getMinusSign();
+
+ // starting parsing position
+ int start = pos.getIndex();
+
+ // validate the string, it have to be in the
+ // same form as the format string or parsing will fail
+ String _negativePrefix = (this.negativePrefix.compareTo("") == 0
+ ? minus + positivePrefix
+ : this.negativePrefix);
+
+ // we check both prefixes, because one might be empty.
+ // We want to pick the longest prefix that matches.
+ int positiveLen = positivePrefix.length();
+ int negativeLen = _negativePrefix.length();
+
+ boolean isNegative = str.startsWith(_negativePrefix);
+ boolean isPositive = str.startsWith(positivePrefix);
+
+ if (isPositive && isNegative)
+ {
+ // By checking this way, we preserve ambiguity in the case
+ // where the negative format differs only in suffix.
+ if (negativeLen > positiveLen)
+ {
+ start += _negativePrefix.length();
+ isNegative = true;
+ }
+ else
+ {
+ start += positivePrefix.length();
+ isPositive = true;
+ if (negativeLen < positiveLen)
+ isNegative = false;
+ }
+ }
+ else if (isNegative)
{
- // 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();
+ start += _negativePrefix.length();
+ isPositive = false;
}
- else if (got_neg)
+ else if (isPositive)
{
- is_neg = true;
- index += np.length();
+ start += positivePrefix.length();
+ isNegative = false;
}
- else if (got_pos)
- index += positivePrefix.length();
else
{
- pos.setErrorIndex (index);
- return null;
+ pos.setErrorIndex(start);
+ 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;
+
+ // other special characters used by the parser
+ char decimalSeparator = symbols.getDecimalSeparator();
+ char zero = symbols.getZeroDigit();
+ char exponent = symbols.getExponential();
+
+ // stop parsing position in the string
+ int stop = start + this.maximumIntegerDigits + maximumFractionDigits + 2;
if (useExponentialNotation)
- last += minExponentDigits + 1;
-
- if (last > 0 && max > last)
- max = last;
+ stop += minExponentDigits + 1;
+
+ boolean inExponent = false;
- char zero = symbols.getZeroDigit();
- int last_group = -1;
- boolean int_part = true;
- boolean exp_part = false;
- for (; index < max; ++index)
+ // correct the size of the end parsing flag
+ int len = str.length();
+ if (len < stop) stop = len;
+
+ char ch;
+ int i = 0;
+ for (i = start; i < stop; i++)
{
- 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;
+ ch = str.charAt(i);
+
+ if (ch >= zero && ch <= (zero + 9))
+ {
+ number.append(ch);
+ }
+ else if (this.parseIntegerOnly)
+ {
+ break;
+ }
+ else if (ch == decimalSeparator)
+ {
+ number.append('.');
+ }
+ else if (ch == exponent)
+ {
+ number.append(ch);
+ inExponent = !inExponent;
+ }
+ else if ((ch == '+' || ch == '-' || ch == minus))
+ {
+ if (inExponent)
+ number.append(ch);
+ else
+ break;
+ }
}
- if (index == start_index)
+ // 2nd special case: infinity
+ // XXX: need to be tested
+ if (str.contains(symbols.getInfinity()))
{
- // Didn't see any digits.
- pos.setErrorIndex(index);
- return null;
- }
+ int inf = str.indexOf(symbols.getInfinity());
+ pos.setIndex(inf);
+
+ // FIXME: ouch, this is really ugly and lazy code...
+ if (this.parseBigDecimal)
+ {
+ if (isNegative)
+ return new BigDecimal(Double.NEGATIVE_INFINITY);
+
+ return new BigDecimal(Double.POSITIVE_INFINITY);
+ }
+
+ if (isNegative)
+ return new Double(Double.NEGATIVE_INFINITY);
- // 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;
+ return new Double(Double.POSITIVE_INFINITY);
}
- else if (! got_pos_suf)
+
+ // no number...
+ if (i == start || number.length() == 0)
{
- pos.setErrorIndex(index);
- return null;
+ pos.setErrorIndex(i);
+ return null;
}
- String suffix = is_neg ? ns : positiveSuffix;
- long parsedMultiplier = 1;
- boolean use_long;
-
- if (is_neg)
- int_buf.insert(0, '-');
+ // now we have to check the suffix, done here after number parsing
+ // or the index will not be updated correctly...
+ boolean isNegativeSuffix = str.endsWith(this.negativeSuffix);
+ boolean isPositiveSuffix = str.endsWith(this.positiveSuffix);
+ boolean positiveEqualsNegative = negativeSuffix.equals(positiveSuffix);
- // Now handle the exponential part if there is one.
- if (exp_buf != null)
+ positiveLen = positiveSuffix.length();
+ negativeLen = negativeSuffix.length();
+
+ if (isNegative && !isNegativeSuffix)
{
- 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;
- }
- }
+ pos.setErrorIndex(i);
+ return null;
}
- else
+ else if (isNegativeSuffix &&
+ !positiveEqualsNegative &&
+ (negativeLen > positiveLen))
{
- 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;
+ isNegative = true;
}
-
- String t = int_buf.toString();
- Number result = null;
- if (use_long)
+ else if (!isPositiveSuffix)
{
- try
- {
- result = new Long (t);
- }
- catch (NumberFormatException x1)
- {
- }
+ pos.setErrorIndex(i);
+ return null;
}
- else
+
+ if (isNegative) number.insert(0, '-');
+
+ pos.setIndex(i);
+
+ // now we handle the return type
+ BigDecimal bigDecimal = new BigDecimal(number.toString());
+ if (this.parseBigDecimal)
+ return bigDecimal;
+
+ // want integer?
+ if (this.parseIntegerOnly)
+ return new Long(bigDecimal.longValue());
+
+ // 3th special case -0.0
+ if (isNegative && (bigDecimal.compareTo(BigDecimal.ZERO) == 0))
+ return new Double(-0.0);
+
+ try
{
- try
- {
- result = new Double (t);
- }
- catch (NumberFormatException x2)
- {
- }
+ BigDecimal integer
+ = bigDecimal.setScale(0, BigDecimal.ROUND_UNNECESSARY);
+ return new Long(integer.longValue());
}
- if (result == null)
+ catch (ArithmeticException e)
{
- pos.setErrorIndex(index);
- return null;
+ return new Double(bigDecimal.doubleValue());
}
-
- 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.
+ *
+ * @param currency The new <code>Currency</code> on the
+ * <code>DecimalFormatSymbols</code>.
*/
public void setCurrency(Currency currency)
{
symbols.setCurrency(currency);
}
-
+
/**
* Sets the symbols used by this instance. This method makes a copy of
* the supplied symbols.
@@ -1164,274 +799,1376 @@ public class DecimalFormat extends NumberFormat
{
symbols = (DecimalFormatSymbols) newSymbols.clone();
}
-
- public void setDecimalSeparatorAlwaysShown (boolean newValue)
+
+ /**
+ * Define if the decimal separator should be always visible or only
+ * visible when needed. This method as effect only on integer values.
+ * Pass <code>true</code> if you want the decimal separator to be
+ * always shown, <code>false</code> otherwise.
+ *
+ * @param newValue true</code> if you want the decimal separator to be
+ * always shown, <code>false</code> otherwise.
+ */
+ public void setDecimalSeparatorAlwaysShown(boolean newValue)
{
decimalSeparatorAlwaysShown = newValue;
}
-
- public void setGroupingSize (int groupSize)
+
+ /**
+ * Sets the number of digits used to group portions of the integer part of
+ * the number. For example, the number <code>123456</code>, with a grouping
+ * size of 3, is rendered <code>123,456</code>.
+ *
+ * @param groupSize The number of digits used while grouping portions
+ * of the integer part of a number.
+ */
+ public void setGroupingSize(int groupSize)
{
groupingSize = (byte) groupSize;
}
-
- public void setMaximumFractionDigits (int newValue)
+
+ /**
+ * Sets the maximum number of digits allowed in the integer
+ * portion of a number to the specified value.
+ * The new value will be the choosen as the minimum between
+ * <code>newvalue</code> and 309. Any value below zero will be
+ * replaced by zero.
+ *
+ * @param newValue The new maximum integer digits value.
+ */
+ public void setMaximumIntegerDigits(int newValue)
{
- super.setMaximumFractionDigits(Math.min(newValue, 340));
+ newValue = (newValue > 0) ? newValue : 0;
+ super.setMaximumIntegerDigits(Math.min(newValue, DEFAULT_INTEGER_DIGITS));
}
-
- public void setMaximumIntegerDigits (int newValue)
+
+ /**
+ * Sets the minimum number of digits allowed in the integer
+ * portion of a number to the specified value.
+ * The new value will be the choosen as the minimum between
+ * <code>newvalue</code> and 309. Any value below zero will be
+ * replaced by zero.
+ *
+ * @param newValue The new minimum integer digits value.
+ */
+ public void setMinimumIntegerDigits(int newValue)
{
- super.setMaximumIntegerDigits(Math.min(newValue, 309));
+ newValue = (newValue > 0) ? newValue : 0;
+ super.setMinimumIntegerDigits(Math.min(newValue, DEFAULT_INTEGER_DIGITS));
}
-
- public void setMinimumFractionDigits (int newValue)
+
+ /**
+ * Sets the maximum number of digits allowed in the fraction
+ * portion of a number to the specified value.
+ * The new value will be the choosen as the minimum between
+ * <code>newvalue</code> and 309. Any value below zero will be
+ * replaced by zero.
+ *
+ * @param newValue The new maximum fraction digits value.
+ */
+ public void setMaximumFractionDigits(int newValue)
{
- super.setMinimumFractionDigits(Math.min(newValue, 340));
+ newValue = (newValue > 0) ? newValue : 0;
+ super.setMaximumFractionDigits(Math.min(newValue, DEFAULT_FRACTION_DIGITS));
}
-
- public void setMinimumIntegerDigits (int newValue)
+
+ /**
+ * Sets the minimum number of digits allowed in the fraction
+ * portion of a number to the specified value.
+ * The new value will be the choosen as the minimum between
+ * <code>newvalue</code> and 309. Any value below zero will be
+ * replaced by zero.
+ *
+ * @param newValue The new minimum fraction digits value.
+ */
+ public void setMinimumFractionDigits(int newValue)
{
- super.setMinimumIntegerDigits(Math.min(newValue, 309));
+ newValue = (newValue > 0) ? newValue : 0;
+ super.setMinimumFractionDigits(Math.min(newValue, DEFAULT_FRACTION_DIGITS));
}
-
- public void setMultiplier (int newValue)
+
+ /**
+ * Sets the multiplier for use in percent and similar formats.
+ * For example, for percent set the multiplier to 100, for permille, set the
+ * miltiplier to 1000.
+ *
+ * @param newValue the new value for multiplier.
+ */
+ public void setMultiplier(int newValue)
{
multiplier = newValue;
}
-
- public void setNegativePrefix (String newValue)
+
+ /**
+ * Sets the negative prefix.
+ *
+ * @param newValue The new negative prefix.
+ */
+ public void setNegativePrefix(String newValue)
{
negativePrefix = newValue;
}
- public void setNegativeSuffix (String newValue)
+ /**
+ * Sets the negative suffix.
+ *
+ * @param newValue The new negative suffix.
+ */
+ public void setNegativeSuffix(String newValue)
{
negativeSuffix = newValue;
}
-
- public void setPositivePrefix (String newValue)
+
+ /**
+ * Sets the positive prefix.
+ *
+ * @param newValue The new positive prefix.
+ */
+ public void setPositivePrefix(String newValue)
{
positivePrefix = newValue;
}
-
- public void setPositiveSuffix (String newValue)
+
+ /**
+ * Sets the new positive suffix.
+ *
+ * @param newValue The new positive suffix.
+ */
+ public void setPositiveSuffix(String newValue)
{
positiveSuffix = newValue;
}
+
+ /**
+ * This method returns a string with the formatting pattern being used
+ * by this object. The string is localized.
+ *
+ * @return A localized <code>String</code> with the formatting pattern.
+ * @see #toPattern()
+ */
+ public String toLocalizedPattern()
+ {
+ return computePattern(this.symbols);
+ }
+
+ /**
+ * This method returns a string with the formatting pattern being used
+ * by this object. The string is not localized.
+ *
+ * @return A <code>String</code> with the formatting pattern.
+ * @see #toLocalizedPattern()
+ */
+ public String toPattern()
+ {
+ return computePattern(nonLocalizedSymbols);
+ }
+
+ /* ***** private methods ***** */
+
+ /**
+ * This is an shortcut helper method used to test if two given strings are
+ * equals.
+ *
+ * @param s1 The first string to test for equality.
+ * @param s2 The second string to test for equality.
+ * @return <code>true</code> if the strings are both <code>null</code> or
+ * equals.
+ */
+ private boolean equals(String s1, String s2)
+ {
+ if (s1 == null || s2 == null)
+ return s1 == s2;
+ return s1.equals(s2);
+ }
+
+
+ /* ****** PATTERN ****** */
+
+ /**
+ * 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());
+ buf.append(syms.getMinusSign());
+ buf.append(syms.getPatternSeparator());
+ buf.append(syms.getPercent());
+ buf.append(syms.getPerMill());
+ buf.append(syms.getZeroDigit());
+ buf.append('\'');
+ buf.append('\u00a4');
+
+ return buf.toString();
+ }
- private void quoteFix(StringBuffer buf, String text, String patChars)
+ /**
+ * Quote special characters as defined by <code>patChars</code> in the
+ * input string.
+ *
+ * @param text
+ * @param patChars
+ * @return A StringBuffer with special characters quoted.
+ */
+ private StringBuffer quoteFix(String text, String patChars)
{
+ StringBuffer buf = new StringBuffer();
+
int len = text.length();
+ char ch;
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);
+ ch = text.charAt(index);
+ if (patChars.indexOf(ch) != -1)
+ {
+ buf.append('\'');
+ buf.append(ch);
+ if (ch != '\'') buf.append('\'');
+ }
+ else
+ {
+ buf.append(ch);
+ }
}
+
+ return buf;
}
-
- private String computePattern(DecimalFormatSymbols syms)
+
+ /**
+ * Returns the format pattern, localized to follow the given
+ * symbols.
+ */
+ private String computePattern(DecimalFormatSymbols symbols)
{
- StringBuffer mainPattern = new StringBuffer ();
+ 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
+ // 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.
+ int _groupingSize = groupingUsed ? groupingSize + 1: groupingSize;
+ int totalDigits = Math.max(minimumIntegerDigits, _groupingSize);
+
+ // if it is not in exponential notiation,
+ // we always have a # prebended
+ if (!useExponentialNotation) mainPattern.append(symbols.getDigit());
+
+ for (int i = 1; i < totalDigits - minimumIntegerDigits; i++)
+ mainPattern.append(symbols.getDigit());
+
+ for (int i = totalDigits - minimumIntegerDigits; i < totalDigits; i++)
+ mainPattern.append(symbols.getZeroDigit());
+
if (groupingUsed)
- mainPattern.insert(mainPattern.length() - groupingSize,
- syms.getGroupingSeparator());
+ {
+ mainPattern.insert(mainPattern.length() - groupingSize,
+ symbols.getGroupingSeparator());
+ }
+
// See if we need decimal info.
- if (minimumFractionDigits > 0 || maximumFractionDigits > 0
- || decimalSeparatorAlwaysShown)
- mainPattern.append(syms.getDecimalSeparator());
+ if (minimumFractionDigits > 0 || maximumFractionDigits > 0 ||
+ decimalSeparatorAlwaysShown)
+ {
+ mainPattern.append(symbols.getDecimalSeparator());
+ }
+
for (int i = 0; i < minimumFractionDigits; ++i)
- mainPattern.append(syms.getZeroDigit());
+ mainPattern.append(symbols.getZeroDigit());
+
for (int i = minimumFractionDigits; i < maximumFractionDigits; ++i)
- mainPattern.append(syms.getDigit());
+ mainPattern.append(symbols.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());
+ mainPattern.append(symbols.getExponential());
+
+ for (int i = 0; i < minExponentDigits; ++i)
+ mainPattern.append(symbols.getZeroDigit());
+
+ if (minExponentDigits == 0)
+ mainPattern.append(symbols.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)
+
+ // save the pattern
+ String pattern = mainPattern.toString();
+
+ // so far we have the pattern itself, now we need to add
+ // the positive and the optional negative prefixes and suffixes
+ String patternChars = patternChars(symbols);
+ mainPattern.insert(0, quoteFix(positivePrefix, patternChars));
+ mainPattern.append(quoteFix(positiveSuffix, patternChars));
+
+ if (hasNegativePrefix)
{
- quoteFix (mainPattern, negativePrefix, patChars);
- mainPattern.append(main);
- quoteFix (mainPattern, negativeSuffix, patChars);
+ mainPattern.append(symbols.getPatternSeparator());
+ mainPattern.append(quoteFix(negativePrefix, patternChars));
+ mainPattern.append(pattern);
+ mainPattern.append(quoteFix(negativeSuffix, patternChars));
}
-
+
+ // finally, return the pattern string
return mainPattern.toString();
}
-
- public String toLocalizedPattern ()
+
+ /* ****** FORMAT PARSING ****** */
+
+ /**
+ * Scan the input string and define a pattern suitable for use
+ * with this decimal format.
+ *
+ * @param pattern
+ * @param symbols
+ */
+ private void applyPatternWithSymbols(String pattern,
+ DecimalFormatSymbols symbols)
{
- return computePattern (symbols);
+ // The pattern string is described by a BNF diagram.
+ // we could use a recursive parser to read and prepare
+ // the string, but this would be too slow and resource
+ // intensive, while this code is quite critical as it is
+ // called always when the class is instantiated and every
+ // time a new pattern is given.
+ // Our strategy is to divide the string into section as given by
+ // the BNF diagram, iterating through the string and setting up
+ // the parameters we need for formatting (which is basicly what
+ // a descendent recursive parser would do - but without recursion).
+ // I'm sure that there are smarter methods to do this.
+
+ // Restore default values. Most of these will be overwritten
+ // but we want to be sure that nothing is left out.
+ setDefaultValues();
+
+ int len = pattern.length();
+ if (len == 0)
+ {
+ // this is another special case...
+ this.minimumIntegerDigits = 1;
+ this.maximumIntegerDigits = DEFAULT_INTEGER_DIGITS;
+ this.minimumFractionDigits = 0;
+ this.maximumFractionDigits = DEFAULT_FRACTION_DIGITS;
+
+ // FIXME: ...and these values may not be valid in all locales
+ this.minExponentDigits = 0;
+ this.showDecimalSeparator = true;
+ this.groupingUsed = true;
+ this.groupingSize = 3;
+
+ return;
+ }
+
+ int start = scanFix(pattern, symbols, 0, true);
+ if (start < len) start = scanNumberInteger(pattern, symbols, start);
+ if (start < len)
+ {
+ start = scanFractionalPortion(pattern, symbols, start);
+ }
+ else
+ {
+ // special case, pattern that ends here does not have a fractional
+ // portion
+ this.minimumFractionDigits = 0;
+ this.maximumFractionDigits = 0;
+ //this.decimalSeparatorAlwaysShown = false;
+ //this.showDecimalSeparator = false;
+ }
+
+ // XXX: this fixes a compatibility test with the RI.
+ // If new uses cases fail, try removing this line first.
+ //if (!this.hasIntegerPattern && !this.hasFractionalPattern)
+ // throw new IllegalArgumentException("No valid pattern found!");
+
+ if (start < len) start = scanExponent(pattern, symbols, start);
+ if (start < len) start = scanFix(pattern, symbols, start, false);
+ if (start < len) scanNegativePattern(pattern, symbols, start);
+
+ if (useExponentialNotation &&
+ (maxIntegerDigitsExponent > minimumIntegerDigits) &&
+ (maxIntegerDigitsExponent > 1))
+ {
+ minimumIntegerDigits = 1;
+ exponentRound = maxIntegerDigitsExponent;
+ }
+
+ if (useExponentialNotation)
+ maximumIntegerDigits = maxIntegerDigitsExponent;
+
+ if (!this.hasFractionalPattern && this.showDecimalSeparator == true)
+ {
+ this.decimalSeparatorAlwaysShown = true;
+ }
}
+
+ /**
+ * Scans for the prefix or suffix portion of the pattern string.
+ * This method handles the positive subpattern of the pattern string.
+ *
+ * @param pattern The pattern string to parse.
+ * @return The position in the pattern string where parsing ended.
+ */
+ private int scanFix(String pattern, DecimalFormatSymbols symbols,
+ int start, boolean prefix)
+ {
+ StringBuffer buffer = new StringBuffer();
+
+ // the number portion is always delimited by one of those
+ // characters
+ char decimalSeparator = symbols.getDecimalSeparator();
+ char patternSeparator = symbols.getPatternSeparator();
+ char groupingSeparator = symbols.getGroupingSeparator();
+ char digit = symbols.getDigit();
+ char zero = symbols.getZeroDigit();
+ char minus = symbols.getMinusSign();
+
+ // other special characters, cached here to avoid method calls later
+ char percent = symbols.getPercent();
+ char permille = symbols.getPerMill();
+
+ String currencySymbol = symbols.getCurrencySymbol();
+
+ boolean quote = false;
+
+ char ch = pattern.charAt(start);
+ if (ch == patternSeparator)
+ {
+ // negative subpattern
+ this.hasNegativePrefix = true;
+ ++start;
+ return start;
+ }
+
+ int len = pattern.length();
+ int i;
+ for (i = start; i < len; i++)
+ {
+ ch = pattern.charAt(i);
+
+ // we are entering into the negative subpattern
+ if (!quote && ch == patternSeparator)
+ {
+ if (this.hasNegativePrefix)
+ {
+ throw new IllegalArgumentException("Invalid pattern found: "
+ + start);
+ }
+
+ this.hasNegativePrefix = true;
+ ++i;
+ break;
+ }
+
+ // this means we are inside the number portion
+ if (!quote &&
+ (ch == minus || ch == digit || ch == zero ||
+ ch == groupingSeparator))
+ break;
+
+ if (!quote && ch == decimalSeparator)
+ {
+ this.showDecimalSeparator = true;
+ break;
+ }
+ else if (quote && ch != '\'')
+ {
+ buffer.append(ch);
+ continue;
+ }
+
+ if (ch == '\u00A4')
+ {
+ // CURRENCY
+ currencySymbol = symbols.getCurrencySymbol();
+
+ // if \u00A4 is doubled, we use the international currency symbol
+ if (i < len && pattern.charAt(i + 1) == '\u00A4')
+ {
+ currencySymbol = symbols.getInternationalCurrencySymbol();
+ i++;
+ }
+
+ this.useCurrencySeparator = true;
+ buffer.append(currencySymbol);
+ }
+ else if (ch == percent)
+ {
+ // PERCENT
+ this.multiplier = 100;
+ buffer.append(ch);
+ }
+ else if (ch == permille)
+ {
+ // PERMILLE
+ this.multiplier = 1000;
+ buffer.append(ch);
+ }
+ else if (ch == '\'')
+ {
+ // QUOTE
+ if (i < len && pattern.charAt(i + 1) == '\'')
+ {
+ // we need to add ' to the buffer
+ buffer.append(ch);
+ i++;
+ }
+ else
+ {
+ quote = !quote;
+ continue;
+ }
+ }
+ else
+ {
+ buffer.append(ch);
+ }
+ }
+
+ if (prefix)
+ {
+ this.positivePrefix = buffer.toString();
+ this.negativePrefix = minus + "" + positivePrefix;
+ }
+ else
+ {
+ this.positiveSuffix = buffer.toString();
+ }
+
+ return i;
+ }
+
+ /**
+ * Scan the given string for number patterns, starting
+ * from <code>start</code>.
+ * This method searches the integer part of the pattern only.
+ *
+ * @param pattern The pattern string to parse.
+ * @param start The starting parse position in the string.
+ * @return The position in the pattern string where parsing ended,
+ * counted from the beginning of the string (that is, 0).
+ */
+ private int scanNumberInteger(String pattern, DecimalFormatSymbols symbols,
+ int start)
+ {
+ char digit = symbols.getDigit();
+ char zero = symbols.getZeroDigit();
+ char groupingSeparator = symbols.getGroupingSeparator();
+ char decimalSeparator = symbols.getDecimalSeparator();
+ char exponent = symbols.getExponential();
+ char patternSeparator = symbols.getPatternSeparator();
+
+ // count the number of zeroes in the pattern
+ // this number defines the minum digits in the integer portion
+ int zeros = 0;
+
+ // count the number of digits used in grouping
+ int _groupingSize = 0;
+
+ this.maxIntegerDigitsExponent = 0;
+
+ boolean intPartTouched = false;
+
+ char ch;
+ int len = pattern.length();
+ int i;
+ for (i = start; i < len; i++)
+ {
+ ch = pattern.charAt(i);
+
+ // break on decimal separator or exponent or pattern separator
+ if (ch == decimalSeparator || ch == exponent)
+ break;
+
+ if (this.hasNegativePrefix && ch == patternSeparator)
+ throw new IllegalArgumentException("Invalid pattern found: "
+ + start);
+
+ if (ch == digit)
+ {
+ // in our implementation we could relax this strict
+ // requirement, but this is used to keep compatibility with
+ // the RI
+ if (zeros > 0) throw new
+ IllegalArgumentException("digit mark following zero in " +
+ "positive subpattern, not allowed. Position: " + i);
+
+ _groupingSize++;
+ intPartTouched = true;
+ this.maxIntegerDigitsExponent++;
+ }
+ else if (ch == zero)
+ {
+ zeros++;
+ _groupingSize++;
+ this.maxIntegerDigitsExponent++;
+ }
+ else if (ch == groupingSeparator)
+ {
+ this.groupingSeparatorInPattern = true;
+ this.groupingUsed = true;
+ _groupingSize = 0;
+ }
+ else
+ {
+ // any other character not listed above
+ // means we are in the suffix portion
+ break;
+ }
+ }
+
+ if (groupingSeparatorInPattern) this.groupingSize = (byte) _groupingSize;
+ this.minimumIntegerDigits = zeros;
+
+ // XXX: compatibility code with the RI: the number of minimum integer
+ // digits is at least one when maximumIntegerDigits is more than zero
+ if (intPartTouched && this.maximumIntegerDigits > 0 &&
+ this.minimumIntegerDigits == 0)
+ this.minimumIntegerDigits = 1;
- public String toPattern ()
+ return i;
+ }
+
+ /**
+ * Scan the given string for number patterns, starting
+ * from <code>start</code>.
+ * This method searches the fractional part of the pattern only.
+ *
+ * @param pattern The pattern string to parse.
+ * @param start The starting parse position in the string.
+ * @return The position in the pattern string where parsing ended,
+ * counted from the beginning of the string (that is, 0).
+ */
+ private int scanFractionalPortion(String pattern,
+ DecimalFormatSymbols symbols,
+ int start)
+ {
+ char digit = symbols.getDigit();
+ char zero = symbols.getZeroDigit();
+ char groupingSeparator = symbols.getGroupingSeparator();
+ char decimalSeparator = symbols.getDecimalSeparator();
+ char exponent = symbols.getExponential();
+ char patternSeparator = symbols.getPatternSeparator();
+
+ // first character needs to be '.' otherwise we are not parsing the
+ // fractional portion
+ char ch = pattern.charAt(start);
+ if (ch != decimalSeparator)
+ {
+ this.minimumFractionDigits = 0;
+ this.maximumFractionDigits = 0;
+ return start;
+ }
+
+ ++start;
+
+ this.hasFractionalPattern = true;
+
+ this.minimumFractionDigits = 0;
+ int digits = 0;
+
+ int len = pattern.length();
+ int i;
+ for (i = start; i < len; i++)
+ {
+ ch = pattern.charAt(i);
+
+ // we hit the exponential or negative subpattern
+ if (ch == exponent || ch == patternSeparator)
+ break;
+
+ // pattern error
+ if (ch == groupingSeparator || ch == decimalSeparator) throw new
+ IllegalArgumentException("unexpected character '" + ch + "' " +
+ "in fractional subpattern. Position: " + i);
+
+ if (ch == digit)
+ {
+ digits++;
+ }
+ else if (ch == zero)
+ {
+ if (digits > 0) throw new
+ IllegalArgumentException("digit mark following zero in " +
+ "positive subpattern, not allowed. Position: " + i);
+
+ this.minimumFractionDigits++;
+ }
+ else
+ {
+ // we are in the suffix section of pattern
+ break;
+ }
+ }
+
+ if (i == start) this.hasFractionalPattern = false;
+
+ this.maximumFractionDigits = this.minimumFractionDigits + digits;
+ this.showDecimalSeparator = true;
+
+ return i;
+ }
+
+ /**
+ * Scan the given string for number patterns, starting
+ * from <code>start</code>.
+ * This method searches the expoential part of the pattern only.
+ *
+ * @param pattern The pattern string to parse.
+ * @param start The starting parse position in the string.
+ * @return The position in the pattern string where parsing ended,
+ * counted from the beginning of the string (that is, 0).
+ */
+ private int scanExponent(String pattern, DecimalFormatSymbols symbols,
+ int start)
{
- return computePattern (nonLocalizedSymbols);
+ char digit = symbols.getDigit();
+ char zero = symbols.getZeroDigit();
+ char groupingSeparator = symbols.getGroupingSeparator();
+ char decimalSeparator = symbols.getDecimalSeparator();
+ char exponent = symbols.getExponential();
+
+ char ch = pattern.charAt(start);
+
+ if (ch == decimalSeparator)
+ {
+ // ignore dots
+ ++start;
+ }
+
+ if (ch != exponent)
+ {
+ this.useExponentialNotation = false;
+ return start;
+ }
+
+ ++start;
+
+ this.minExponentDigits = 0;
+
+ int len = pattern.length();
+ int i;
+ for (i = start; i < len; i++)
+ {
+ ch = pattern.charAt(i);
+
+ if (ch == groupingSeparator || ch == decimalSeparator ||
+ ch == digit || ch == exponent) throw new
+ IllegalArgumentException("unexpected character '" + ch + "' " +
+ "in exponential subpattern. Position: " + i);
+
+ if (ch == zero)
+ {
+ this.minExponentDigits++;
+ }
+ else
+ {
+ // any character other than zero is an exit point
+ break;
+ }
+ }
+
+ this.useExponentialNotation = true;
+
+ return i;
+ }
+
+ /**
+ * Scan the given string for number patterns, starting
+ * from <code>start</code>.
+ * This method searches the negative part of the pattern only and scan
+ * throught the end of the string.
+ *
+ * @param pattern The pattern string to parse.
+ * @param start The starting parse position in the string.
+ */
+ private void scanNegativePattern(String pattern, DecimalFormatSymbols symbols,
+ int start)
+ {
+ StringBuffer buffer = new StringBuffer();
+
+ // the number portion is always delimited by one of those
+ // characters
+ char decimalSeparator = symbols.getDecimalSeparator();
+ char patternSeparator = symbols.getPatternSeparator();
+ char groupingSeparator = symbols.getGroupingSeparator();
+ char digit = symbols.getDigit();
+ char zero = symbols.getZeroDigit();
+ char minus = symbols.getMinusSign();
+
+ // other special charcaters, cached here to avoid method calls later
+ char percent = symbols.getPercent();
+ char permille = symbols.getPerMill();
+
+ String CURRENCY_SYMBOL = symbols.getCurrencySymbol();
+ String currencySymbol = CURRENCY_SYMBOL;
+
+ boolean quote = false;
+ boolean prefixDone = false;
+
+ int len = pattern.length();
+ if (len > 0) this.hasNegativePrefix = true;
+
+ char ch = pattern.charAt(start);
+ if (ch == patternSeparator)
+ {
+ // no pattern separator in the negative pattern
+ if ((start + 1) > len) throw new
+ IllegalArgumentException("unexpected character '" + ch + "' " +
+ "in negative subpattern.");
+ start++;
+ }
+
+ int i;
+ for (i = start; i < len; i++)
+ {
+ ch = pattern.charAt(i);
+
+ // this means we are inside the number portion
+ if (!quote &&
+ (ch == digit || ch == zero || ch == decimalSeparator ||
+ ch == patternSeparator || ch == groupingSeparator))
+ {
+ if (!prefixDone)
+ {
+ this.negativePrefix = buffer.toString();
+ buffer.delete(0, buffer.length());
+ prefixDone = true;
+ }
+ }
+ else if (ch == minus)
+ {
+ buffer.append(ch);
+ }
+ else if (quote && ch != '\'')
+ {
+ buffer.append(ch);
+ }
+ else if (ch == '\u00A4')
+ {
+ // CURRENCY
+ currencySymbol = CURRENCY_SYMBOL;
+
+ // if \u00A4 is doubled, we use the international currency symbol
+ if (i < len && pattern.charAt(i + 1) == '\u00A4')
+ {
+ currencySymbol = symbols.getInternationalCurrencySymbol();
+ i = i + 2;
+ }
+
+ // FIXME: not sure about this, the specs says that we only have to
+ // change prefix and suffix, so leave it as commented
+ // unless in case of bug report/errors
+ //this.useCurrencySeparator = true;
+
+ buffer.append(currencySymbol);
+ }
+ else if (ch == percent)
+ {
+ // PERCENT
+ this.negativePatternMultiplier = 100;
+ buffer.append(ch);
+ }
+ else if (ch == permille)
+ {
+ // PERMILLE
+ this.negativePatternMultiplier = 1000;
+ buffer.append(ch);
+ }
+ else if (ch == '\'')
+ {
+ // QUOTE
+ if (i < len && pattern.charAt(i + 1) == '\'')
+ {
+ // we need to add ' to the buffer
+ buffer.append(ch);
+ i++;
+ }
+ else
+ {
+ quote = !quote;
+ }
+ }
+ else if (ch == patternSeparator)
+ {
+ // no pattern separator in the negative pattern
+ throw new IllegalArgumentException("unexpected character '" + ch +
+ "' in negative subpattern.");
+ }
+ else
+ {
+ buffer.append(ch);
+ }
+ }
+
+ if (prefixDone)
+ this.negativeSuffix = buffer.toString();
+ else
+ this.negativePrefix = buffer.toString();
}
+
+ /* ****** FORMATTING ****** */
+
+ /**
+ * Handles the real formatting.
+ *
+ * We use a BigDecimal to format the number without precision loss.
+ * All the rounding is done by methods in BigDecimal.
+ * The <code>isLong</code> parameter is used to determine if we are
+ * formatting a long or BigInteger. In this case, we avoid to format
+ * the fractional part of the number (unless specified otherwise in the
+ * format string) that would consist only of a 0 digit.
+ *
+ * @param number A BigDecimal representation fo the input number.
+ * @param dest The destination buffer.
+ * @param isLong A boolean that indicates if this BigDecimal is a real
+ * decimal or an integer.
+ * @param fieldPos Use to keep track of the formatting position.
+ */
+ private void formatInternal(BigDecimal number, boolean isLong,
+ StringBuffer dest, FieldPosition fieldPos)
+ {
+ int _multiplier = this.multiplier;
+
+ // used to track attribute starting position for each attribute
+ int attributeStart = -1;
+
+ // now get the sign this will be used by the special case Inifinity
+ // and by the normal cases.
+ boolean isNegative = (number.signum() < 0) ? true : false;
+ if (isNegative)
+ {
+ attributeStart = dest.length();
+
+ // append the negative prefix to the string
+ dest.append(negativePrefix);
+
+ // once got the negative prefix, we can use
+ // the absolute value.
+ number = number.abs();
+
+ _multiplier = negativePatternMultiplier;
+
+ addAttribute(Field.SIGN, attributeStart, dest.length());
+ }
+ else
+ {
+ // not negative, use the positive prefix
+ dest.append(positivePrefix);
+ }
+
+ // these are used ot update the field position
+ int beginIndexInt = dest.length();
+ int endIndexInt = 0;
+ int beginIndexFract = 0;
+ int endIndexFract = 0;
+
+ // compute the multiplier to use with percent and similar
+ number = number.multiply(new BigDecimal(_multiplier));
+
+ // XXX: special case, not sure if it belongs here or if it is
+ // correct at all. There may be other special cases as well
+ // these should be handled in the format string parser.
+ if (this.maximumIntegerDigits == 0 && this.maximumFractionDigits == 0)
+ {
+ number = BigDecimal.ZERO;
+ this.maximumIntegerDigits = 1;
+ this.minimumIntegerDigits = 1;
+ }
+
+ // get the absolute number
+ number = number.abs();
- private static final int MAXIMUM_INTEGER_DIGITS = 309;
+ // the scaling to use while formatting this number
+ int scale = this.maximumFractionDigits;
+
+ // this is the actual number we will use
+ // it is corrected later on to handle exponential
+ // notation, if needed
+ long exponent = 0;
+
+ // are we using exponential notation?
+ if (this.useExponentialNotation)
+ {
+ exponent = getExponent(number);
+ number = number.movePointLeft((int) exponent);
+
+ // FIXME: this makes the test ##.###E0 to pass,
+ // but all all the other tests to fail...
+ // this should be really something like
+ // min + max - what is already shown...
+ //scale = this.minimumIntegerDigits + this.maximumFractionDigits;
+ }
+
+ // round the number to the nearest neighbor
+ number = number.setScale(scale, BigDecimal.ROUND_HALF_EVEN);
- // 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;
+ // now get the integer and fractional part of the string
+ // that will be processed later
+ String plain = number.toPlainString();
+
+ String intPart = null;
+ String fractPart = null;
+
+ // remove - from the integer part, this is needed as
+ // the Narrowing Primitive Conversions algorithm used may loose
+ // information about the sign
+ int minusIndex = plain.lastIndexOf('-', 0);
+ if (minusIndex > -1) plain = plain.substring(minusIndex + 1);
+
+ // strip the decimal portion
+ int dot = plain.indexOf('.');
+ if (dot > -1)
+ {
+ intPart = plain.substring(0, dot);
+ dot++;
+
+ if (useExponentialNotation)
+ fractPart = plain.substring(dot, dot + scale);
+ else
+ fractPart = plain.substring(dot);
+ }
+ else
+ {
+ intPart = plain;
+ }
+
+ // used in various places later on
+ int intPartLen = intPart.length();
+ endIndexInt = intPartLen;
+
+ // if the number of digits in our intPart is not greater than the
+ // minimum we have to display, we append zero to the destination
+ // buffer before adding the integer portion of the number.
+ int zeroes = minimumIntegerDigits - intPartLen;
+ if (zeroes > 0)
+ {
+ attributeStart = Math.max(dest.length() - 1, 0);
+ appendZero(dest, zeroes, minimumIntegerDigits);
+ }
- private void readObject(ObjectInputStream stream)
- throws IOException, ClassNotFoundException
- {
- stream.defaultReadObject();
- if (serialVersionOnStream < 1)
+ if (this.useExponentialNotation)
+ {
+ // For exponential numbers, the significant in mantissa are
+ // the sum of the minimum integer and maximum fraction
+ // digits, and does not take into account the maximun integer
+ // digits to display.
+ // This methods takes care of the integer portion of the mantissa.
+ if (attributeStart < 0)
+ attributeStart = Math.max(dest.length() - 1, 0);
+ appendDigit(intPart, dest, this.groupingUsed);
+ }
+ else
{
- useExponentialNotation = false;
- serialVersionOnStream = 1;
+ // non exponential notation
+ intPartLen = intPart.length();
+ int canary = Math.min(intPartLen, this.maximumIntegerDigits);
+
+ // remove from the string the number in excess
+ // use only latest digits
+ intPart = intPart.substring(intPartLen - canary);
+ endIndexInt = intPart.length() + 1;
+
+ // append it
+ if (maximumIntegerDigits > 0 &&
+ !(this.minimumIntegerDigits == 0 &&
+ intPart.compareTo(String.valueOf(symbols.getZeroDigit())) == 0))
+ {
+ if (attributeStart < 0)
+ attributeStart = Math.max(dest.length() - 1, 0);
+ appendDigit(intPart, dest, this.groupingUsed);
+ }
+ }
+
+ // add the INTEGER attribute
+ addAttribute(Field.INTEGER, attributeStart, dest.length());
+
+ if (this.decimalSeparatorAlwaysShown ||
+ ((!isLong || this.useExponentialNotation)
+ && this.showDecimalSeparator &&
+ this.maximumFractionDigits > 0))
+ {
+ attributeStart = dest.length();
+
+ if (this.useCurrencySeparator)
+ dest.append(symbols.getMonetaryDecimalSeparator());
+ else
+ dest.append(symbols.getDecimalSeparator());
+
+ // add the INTEGER attribute
+ addAttribute(Field.DECIMAL_SEPARATOR, attributeStart, dest.length());
+ }
+
+ // now handle the fraction portion of the number
+ if ((!isLong || this.useExponentialNotation)
+ && this.maximumFractionDigits > 0)
+ {
+ attributeStart = dest.length();
+ beginIndexFract = attributeStart;
+
+ int digits = this.minimumFractionDigits;
+
+ if (this.useExponentialNotation)
+ {
+ digits = (this.minimumIntegerDigits + this.minimumFractionDigits)
+ - dest.length();
+ if (digits < 0) digits = 0;
+ }
+
+ fractPart = adjustTrailingZeros(fractPart, digits);
+ appendDigit(fractPart, dest, false);
+
+ endIndexFract = dest.length();
+ addAttribute(Field.FRACTION, attributeStart, endIndexFract);
+ }
+
+ // and the exponent
+ if (this.useExponentialNotation)
+ {
+ attributeStart = dest.length();
+
+ dest.append(symbols.getExponential());
+
+ addAttribute(Field.EXPONENT_SYMBOL, attributeStart, dest.length());
+ attributeStart = dest.length();
+
+ if (exponent < 0)
+ {
+ dest.append(symbols.getMinusSign());
+ exponent = -exponent;
+
+ addAttribute(Field.EXPONENT_SIGN, attributeStart, dest.length());
+ }
+
+ attributeStart = dest.length();
+
+ String exponentString = String.valueOf(exponent);
+ int exponentLength = exponentString.length();
+
+ for (int i = 0; i < minExponentDigits - exponentLength; i++)
+ dest.append(symbols.getZeroDigit());
+
+ for (int i = 0; i < exponentLength; ++i)
+ dest.append(exponentString.charAt(i));
+
+ addAttribute(Field.EXPONENT, attributeStart, dest.length());
+ }
+
+ // now include the suffixes...
+ if (isNegative)
+ {
+ dest.append(negativeSuffix);
+ }
+ else
+ {
+ dest.append(positiveSuffix);
}
+
+ // ...update field position, if needed, and return...
+ if ((fieldPos.getField() == INTEGER_FIELD ||
+ fieldPos.getFieldAttribute() == NumberFormat.Field.INTEGER))
+ {
+ fieldPos.setBeginIndex(beginIndexInt);
+ fieldPos.setEndIndex(endIndexInt);
+ }
+
+ if ((fieldPos.getField() == FRACTION_FIELD ||
+ fieldPos.getFieldAttribute() == NumberFormat.Field.FRACTION))
+ {
+ fieldPos.setBeginIndex(beginIndexFract);
+ fieldPos.setEndIndex(endIndexFract);
+ }
}
- // The locale-independent pattern symbols happen to be the same as
- // the US symbols.
- private static final DecimalFormatSymbols nonLocalizedSymbols
- = new DecimalFormatSymbols (Locale.US);
+ /**
+ * Append to <code>dest</code>the give number of zeros.
+ * Grouping is added if needed.
+ * The integer totalDigitCount defines the total number of digits
+ * of the number to which we are appending zeroes.
+ */
+ private void appendZero(StringBuffer dest, int zeroes, int totalDigitCount)
+ {
+ char ch = symbols.getZeroDigit();
+ char gSeparator = symbols.getGroupingSeparator();
+
+ int i = 0;
+ int gPos = totalDigitCount;
+ for (i = 0; i < zeroes; i++, gPos--)
+ {
+ if (this.groupingSeparatorInPattern &&
+ (this.groupingUsed && this.groupingSize != 0) &&
+ (gPos % groupingSize == 0 && i > 0))
+ dest.append(gSeparator);
+
+ dest.append(ch);
+ }
+
+ // special case, that requires adding an additional separator
+ if (this.groupingSeparatorInPattern &&
+ (this.groupingUsed && this.groupingSize != 0) &&
+ (gPos % groupingSize == 0))
+ dest.append(gSeparator);
+ }
+
+ /**
+ * Append src to <code>dest</code>.
+ *
+ * Grouping is added if <code>groupingUsed</code> is set
+ * to <code>true</code>.
+ */
+ private void appendDigit(String src, StringBuffer dest,
+ boolean groupingUsed)
+ {
+ int zero = symbols.getZeroDigit() - '0';
+
+ int ch;
+ char gSeparator = symbols.getGroupingSeparator();
+
+ int len = src.length();
+ for (int i = 0, gPos = len; i < len; i++, gPos--)
+ {
+ ch = src.charAt(i);
+ if (groupingUsed && this.groupingSize != 0 &&
+ gPos % groupingSize == 0 && i > 0)
+ dest.append(gSeparator);
+ dest.append((char) (zero + ch));
+ }
+ }
+
/**
- * <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.
+ * Calculate the exponent to use if eponential notation is used.
+ * The exponent is calculated as a power of ten.
+ * <code>number</code> should be positive, if is zero, or less than zero,
+ * zero is returned.
*/
- private String substituteCurrency(String string, double number)
+ private long getExponent(BigDecimal number)
{
- int index;
- int length;
- char currentChar;
- StringBuffer buf;
+ long exponent = 0;
- index = 0;
- length = string.length();
- buf = new StringBuffer();
+ if (number.signum() > 0)
+ {
+ double _number = number.doubleValue();
+ exponent = (long) Math.floor (Math.log10(_number));
+
+ // get the right value for the exponent
+ exponent = exponent - (exponent % this.exponentRound);
+
+ // if the minimumIntegerDigits is more than zero
+ // we display minimumIntegerDigits of digits.
+ // so, for example, if minimumIntegerDigits == 2
+ // and the actual number is 0.123 it will be
+ // formatted as 12.3E-2
+ // this means that the exponent have to be shifted
+ // to the correct value.
+ if (minimumIntegerDigits > 0)
+ exponent -= minimumIntegerDigits - 1;
+ }
+
+ return exponent;
+ }
+
+ /**
+ * Remove contiguos zeros from the end of the <code>src</code> string,
+ * if src contains more than <code>minimumDigits</code> digits.
+ * if src contains less that <code>minimumDigits</code>,
+ * then append zeros to the string.
+ *
+ * Only the first block of zero digits is removed from the string
+ * and only if they fall in the src.length - minimumDigits
+ * portion of the string.
+ *
+ * @param src The string with the correct number of zeros.
+ */
+ private String adjustTrailingZeros(String src, int minimumDigits)
+ {
+ int len = src.length();
+ String result;
- while (index < length)
+ // remove all trailing zero
+ if (len > minimumDigits)
{
- 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;
- }
+ int zeros = 0;
+ for (int i = len - 1; i > minimumDigits; i--)
+ {
+ if (src.charAt(i) == '0')
+ ++zeros;
+ else
+ break;
+ }
+ result = src.substring(0, len - zeros);
}
- return buf.toString();
+ else
+ {
+ char zero = symbols.getZeroDigit();
+ StringBuffer _result = new StringBuffer(src);
+ for (int i = len; i < minimumDigits; i++)
+ {
+ _result.append(zero);
+ }
+ result = _result.toString();
+ }
+
+ return result;
+ }
+
+ /**
+ * Adds an attribute to the attributes list.
+ *
+ * @param field
+ * @param begin
+ * @param end
+ */
+ private void addAttribute(Field field, int begin, int end)
+ {
+ /*
+ * This method and its implementation derives directly from the
+ * ICU4J (http://icu.sourceforge.net/) library, distributed under MIT/X.
+ */
+
+ FieldPosition pos = new FieldPosition(field);
+ pos.setBeginIndex(begin);
+ pos.setEndIndex(end);
+ attributes.add(pos);
}
+ /**
+ * Sets the default values for the various properties in this DecimaFormat.
+ */
+ private void setDefaultValues()
+ {
+ // Maybe we should add these values to the message bundle and take
+ // the most appropriate for them for any locale.
+ // Anyway, these seem to be good values for a default in most languages.
+ // Note that most of these will change based on the format string.
+
+ this.negativePrefix = String.valueOf(symbols.getMinusSign());
+ this.negativeSuffix = "";
+ this.positivePrefix = "";
+ this.positiveSuffix = "";
+
+ this.multiplier = 1;
+ this.negativePatternMultiplier = 1;
+ this.exponentRound = 1;
+
+ this.hasNegativePrefix = false;
+
+ this.minimumIntegerDigits = 1;
+ this.maximumIntegerDigits = DEFAULT_INTEGER_DIGITS;
+ this.minimumFractionDigits = 0;
+ this.maximumFractionDigits = DEFAULT_FRACTION_DIGITS;
+ this.minExponentDigits = 0;
+
+ this.groupingSize = 0;
+
+ this.decimalSeparatorAlwaysShown = false;
+ this.showDecimalSeparator = false;
+ this.useExponentialNotation = false;
+ this.groupingUsed = false;
+ this.groupingSeparatorInPattern = false;
+
+ this.useCurrencySeparator = false;
+
+ this.hasFractionalPattern = false;
+ }
}