12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279 |
- /* 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;
- }
- }
|