| /* DecimalFormat.java -- Formats and parses numbers |
| Copyright (C) 1999, 2000, 2001, 2003, 2004, 2005, 2012 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 <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 |
| { |
| /** 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<FieldPosition> attributes = new ArrayList<FieldPosition>(); |
| |
| /** |
| * Constructs a <code>DecimalFormat</code> which uses the default |
| * pattern and symbols. |
| */ |
| public DecimalFormat() |
| { |
| this ("#,##0.###"); |
| } |
| |
| /** |
| * Constructs a <code>DecimalFormat</code> which uses the given |
| * pattern and the default symbols for formatting and parsing. |
| * |
| * @param pattern the non-localized pattern to use. |
| * @throws NullPointerException if any argument is null. |
| * @throws IllegalArgumentException if the pattern is invalid. |
| */ |
| public DecimalFormat(String pattern) |
| { |
| this (pattern, new DecimalFormatSymbols()); |
| } |
| |
| /** |
| * Constructs a <code>DecimalFormat</code> using the given pattern |
| * and formatting symbols. This construction method is used to give |
| * complete control over the formatting process. |
| * |
| * @param pattern the non-localized pattern to use. |
| * @param symbols the set of symbols used for parsing and formatting. |
| * @throws NullPointerException if any argument is null. |
| * @throws IllegalArgumentException if the pattern is invalid. |
| */ |
| public DecimalFormat(String pattern, DecimalFormatSymbols symbols) |
| { |
| this.symbols = (DecimalFormatSymbols) symbols.clone(); |
| 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 <code>true</code> if: |
| * <ul> |
| * <li><code>obj</code> is not <code>null</code>;</li> |
| * <li><code>obj</code> is an instance of <code>DecimalFormat</code>;</li> |
| * <li>this instance and <code>obj</code> have the same attributes;</li> |
| * </ul> |
| * |
| * @param obj the object (<code>null</code> permitted). |
| * |
| * @return A boolean. |
| */ |
| public boolean equals(Object obj) |
| { |
| if (! (obj instanceof DecimalFormat)) |
| return false; |
| DecimalFormat dup = (DecimalFormat) obj; |
| return (decimalSeparatorAlwaysShown == dup.decimalSeparatorAlwaysShown |
| && groupingUsed == dup.groupingUsed |
| && 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 <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)) |
| { |
| // 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 <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) |
| { |
| /* |
| * 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 = 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 <code>DecimalFormatSymbols</code> used by this |
| * <code>DecimalFormat</code>. |
| * |
| * @return A new instance of <code>Currency</code> if |
| * the currency code matches a known one, null otherwise. |
| */ |
| public Currency getCurrency() |
| { |
| return symbols.getCurrency(); |
| } |
| |
| /** |
| * Returns a copy of the symbols used by this instance. |
| * |
| * @return A copy of the symbols. |
| */ |
| public DecimalFormatSymbols getDecimalFormatSymbols() |
| { |
| return (DecimalFormatSymbols) symbols.clone(); |
| } |
| |
| /** |
| * 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 <code>parse(java.lang.String, java.text.ParsePosition)</code> |
| * should return a {@link BigDecimal} or not. |
| * |
| * @param newValue |
| */ |
| public void setParseBigDecimal(boolean newValue) |
| { |
| this.parseBigDecimal = newValue; |
| } |
| |
| /** |
| * 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 this.parseBigDecimal; |
| } |
| |
| /** |
| * 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) |
| { |
| // 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 <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) |
| { |
| 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 (<code>null</code> 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 <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; |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * 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) |
| { |
| 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 |
| * <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) |
| { |
| 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 |
| * <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) |
| { |
| 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 |
| * <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) |
| { |
| 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 <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) |
| { |
| 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 <code>patChars</code> 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 <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; |
| |
| 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) |
| { |
| 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 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 <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) |
| { |
| // 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 <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)); |
| } |
| } |
| |
| /** |
| * 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 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 <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; |
| |
| // 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; |
| } |
| } |