/* 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. */
/*
* 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.lang.CPStringBuilder;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Currency;
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
* 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 NumberFormat
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
{
/** 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 decimalSeparatorAlwaysShown
,
* 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 exponentRound
*/
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 DecimalFormat
which uses the default
* pattern and symbols.
*/
public DecimalFormat()
{
this ("#,##0.###");
}
/**
* Constructs a DecimalFormat
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 DecimalFormat
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();
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);
}
/**
* Apply the given localized pattern 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)
{
applyPatternWithSymbols(pattern, nonLocalizedSymbols);
}
public Object clone()
{
DecimalFormat c = (DecimalFormat) super.clone();
c.symbols = (DecimalFormatSymbols) symbols.clone();
return c;
}
/**
* Tests this instance for equality with an arbitrary object. This method
* returns true
if:
*
obj
is not null
;obj
is an instance of DecimalFormat
;obj
have the same attributes;null
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
&& groupingSeparatorInPattern == dup.groupingSeparatorInPattern
&& groupingSize == dup.groupingSize
&& multiplier == dup.multiplier
&& useExponentialNotation == dup.useExponentialNotation
&& minExponentDigits == dup.minExponentDigits
&& minimumIntegerDigits == dup.minimumIntegerDigits
&& 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)
&& equals(positiveSuffix, dup.positiveSuffix)
&& symbols.equals(dup.symbols));
}
/**
* Returns a hash code for this object.
*
* @return A hash code.
*/
public int hashCode()
{
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 final StringBuffer format(Object obj, StringBuffer sbuf, FieldPosition pos)
{
if (obj instanceof BigInteger)
{
BigDecimal decimal = new BigDecimal((BigInteger) obj);
formatInternal(decimal, true, sbuf, pos);
return sbuf;
}
else if (obj instanceof BigDecimal)
{
formatInternal((BigDecimal) obj, true, sbuf, pos);
return sbuf;
}
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 dest
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))
{
// 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);
}
}
else if (Double.isInfinite(number))
{
// 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
{
// get the number as a BigDecimal
BigDecimal bigDecimal = new BigDecimal(String.valueOf(number));
formatInternal(bigDecimal, false, dest, fieldPos);
}
return dest;
}
/**
* 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)
{
BigDecimal bigDecimal = new BigDecimal(String.valueOf(number));
formatInternal(bigDecimal, true, dest, fieldPos);
return dest;
}
/**
* Return an AttributedCharacterIterator
as a result of
* the formatting of the passed {@link Object}.
*
* @return An {@link AttributedCharacterIterator}.
* @throws NullPointerException if value is null
.
* @throws IllegalArgumentException if value is not an instance of
* {@link Number}.
*/
public AttributedCharacterIterator formatToCharacterIterator(Object value)
{
/*
* This method implementation derives directly from the
* ICU4J (http://icu.sourceforge.net/) library, distributed under MIT/X.
*/
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));
AttributedString as = new AttributedString(text.toString());
// add NumberFormat field attributes to the AttributedString
for (int i = 0; i < attributes.size(); i++)
{
FieldPosition pos = (FieldPosition) attributes.get(i);
Format.Field attribute = pos.getFieldAttribute();
as.addAttribute(attribute, attribute, pos.getBeginIndex(),
pos.getEndIndex());
}
// return the CharacterIterator from AttributedString
return as.getIterator();
}
/**
* Returns the currency corresponding to the currency symbol stored
* in the instance of DecimalFormatSymbols
used by this
* DecimalFormat
.
*
* @return A new instance of Currency
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();
}
/**
* 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;
}
/**
* Gets the multiplier used in percent and similar formats.
*
* @return The multiplier used in percent and similar formats.
*/
public int getMultiplier()
{
return multiplier;
}
/**
* Gets the negative prefix.
*
* @return The negative prefix.
*/
public String getNegativePrefix()
{
return negativePrefix;
}
/**
* Gets the negative suffix.
*
* @return The negative suffix.
*/
public String getNegativeSuffix()
{
return negativeSuffix;
}
/**
* Gets the positive prefix.
*
* @return The positive prefix.
*/
public String getPositivePrefix()
{
return positivePrefix;
}
/**
* Gets the positive suffix.
*
* @return The positive suffix.
*/
public String getPositiveSuffix()
{
return positiveSuffix;
}
public boolean isDecimalSeparatorAlwaysShown()
{
return decimalSeparatorAlwaysShown;
}
/**
* Define if parse(java.lang.String, java.text.ParsePosition)
* should return a {@link BigDecimal} or not.
*
* @param newValue
*/
public void setParseBigDecimal(boolean newValue)
{
this.parseBigDecimal = newValue;
}
/**
* Returns true
if
* parse(java.lang.String, java.text.ParsePosition)
returns
* a BigDecimal
, false
otherwise.
* The default return value for this method is false
.
*
* @return true
if the parse method returns a {@link BigDecimal},
* false
otherwise.
* @since 1.5
* @see #setParseBigDecimal(boolean)
*/
public boolean isParseBigDecimal()
{
return this.parseBigDecimal;
}
/**
* This method parses the specified string into a Number
.
*
* The parsing starts at pos
, which is updated as the parser
* consume characters in the passed string.
* On error, the Position
object index is not updated, while
* error position is set appropriately, an null
is returned.
*
* @param str The string to parse.
* @param pos The desired ParsePosition
.
*
* @return The parsed Number
*/
public Number parse(String str, ParsePosition pos)
{
// a special values before anything else
// NaN
if (str.contains(this.symbols.getNaN()))
return Double.valueOf(Double.NaN);
// this will be our final number
CPStringBuilder number = new CPStringBuilder();
// 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)
{
start += _negativePrefix.length();
isPositive = false;
}
else if (isPositive)
{
start += positivePrefix.length();
isNegative = false;
}
else
{
pos.setErrorIndex(start);
return null;
}
// 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)
stop += minExponentDigits + 1;
boolean inExponent = false;
// correct the size of the end parsing flag
int len = str.length();
if (len < stop) stop = len;
char groupingSeparator = symbols.getGroupingSeparator();
int i = start;
while (i < stop)
{
char ch = str.charAt(i);
i++;
if (ch >= zero && ch <= (zero + 9))
{
number.append(ch);
}
else if (this.parseIntegerOnly)
{
i--;
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
{
i--;
break;
}
}
else
{
if (!groupingUsed || ch != groupingSeparator)
{
i--;
break;
}
}
}
// 2nd special case: infinity
// XXX: need to be tested
if (str.contains(symbols.getInfinity()))
{
int inf = str.indexOf(symbols.getInfinity());
pos.setIndex(inf);
// FIXME: ouch, this is really ugly and lazy code...
if (this.parseBigDecimal)
{
if (isNegative)
return BigDecimal.valueOf(Double.NEGATIVE_INFINITY);
return BigDecimal.valueOf(Double.POSITIVE_INFINITY);
}
if (isNegative)
return Double.valueOf(Double.NEGATIVE_INFINITY);
return Double.valueOf(Double.POSITIVE_INFINITY);
}
// no number...
if (i == start || number.length() == 0)
{
pos.setErrorIndex(i);
return null;
}
// now we have to check the suffix, done here after number parsing
// or the index will not be updated correctly...
boolean hasNegativeSuffix = str.endsWith(this.negativeSuffix);
boolean hasPositiveSuffix = str.endsWith(this.positiveSuffix);
boolean positiveEqualsNegative = negativeSuffix.equals(positiveSuffix);
positiveLen = positiveSuffix.length();
negativeLen = negativeSuffix.length();
if (isNegative && !hasNegativeSuffix)
{
pos.setErrorIndex(i);
return null;
}
else if (hasNegativeSuffix &&
!positiveEqualsNegative &&
(negativeLen > positiveLen))
{
isNegative = true;
}
else if (!hasPositiveSuffix)
{
pos.setErrorIndex(i);
return null;
}
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 Long.valueOf(bigDecimal.longValue());
// 3th special case -0.0
if (isNegative && (bigDecimal.compareTo(BigDecimal.ZERO) == 0))
return Double.valueOf(-0.0);
try
{
BigDecimal integer
= bigDecimal.setScale(0, BigDecimal.ROUND_UNNECESSARY);
return Long.valueOf(integer.longValue());
}
catch (ArithmeticException e)
{
return Double.valueOf(bigDecimal.doubleValue());
}
}
/**
* Sets the Currency
on the
* DecimalFormatSymbols
used, which also sets the
* currency symbols on those symbols.
*
* @param currency The new Currency
on the
* DecimalFormatSymbols
.
*/
public void setCurrency(Currency currency)
{
Currency current = symbols.getCurrency();
if (current != currency)
{
String oldSymbol = symbols.getCurrencySymbol();
int len = oldSymbol.length();
symbols.setCurrency(currency);
String newSymbol = symbols.getCurrencySymbol();
int posPre = positivePrefix.indexOf(oldSymbol);
if (posPre != -1)
positivePrefix = positivePrefix.substring(0, posPre) +
newSymbol + positivePrefix.substring(posPre+len);
int negPre = negativePrefix.indexOf(oldSymbol);
if (negPre != -1)
negativePrefix = negativePrefix.substring(0, negPre) +
newSymbol + negativePrefix.substring(negPre+len);
int posSuf = positiveSuffix.indexOf(oldSymbol);
if (posSuf != -1)
positiveSuffix = positiveSuffix.substring(0, posSuf) +
newSymbol + positiveSuffix.substring(posSuf+len);
int negSuf = negativeSuffix.indexOf(oldSymbol);
if (negSuf != -1)
negativeSuffix = negativeSuffix.substring(0, negSuf) +
newSymbol + negativeSuffix.substring(negSuf+len);
}
}
/**
* Sets the symbols used by this instance. This method makes a copy of
* the supplied symbols.
*
* @param newSymbols the symbols (null
not permitted).
*/
public void setDecimalFormatSymbols(DecimalFormatSymbols newSymbols)
{
symbols = (DecimalFormatSymbols) newSymbols.clone();
}
/**
* Define if the decimal separator should be always visible or only
* visible when needed. This method as effect only on integer values.
* Pass true
if you want the decimal separator to be
* always shown, false
otherwise.
*
* @param newValue true if you want the decimal separator to be
* always shown, false
otherwise.
*/
public void setDecimalSeparatorAlwaysShown(boolean newValue)
{
decimalSeparatorAlwaysShown = newValue;
}
/**
* Sets the number of digits used to group portions of the integer part of
* the number. For example, the number 123456
, with a grouping
* size of 3, is rendered 123,456
.
*
* @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;
}
/**
* 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
* newvalue
and 309. Any value below zero will be
* replaced by zero.
*
* @param newValue The new maximum integer digits value.
*/
public void setMaximumIntegerDigits(int newValue)
{
newValue = (newValue > 0) ? newValue : 0;
super.setMaximumIntegerDigits(Math.min(newValue, DEFAULT_INTEGER_DIGITS));
}
/**
* 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
* newvalue
and 309. Any value below zero will be
* replaced by zero.
*
* @param newValue The new minimum integer digits value.
*/
public void setMinimumIntegerDigits(int newValue)
{
newValue = (newValue > 0) ? newValue : 0;
super.setMinimumIntegerDigits(Math.min(newValue, DEFAULT_INTEGER_DIGITS));
}
/**
* 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
* newvalue
and 309. Any value below zero will be
* replaced by zero.
*
* @param newValue The new maximum fraction digits value.
*/
public void setMaximumFractionDigits(int newValue)
{
newValue = (newValue > 0) ? newValue : 0;
super.setMaximumFractionDigits(Math.min(newValue, DEFAULT_FRACTION_DIGITS));
}
/**
* 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
* newvalue
and 309. Any value below zero will be
* replaced by zero.
*
* @param newValue The new minimum fraction digits value.
*/
public void setMinimumFractionDigits(int newValue)
{
newValue = (newValue > 0) ? newValue : 0;
super.setMinimumFractionDigits(Math.min(newValue, DEFAULT_FRACTION_DIGITS));
}
/**
* 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;
}
/**
* Sets the negative prefix.
*
* @param newValue The new negative prefix.
*/
public void setNegativePrefix(String newValue)
{
negativePrefix = newValue;
}
/**
* Sets the negative suffix.
*
* @param newValue The new negative suffix.
*/
public void setNegativeSuffix(String newValue)
{
negativeSuffix = newValue;
}
/**
* Sets the positive prefix.
*
* @param newValue The new positive prefix.
*/
public void setPositivePrefix(String newValue)
{
positivePrefix = 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 String
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 String
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 true
if the strings are both null
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)
{
CPStringBuilder buf = new CPStringBuilder ();
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();
}
/**
* Quote special characters as defined by patChars
in the
* input string.
*
* @param text
* @param patChars
* @return A StringBuffer with special characters quoted.
*/
private CPStringBuilder quoteFix(String text, String patChars)
{
CPStringBuilder buf = new CPStringBuilder();
int len = text.length();
char ch;
for (int index = 0; index < len; ++index)
{
ch = text.charAt(index);
if (patChars.indexOf(ch) != -1)
{
buf.append('\'');
buf.append(ch);
if (ch != '\'') buf.append('\'');
}
else
{
buf.append(ch);
}
}
return buf;
}
/**
* Returns the format pattern, localized to follow the given
* symbols.
*/
private String computePattern(DecimalFormatSymbols symbols)
{
StringBuilder mainPattern = new StringBuilder();
// 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 _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,
symbols.getGroupingSeparator());
}
// See if we need decimal info.
if (minimumFractionDigits > 0 || maximumFractionDigits > 0 ||
decimalSeparatorAlwaysShown)
{
mainPattern.append(symbols.getDecimalSeparator());
}
for (int i = 0; i < minimumFractionDigits; ++i)
mainPattern.append(symbols.getZeroDigit());
for (int i = minimumFractionDigits; i < maximumFractionDigits; ++i)
mainPattern.append(symbols.getDigit());
if (useExponentialNotation)
{
mainPattern.append(symbols.getExponential());
for (int i = 0; i < minExponentDigits; ++i)
mainPattern.append(symbols.getZeroDigit());
if (minExponentDigits == 0)
mainPattern.append(symbols.getDigit());
}
// 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)
{
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();
}
/* ****** 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)
{
// 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 sourceSymbols,
int start, boolean prefix)
{
CPStringBuilder buffer = new CPStringBuilder();
// the number portion is always delimited by one of those
// characters
char decimalSeparator = sourceSymbols.getDecimalSeparator();
char patternSeparator = sourceSymbols.getPatternSeparator();
char groupingSeparator = sourceSymbols.getGroupingSeparator();
char digit = sourceSymbols.getDigit();
char zero = sourceSymbols.getZeroDigit();
char minus = sourceSymbols.getMinusSign();
// other special characters, cached here to avoid method calls later
char percent = sourceSymbols.getPercent();
char permille = sourceSymbols.getPerMill();
String currencySymbol = this.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 = this.symbols.getCurrencySymbol();
// if \u00A4 is doubled, we use the international currency symbol
if ((i + 1) < len && pattern.charAt(i + 1) == '\u00A4')
{
currencySymbol = this.symbols.getInternationalCurrencySymbol();
i++;
}
this.useCurrencySeparator = true;
buffer.append(currencySymbol);
}
else if (ch == percent)
{
// PERCENT
this.multiplier = 100;
buffer.append(this.symbols.getPercent());
}
else if (ch == permille)
{
// PERMILLE
this.multiplier = 1000;
buffer.append(this.symbols.getPerMill());
}
else if (ch == '\'')
{
// QUOTE
if ((i + 1) < 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 start
.
* 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;
return i;
}
/**
* Scan the given string for number patterns, starting
* from start
.
* 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 start
.
* 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)
{
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 start
.
* 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 sourceSymbols,
int start)
{
StringBuilder buffer = new StringBuilder();
// the number portion is always delimited by one of those
// characters
char decimalSeparator = sourceSymbols.getDecimalSeparator();
char patternSeparator = sourceSymbols.getPatternSeparator();
char groupingSeparator = sourceSymbols.getGroupingSeparator();
char digit = sourceSymbols.getDigit();
char zero = sourceSymbols.getZeroDigit();
char minus = sourceSymbols.getMinusSign();
// other special charcaters, cached here to avoid method calls later
char percent = sourceSymbols.getPercent();
char permille = sourceSymbols.getPerMill();
String CURRENCY_SYMBOL = this.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(this.symbols.getMinusSign());
}
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 + 1) < len && pattern.charAt(i + 1) == '\u00A4')
{
currencySymbol = this.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(this.symbols.getPercent());
}
else if (ch == permille)
{
// PERMILLE
this.negativePatternMultiplier = 1000;
buffer.append(this.symbols.getPerMill());
}
else if (ch == '\'')
{
// QUOTE
if ((i + 1) < 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 isLong
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)
{
// The specs says that fieldPos should not be null, and that we
// should throw a NPE, but it seems that in few classes that
// reference this one, fieldPos is set to null.
// This is even defined in the javadoc, see for example MessageFormat.
// I think the best here is to check for fieldPos and build one if it is
// null. If it cause harms or regressions, just remove this line and
// fix the classes in the point of call, insted.
if (fieldPos == null) fieldPos = new FieldPosition(0);
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(BigDecimal.valueOf(_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();
// 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);
// 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);
}
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.
if (attributeStart < 0)
attributeStart = Math.max(dest.length() - 1, 0);
appendDigit(intPart, dest, this.groupingUsed);
}
else
{
// 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());
// ...update field position, if needed, and return...
if ((fieldPos.getField() == INTEGER_FIELD ||
fieldPos.getFieldAttribute() == NumberFormat.Field.INTEGER))
{
fieldPos.setBeginIndex(beginIndexInt);
fieldPos.setEndIndex(endIndexInt);
}
handleFractionalPart(dest, fractPart, fieldPos, isLong);
// 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);
}
}
/**
* Add to the input buffer the result of formatting the fractional
* portion of the number.
*
* @param dest
* @param fractPart
* @param fieldPos
* @param isLong
*/
private void handleFractionalPart(StringBuffer dest, String fractPart,
FieldPosition fieldPos, boolean isLong)
{
int dotStart = 0;
int dotEnd = 0;
boolean addDecimal = false;
if (this.decimalSeparatorAlwaysShown ||
((!isLong || this.useExponentialNotation) &&
this.showDecimalSeparator && this.maximumFractionDigits > 0) ||
this.minimumFractionDigits > 0)
{
dotStart = dest.length();
if (this.useCurrencySeparator)
dest.append(symbols.getMonetaryDecimalSeparator());
else
dest.append(symbols.getDecimalSeparator());
dotEnd = dest.length();
addDecimal = true;
}
// now handle the fraction portion of the number
int fractStart = 0;
int fractEnd = 0;
boolean addFractional = false;
if ((!isLong || this.useExponentialNotation)
&& this.maximumFractionDigits > 0
|| this.minimumFractionDigits > 0)
{
fractStart = dest.length();
fractEnd = fractStart;
int digits = this.minimumFractionDigits;
if (this.useExponentialNotation)
{
digits = (this.minimumIntegerDigits + this.minimumFractionDigits)
- dest.length();
if (digits < 0) digits = 0;
}
fractPart = adjustTrailingZeros(fractPart, digits);
// FIXME: this code must be improved
// now check if the factional part is just 0, in this case
// we need to remove the '.' unless requested
boolean allZeros = true;
char fracts[] = fractPart.toCharArray();
for (int i = 0; i < fracts.length; i++)
{
if (fracts[i] != '0')
allZeros = false;
}
if (!allZeros || (minimumFractionDigits > 0))
{
appendDigit(fractPart, dest, false);
fractEnd = dest.length();
addDecimal = true;
addFractional = true;
}
else if (!this.decimalSeparatorAlwaysShown)
{
dest.deleteCharAt(dest.length() - 1);
addDecimal = false;
}
else
{
fractEnd = dest.length();
addFractional = true;
}
}
if (addDecimal)
addAttribute(Field.DECIMAL_SEPARATOR, dotStart, dotEnd);
if (addFractional)
addAttribute(Field.FRACTION, fractStart, fractEnd);
if ((fieldPos.getField() == FRACTION_FIELD ||
fieldPos.getFieldAttribute() == NumberFormat.Field.FRACTION))
{
fieldPos.setBeginIndex(fractStart);
fieldPos.setEndIndex(fractEnd);
}
}
/**
* Append to dest
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 dest
.
*
* Grouping is added if groupingUsed
is set
* to true
.
*/
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));
}
}
/**
* Calculate the exponent to use if eponential notation is used.
* The exponent is calculated as a power of ten.
* number
should be positive, if is zero, or less than zero,
* zero is returned.
*/
private long getExponent(BigDecimal number)
{
long exponent = 0;
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 src
string,
* if src contains more than minimumDigits
digits.
* if src contains less that minimumDigits
,
* 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;
// remove all trailing zero
if (len > minimumDigits)
{
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);
}
else
{
char zero = symbols.getZeroDigit();
CPStringBuilder _result = new CPStringBuilder(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;
}
}