Merge from mainline
[official-gcc.git] / libjava / classpath / java / text / DecimalFormat.java
bloba9ec7767f94668ce5807a9c281671831873f0426
1 /* DecimalFormat.java -- Formats and parses numbers
2 Copyright (C) 1999, 2000, 2001, 2003, 2004, 2005 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
38 package java.text;
40 import gnu.java.text.AttributedFormatBuffer;
41 import gnu.java.text.FormatBuffer;
42 import gnu.java.text.FormatCharacterIterator;
43 import gnu.java.text.StringFormatBuffer;
45 import java.io.IOException;
46 import java.io.ObjectInputStream;
47 import java.util.Currency;
48 import java.util.HashMap;
49 import java.util.Locale;
51 /**
52 * @author Tom Tromey (tromey@cygnus.com)
53 * @author Andrew John Hughes (gnu_andrew@member.fsf.org)
54 * @date March 4, 1999
56 /* Written using "Java Class Libraries", 2nd edition, plus online
57 * API docs for JDK 1.2 from http://www.javasoft.com.
58 * Status: Believed complete and correct to 1.2.
59 * Note however that the docs are very unclear about how format parsing
60 * should work. No doubt there are problems here.
62 public class DecimalFormat extends NumberFormat
64 // This is a helper for applyPatternWithSymbols. It reads a prefix
65 // or a suffix. It can cause some side-effects.
66 private int scanFix (String pattern, int index, FormatBuffer buf,
67 String patChars, DecimalFormatSymbols syms,
68 boolean is_suffix)
70 int len = pattern.length();
71 boolean quoteStarted = false;
72 buf.clear();
74 boolean multiplierSet = false;
75 while (index < len)
77 char c = pattern.charAt(index);
79 if (quoteStarted)
81 if (c == '\'')
82 quoteStarted = false;
83 else
84 buf.append(c);
85 index++;
86 continue;
89 if (c == '\'' && index + 1 < len
90 && pattern.charAt(index + 1) == '\'')
92 buf.append(c);
93 index++;
95 else if (c == '\'')
97 quoteStarted = true;
99 else if (c == '\u00a4')
101 /* Currency interpreted later */
102 buf.append(c);
104 else if (c == syms.getPercent())
106 if (multiplierSet)
107 throw new IllegalArgumentException ("multiplier already set " +
108 "- index: " + index);
109 multiplierSet = true;
110 multiplier = 100;
111 buf.append(c, NumberFormat.Field.PERCENT);
113 else if (c == syms.getPerMill())
115 if (multiplierSet)
116 throw new IllegalArgumentException ("multiplier already set " +
117 "- index: " + index);
118 multiplierSet = true;
119 multiplier = 1000;
120 buf.append(c, NumberFormat.Field.PERMILLE);
122 else if (patChars.indexOf(c) != -1)
124 // This is a pattern character.
125 break;
127 else
129 buf.append(c);
131 index++;
134 if (quoteStarted)
135 throw new IllegalArgumentException ("pattern is lacking a closing quote");
137 return index;
140 // A helper which reads a number format.
141 private int scanFormat (String pattern, int index, String patChars,
142 DecimalFormatSymbols syms, boolean is_positive)
144 int max = pattern.length();
146 int countSinceGroup = 0;
147 int zeroCount = 0;
148 boolean saw_group = false;
151 // Scan integer part.
153 while (index < max)
155 char c = pattern.charAt(index);
157 if (c == syms.getDigit())
159 if (zeroCount > 0)
160 throw new IllegalArgumentException ("digit mark following " +
161 "zero - index: " + index);
162 ++countSinceGroup;
164 else if (c == syms.getZeroDigit())
166 ++zeroCount;
167 ++countSinceGroup;
169 else if (c == syms.getGroupingSeparator())
171 countSinceGroup = 0;
172 saw_group = true;
174 else
175 break;
177 ++index;
180 // We can only side-effect when parsing the positive format.
181 if (is_positive)
183 groupingUsed = saw_group;
184 groupingSize = (byte) countSinceGroup;
185 // Checking "zeroCount > 0" avoids 0 being formatted into "" with "#".
186 if (zeroCount > 0)
187 minimumIntegerDigits = zeroCount;
190 // Early termination.
191 if (index == max || pattern.charAt(index) == syms.getGroupingSeparator())
193 if (is_positive)
194 decimalSeparatorAlwaysShown = false;
195 return index;
198 if (pattern.charAt(index) == syms.getDecimalSeparator())
200 ++index;
203 // Scan fractional part.
205 int hashCount = 0;
206 zeroCount = 0;
207 while (index < max)
209 char c = pattern.charAt(index);
210 if (c == syms.getZeroDigit())
212 if (hashCount > 0)
213 throw new IllegalArgumentException ("zero mark " +
214 "following digit - index: " + index);
215 ++zeroCount;
217 else if (c == syms.getDigit())
219 ++hashCount;
221 else if (c != syms.getExponential()
222 && c != syms.getPatternSeparator()
223 && c != syms.getPercent()
224 && c != syms.getPerMill()
225 && patChars.indexOf(c) != -1)
226 throw new IllegalArgumentException ("unexpected special " +
227 "character - index: " + index);
228 else
229 break;
231 ++index;
234 if (is_positive)
236 maximumFractionDigits = hashCount + zeroCount;
237 minimumFractionDigits = zeroCount;
240 if (index == max)
241 return index;
244 if (pattern.charAt(index) == syms.getExponential())
247 // Scan exponential format.
249 zeroCount = 0;
250 ++index;
251 while (index < max)
253 char c = pattern.charAt(index);
254 if (c == syms.getZeroDigit())
255 ++zeroCount;
256 else if (c == syms.getDigit())
258 if (zeroCount > 0)
259 throw new
260 IllegalArgumentException ("digit mark following zero " +
261 "in exponent - index: " +
262 index);
264 else if (patChars.indexOf(c) != -1)
265 throw new IllegalArgumentException ("unexpected special " +
266 "character - index: " +
267 index);
268 else
269 break;
271 ++index;
274 if (is_positive)
276 useExponentialNotation = true;
277 minExponentDigits = (byte) zeroCount;
280 maximumIntegerDigits = groupingSize;
281 groupingSize = 0;
282 if (maximumIntegerDigits > minimumIntegerDigits && maximumIntegerDigits > 0)
284 minimumIntegerDigits = 1;
285 exponentRound = maximumIntegerDigits;
287 else
288 exponentRound = 1;
291 return index;
294 // This helper function creates a string consisting of all the
295 // characters which can appear in a pattern and must be quoted.
296 private String patternChars (DecimalFormatSymbols syms)
298 StringBuffer buf = new StringBuffer ();
299 buf.append(syms.getDecimalSeparator());
300 buf.append(syms.getDigit());
301 buf.append(syms.getExponential());
302 buf.append(syms.getGroupingSeparator());
303 // Adding this one causes pattern application to fail.
304 // Of course, omitting is causes toPattern to fail.
305 // ... but we already have bugs there. FIXME.
306 // buf.append(syms.getMinusSign());
307 buf.append(syms.getPatternSeparator());
308 buf.append(syms.getPercent());
309 buf.append(syms.getPerMill());
310 buf.append(syms.getZeroDigit());
311 buf.append('\u00a4');
312 return buf.toString();
315 private void applyPatternWithSymbols(String pattern, DecimalFormatSymbols syms)
317 // Initialize to the state the parser expects.
318 negativePrefix = "";
319 negativeSuffix = "";
320 positivePrefix = "";
321 positiveSuffix = "";
322 decimalSeparatorAlwaysShown = false;
323 groupingSize = 0;
324 minExponentDigits = 0;
325 multiplier = 1;
326 useExponentialNotation = false;
327 groupingUsed = false;
328 maximumFractionDigits = 0;
329 maximumIntegerDigits = MAXIMUM_INTEGER_DIGITS;
330 minimumFractionDigits = 0;
331 minimumIntegerDigits = 1;
333 AttributedFormatBuffer buf = new AttributedFormatBuffer ();
334 String patChars = patternChars (syms);
336 int max = pattern.length();
337 int index = scanFix (pattern, 0, buf, patChars, syms, false);
338 buf.sync();
339 positivePrefix = buf.getBuffer().toString();
340 positivePrefixRanges = buf.getRanges();
341 positivePrefixAttrs = buf.getAttributes();
343 index = scanFormat (pattern, index, patChars, syms, true);
345 index = scanFix (pattern, index, buf, patChars, syms, true);
346 buf.sync();
347 positiveSuffix = buf.getBuffer().toString();
348 positiveSuffixRanges = buf.getRanges();
349 positiveSuffixAttrs = buf.getAttributes();
351 if (index == pattern.length())
353 // No negative info.
354 negativePrefix = null;
355 negativeSuffix = null;
357 else
359 if (pattern.charAt(index) != syms.getPatternSeparator())
360 throw new IllegalArgumentException ("separator character " +
361 "expected - index: " + index);
363 index = scanFix (pattern, index + 1, buf, patChars, syms, false);
364 buf.sync();
365 negativePrefix = buf.getBuffer().toString();
366 negativePrefixRanges = buf.getRanges();
367 negativePrefixAttrs = buf.getAttributes();
369 // We parse the negative format for errors but we don't let
370 // it side-effect this object.
371 index = scanFormat (pattern, index, patChars, syms, false);
373 index = scanFix (pattern, index, buf, patChars, syms, true);
374 buf.sync();
375 negativeSuffix = buf.getBuffer().toString();
376 negativeSuffixRanges = buf.getRanges();
377 negativeSuffixAttrs = buf.getAttributes();
379 if (index != pattern.length())
380 throw new IllegalArgumentException ("end of pattern expected " +
381 "- index: " + index);
385 public void applyLocalizedPattern (String pattern)
387 // JCL p. 638 claims this throws a ParseException but p. 629
388 // contradicts this. Empirical tests with patterns of "0,###.0"
389 // and "#.#.#" corroborate the p. 629 statement that an
390 // IllegalArgumentException is thrown.
391 applyPatternWithSymbols (pattern, symbols);
394 public void applyPattern (String pattern)
396 // JCL p. 638 claims this throws a ParseException but p. 629
397 // contradicts this. Empirical tests with patterns of "0,###.0"
398 // and "#.#.#" corroborate the p. 629 statement that an
399 // IllegalArgumentException is thrown.
400 applyPatternWithSymbols (pattern, nonLocalizedSymbols);
403 public Object clone ()
405 DecimalFormat c = (DecimalFormat) super.clone ();
406 c.symbols = (DecimalFormatSymbols) symbols.clone ();
407 return c;
411 * Constructs a <code>DecimalFormat</code> which uses the default
412 * pattern and symbols.
414 public DecimalFormat ()
416 this ("#,##0.###");
420 * Constructs a <code>DecimalFormat</code> which uses the given
421 * pattern and the default symbols for formatting and parsing.
423 * @param pattern the non-localized pattern to use.
424 * @throws NullPointerException if any argument is null.
425 * @throws IllegalArgumentException if the pattern is invalid.
427 public DecimalFormat (String pattern)
429 this (pattern, new DecimalFormatSymbols ());
433 * Constructs a <code>DecimalFormat</code> using the given pattern
434 * and formatting symbols. This construction method is used to give
435 * complete control over the formatting process.
437 * @param pattern the non-localized pattern to use.
438 * @param symbols the set of symbols used for parsing and formatting.
439 * @throws NullPointerException if any argument is null.
440 * @throws IllegalArgumentException if the pattern is invalid.
442 public DecimalFormat(String pattern, DecimalFormatSymbols symbols)
444 this.symbols = (DecimalFormatSymbols) symbols.clone();
445 applyPattern(pattern);
448 private boolean equals(String s1, String s2)
450 if (s1 == null || s2 == null)
451 return s1 == s2;
452 return s1.equals(s2);
456 * Tests this instance for equality with an arbitrary object. This method
457 * returns <code>true</code> if:
458 * <ul>
459 * <li><code>obj</code> is not <code>null</code>;</li>
460 * <li><code>obj</code> is an instance of <code>DecimalFormat</code>;</li>
461 * <li>this instance and <code>obj</code> have the same attributes;</li>
462 * </ul>
464 * @param obj the object (<code>null</code> permitted).
466 * @return A boolean.
468 public boolean equals(Object obj)
470 if (! (obj instanceof DecimalFormat))
471 return false;
472 DecimalFormat dup = (DecimalFormat) obj;
473 return (decimalSeparatorAlwaysShown == dup.decimalSeparatorAlwaysShown
474 && groupingUsed == dup.groupingUsed
475 && groupingSize == dup.groupingSize
476 && multiplier == dup.multiplier
477 && useExponentialNotation == dup.useExponentialNotation
478 && minExponentDigits == dup.minExponentDigits
479 && minimumIntegerDigits == dup.minimumIntegerDigits
480 && maximumIntegerDigits == dup.maximumIntegerDigits
481 && minimumFractionDigits == dup.minimumFractionDigits
482 && maximumFractionDigits == dup.maximumFractionDigits
483 && equals(negativePrefix, dup.negativePrefix)
484 && equals(negativeSuffix, dup.negativeSuffix)
485 && equals(positivePrefix, dup.positivePrefix)
486 && equals(positiveSuffix, dup.positiveSuffix)
487 && symbols.equals(dup.symbols));
490 private void formatInternal (double number, FormatBuffer dest,
491 FieldPosition fieldPos)
493 // A very special case.
494 if (Double.isNaN(number))
496 dest.append(symbols.getNaN());
497 if (fieldPos != null &&
498 (fieldPos.getField() == INTEGER_FIELD ||
499 fieldPos.getFieldAttribute() == NumberFormat.Field.INTEGER))
501 int index = dest.length();
502 fieldPos.setBeginIndex(index - symbols.getNaN().length());
503 fieldPos.setEndIndex(index);
505 return;
508 boolean is_neg = number < 0;
509 if (is_neg)
511 if (negativePrefix != null)
513 dest.append(substituteCurrency(negativePrefix, number),
514 negativePrefixRanges, negativePrefixAttrs);
516 else
518 dest.append(symbols.getMinusSign(), NumberFormat.Field.SIGN);
519 dest.append(substituteCurrency(positivePrefix, number),
520 positivePrefixRanges, positivePrefixAttrs);
522 number = - number;
524 else
526 dest.append(substituteCurrency(positivePrefix, number),
527 positivePrefixRanges, positivePrefixAttrs);
529 int integerBeginIndex = dest.length();
530 int integerEndIndex = 0;
531 int zeroStart = symbols.getZeroDigit() - '0';
533 if (Double.isInfinite (number))
535 dest.append(symbols.getInfinity());
536 integerEndIndex = dest.length();
538 else
540 number *= multiplier;
542 // Compute exponent.
543 long exponent = 0;
544 double baseNumber;
545 if (useExponentialNotation)
547 exponent = (long) Math.floor (Math.log(number) / Math.log(10));
548 exponent = exponent - (exponent % exponentRound);
549 if (minimumIntegerDigits > 0)
550 exponent -= minimumIntegerDigits - 1;
551 baseNumber = (number / Math.pow(10.0, exponent));
553 else
554 baseNumber = number;
556 // Round to the correct number of digits.
557 baseNumber += 5 * Math.pow(10.0, - maximumFractionDigits - 1);
559 int index = dest.length();
560 //double intPart = Math.floor(baseNumber);
561 String intPart = Long.toString((long)Math.floor(baseNumber));
562 int count, groupPosition = intPart.length();
564 dest.setDefaultAttribute(NumberFormat.Field.INTEGER);
566 for (count = 0; count < minimumIntegerDigits-intPart.length(); count++)
567 dest.append(symbols.getZeroDigit());
569 for (count = 0;
570 count < maximumIntegerDigits && count < intPart.length();
571 count++)
573 int dig = intPart.charAt(count);
575 // Append group separator if required.
576 if (groupingUsed && count > 0 && groupingSize != 0 && groupPosition % groupingSize == 0)
578 dest.append(symbols.getGroupingSeparator(), NumberFormat.Field.GROUPING_SEPARATOR);
579 dest.setDefaultAttribute(NumberFormat.Field.INTEGER);
581 dest.append((char) (zeroStart + dig));
583 groupPosition--;
585 dest.setDefaultAttribute(null);
587 integerEndIndex = dest.length();
589 int decimal_index = integerEndIndex;
590 int consecutive_zeros = 0;
591 int total_digits = 0;
593 int localMaximumFractionDigits = maximumFractionDigits;
595 if (useExponentialNotation)
596 localMaximumFractionDigits += minimumIntegerDigits - count;
598 // Strip integer part from NUMBER.
599 double fracPart = baseNumber - Math.floor(baseNumber);
601 if ( ((fracPart != 0 || minimumFractionDigits > 0) && localMaximumFractionDigits > 0)
602 || decimalSeparatorAlwaysShown)
604 dest.append (symbols.getDecimalSeparator(), NumberFormat.Field.DECIMAL_SEPARATOR);
607 int fraction_begin = dest.length();
608 dest.setDefaultAttribute(NumberFormat.Field.FRACTION);
609 for (count = 0;
610 count < localMaximumFractionDigits
611 && (fracPart != 0 || count < minimumFractionDigits);
612 ++count)
614 ++total_digits;
615 fracPart *= 10;
616 long dig = (long) fracPart;
617 if (dig == 0)
618 ++consecutive_zeros;
619 else
620 consecutive_zeros = 0;
621 dest.append((char) (symbols.getZeroDigit() + dig));
623 // Strip integer part from FRACPART.
624 fracPart = fracPart - Math.floor (fracPart);
627 // Strip extraneous trailing `0's. We can't always detect
628 // these in the loop.
629 int extra_zeros = Math.min (consecutive_zeros,
630 total_digits - minimumFractionDigits);
631 if (extra_zeros > 0)
633 dest.cutTail(extra_zeros);
634 total_digits -= extra_zeros;
635 if (total_digits == 0 && !decimalSeparatorAlwaysShown)
636 dest.cutTail(1);
639 if (fieldPos != null && fieldPos.getField() == FRACTION_FIELD)
641 fieldPos.setBeginIndex(fraction_begin);
642 fieldPos.setEndIndex(dest.length());
645 // Finally, print the exponent.
646 if (useExponentialNotation)
648 dest.append(symbols.getExponential(), NumberFormat.Field.EXPONENT_SYMBOL);
649 if (exponent < 0)
651 dest.append (symbols.getMinusSign (), NumberFormat.Field.EXPONENT_SIGN);
652 exponent = - exponent;
654 index = dest.length();
655 dest.setDefaultAttribute(NumberFormat.Field.EXPONENT);
656 String exponentString = Long.toString ((long) exponent);
658 for (count = 0; count < minExponentDigits-exponentString.length();
659 count++)
660 dest.append((char) symbols.getZeroDigit());
662 for (count = 0;
663 count < exponentString.length();
664 ++count)
666 int dig = exponentString.charAt(count);
667 dest.append((char) (zeroStart + dig));
672 if (fieldPos != null &&
673 (fieldPos.getField() == INTEGER_FIELD ||
674 fieldPos.getFieldAttribute() == NumberFormat.Field.INTEGER))
676 fieldPos.setBeginIndex(integerBeginIndex);
677 fieldPos.setEndIndex(integerEndIndex);
680 if (is_neg && negativeSuffix != null)
682 dest.append(substituteCurrency(negativeSuffix, number),
683 negativeSuffixRanges, negativeSuffixAttrs);
685 else
687 dest.append(substituteCurrency(positiveSuffix, number),
688 positiveSuffixRanges, positiveSuffixAttrs);
692 public StringBuffer format (double number, StringBuffer dest,
693 FieldPosition fieldPos)
695 formatInternal (number, new StringFormatBuffer(dest), fieldPos);
696 return dest;
699 public AttributedCharacterIterator formatToCharacterIterator (Object value)
701 AttributedFormatBuffer sbuf = new AttributedFormatBuffer();
703 if (value instanceof Number)
704 formatInternal(((Number) value).doubleValue(), sbuf, null);
705 else
706 throw new IllegalArgumentException
707 ("Cannot format given Object as a Number");
709 sbuf.sync();
710 return new FormatCharacterIterator(sbuf.getBuffer().toString(),
711 sbuf.getRanges(),
712 sbuf.getAttributes());
715 public StringBuffer format (long number, StringBuffer dest,
716 FieldPosition fieldPos)
718 // If using exponential notation, we just format as a double.
719 if (useExponentialNotation)
720 return format ((double) number, dest, fieldPos);
722 boolean is_neg = number < 0;
723 if (is_neg)
725 if (negativePrefix != null)
726 dest.append(substituteCurrency(negativePrefix, number));
727 else
729 dest.append(symbols.getMinusSign());
730 dest.append(substituteCurrency(positivePrefix, number));
732 number = - number;
734 else
735 dest.append(substituteCurrency(positivePrefix, number));
737 int integerBeginIndex = dest.length();
738 int index = dest.length();
739 int count = 0;
741 /* Handle percentages, etc. */
742 number *= multiplier;
743 while (count < maximumIntegerDigits
744 && (number > 0 || count < minimumIntegerDigits))
746 long dig = number % 10;
747 number /= 10;
748 // NUMBER and DIG will be less than 0 if the original number
749 // was the most negative long.
750 if (dig < 0)
752 dig = - dig;
753 number = - number;
756 // Append group separator if required.
757 if (groupingUsed && count > 0 && groupingSize != 0 && count % groupingSize == 0)
758 dest.insert(index, symbols.getGroupingSeparator());
760 dest.insert(index, (char) (symbols.getZeroDigit() + dig));
762 ++count;
765 if (fieldPos != null && fieldPos.getField() == INTEGER_FIELD)
767 fieldPos.setBeginIndex(integerBeginIndex);
768 fieldPos.setEndIndex(dest.length());
771 if (decimalSeparatorAlwaysShown || minimumFractionDigits > 0)
773 dest.append(symbols.getDecimalSeparator());
774 if (fieldPos != null && fieldPos.getField() == FRACTION_FIELD)
776 fieldPos.setBeginIndex(dest.length());
777 fieldPos.setEndIndex(dest.length() + minimumFractionDigits);
781 for (count = 0; count < minimumFractionDigits; ++count)
782 dest.append(symbols.getZeroDigit());
784 dest.append((is_neg && negativeSuffix != null)
785 ? substituteCurrency(negativeSuffix, number)
786 : substituteCurrency(positiveSuffix, number));
787 return dest;
791 * Returns the currency corresponding to the currency symbol stored
792 * in the instance of <code>DecimalFormatSymbols</code> used by this
793 * <code>DecimalFormat</code>.
795 * @return A new instance of <code>Currency</code> if
796 * the currency code matches a known one, null otherwise.
798 public Currency getCurrency()
800 return symbols.getCurrency();
804 * Returns a copy of the symbols used by this instance.
806 * @return A copy of the symbols.
808 public DecimalFormatSymbols getDecimalFormatSymbols()
810 return (DecimalFormatSymbols) symbols.clone();
813 public int getGroupingSize ()
815 return groupingSize;
818 public int getMultiplier ()
820 return multiplier;
823 public String getNegativePrefix ()
825 return negativePrefix;
828 public String getNegativeSuffix ()
830 return negativeSuffix;
833 public String getPositivePrefix ()
835 return positivePrefix;
838 public String getPositiveSuffix ()
840 return positiveSuffix;
844 * Returns a hash code for this object.
846 * @return A hash code.
848 public int hashCode()
850 return toPattern().hashCode();
853 public boolean isDecimalSeparatorAlwaysShown ()
855 return decimalSeparatorAlwaysShown;
858 public Number parse (String str, ParsePosition pos)
861 * Our strategy is simple: copy the text into separate buffers: one for the int part,
862 * one for the fraction part and for the exponential part.
863 * We translate or omit locale-specific information.
864 * If exponential is sufficiently big we merge the fraction and int part and
865 * remove the '.' and then we use Long to convert the number. In the other
866 * case, we use Double to convert the full number.
869 boolean is_neg = false;
870 int index = pos.getIndex();
871 StringBuffer int_buf = new StringBuffer ();
873 // We have to check both prefixes, because one might be empty. We
874 // want to pick the longest prefix that matches.
875 boolean got_pos = str.startsWith(positivePrefix, index);
876 String np = (negativePrefix != null
877 ? negativePrefix
878 : positivePrefix + symbols.getMinusSign());
879 boolean got_neg = str.startsWith(np, index);
881 if (got_pos && got_neg)
883 // By checking this way, we preserve ambiguity in the case
884 // where the negative format differs only in suffix. We
885 // check this again later.
886 if (np.length() > positivePrefix.length())
888 is_neg = true;
889 index += np.length();
891 else
892 index += positivePrefix.length();
894 else if (got_neg)
896 is_neg = true;
897 index += np.length();
899 else if (got_pos)
900 index += positivePrefix.length();
901 else
903 pos.setErrorIndex (index);
904 return null;
907 // FIXME: handle Inf and NaN.
909 // FIXME: do we have to respect minimum digits?
910 // What about multiplier?
912 StringBuffer buf = int_buf;
913 StringBuffer frac_buf = null;
914 StringBuffer exp_buf = null;
915 int start_index = index;
916 int max = str.length();
917 int exp_index = -1;
918 int last = index + maximumIntegerDigits;
920 if (maximumFractionDigits > 0)
921 last += maximumFractionDigits + 1;
923 if (useExponentialNotation)
924 last += minExponentDigits + 1;
926 if (last > 0 && max > last)
927 max = last;
929 char zero = symbols.getZeroDigit();
930 int last_group = -1;
931 boolean int_part = true;
932 boolean exp_part = false;
933 for (; index < max; ++index)
935 char c = str.charAt(index);
937 // FIXME: what about grouping size?
938 if (groupingUsed && c == symbols.getGroupingSeparator())
940 if (last_group != -1
941 && groupingSize != 0
942 && (index - last_group) % groupingSize != 0)
944 pos.setErrorIndex(index);
945 return null;
947 last_group = index+1;
949 else if (c >= zero && c <= zero + 9)
951 buf.append((char) (c - zero + '0'));
953 else if (parseIntegerOnly)
954 break;
955 else if (c == symbols.getDecimalSeparator())
957 if (last_group != -1
958 && groupingSize != 0
959 && (index - last_group) % groupingSize != 0)
961 pos.setErrorIndex(index);
962 return null;
964 buf = frac_buf = new StringBuffer();
965 frac_buf.append('.');
966 int_part = false;
968 else if (c == symbols.getExponential())
970 buf = exp_buf = new StringBuffer();
971 int_part = false;
972 exp_part = true;
973 exp_index = index+1;
975 else if (exp_part
976 && (c == '+' || c == '-' || c == symbols.getMinusSign()))
978 // For exponential notation.
979 buf.append(c);
981 else
982 break;
985 if (index == start_index)
987 // Didn't see any digits.
988 pos.setErrorIndex(index);
989 return null;
992 // Check the suffix. We must do this before converting the
993 // buffer to a number to handle the case of a number which is
994 // the most negative Long.
995 boolean got_pos_suf = str.startsWith(positiveSuffix, index);
996 String ns = (negativePrefix == null ? positiveSuffix : negativeSuffix);
997 boolean got_neg_suf = str.startsWith(ns, index);
998 if (is_neg)
1000 if (! got_neg_suf)
1002 pos.setErrorIndex(index);
1003 return null;
1006 else if (got_pos && got_neg && got_neg_suf)
1008 is_neg = true;
1010 else if (got_pos != got_pos_suf && got_neg != got_neg_suf)
1012 pos.setErrorIndex(index);
1013 return null;
1015 else if (! got_pos_suf)
1017 pos.setErrorIndex(index);
1018 return null;
1021 String suffix = is_neg ? ns : positiveSuffix;
1022 long parsedMultiplier = 1;
1023 boolean use_long;
1025 if (is_neg)
1026 int_buf.insert(0, '-');
1028 // Now handle the exponential part if there is one.
1029 if (exp_buf != null)
1031 int exponent_value;
1035 exponent_value = Integer.parseInt(exp_buf.toString());
1037 catch (NumberFormatException x1)
1039 pos.setErrorIndex(exp_index);
1040 return null;
1043 if (frac_buf == null)
1045 // We only have to add some zeros to the int part.
1046 // Build a multiplier.
1047 for (int i = 0; i < exponent_value; i++)
1048 int_buf.append('0');
1050 use_long = true;
1052 else
1054 boolean long_sufficient;
1056 if (exponent_value < frac_buf.length()-1)
1058 int lastNonNull = -1;
1059 /* We have to check the fraction buffer: it may only be full of '0'
1060 * or be sufficiently filled with it to convert the number into Long.
1062 for (int i = 1; i < frac_buf.length(); i++)
1063 if (frac_buf.charAt(i) != '0')
1064 lastNonNull = i;
1066 long_sufficient = (lastNonNull < 0 || lastNonNull <= exponent_value);
1068 else
1069 long_sufficient = true;
1071 if (long_sufficient)
1073 for (int i = 1; i < frac_buf.length() && i < exponent_value; i++)
1074 int_buf.append(frac_buf.charAt(i));
1075 for (int i = frac_buf.length()-1; i < exponent_value; i++)
1076 int_buf.append('0');
1077 use_long = true;
1079 else
1082 * A long type is not sufficient, we build the full buffer to
1083 * be parsed by Double.
1085 int_buf.append(frac_buf);
1086 int_buf.append('E');
1087 int_buf.append(exp_buf);
1088 use_long = false;
1092 else
1094 if (frac_buf != null)
1096 /* Check whether the fraction buffer contains only '0' */
1097 int i;
1098 for (i = 1; i < frac_buf.length(); i++)
1099 if (frac_buf.charAt(i) != '0')
1100 break;
1102 if (i != frac_buf.length())
1104 use_long = false;
1105 int_buf.append(frac_buf);
1107 else
1108 use_long = true;
1110 else
1111 use_long = true;
1114 String t = int_buf.toString();
1115 Number result = null;
1116 if (use_long)
1120 result = new Long (t);
1122 catch (NumberFormatException x1)
1126 else
1130 result = new Double (t);
1132 catch (NumberFormatException x2)
1136 if (result == null)
1138 pos.setErrorIndex(index);
1139 return null;
1142 pos.setIndex(index + suffix.length());
1144 return result;
1148 * Sets the <code>Currency</code> on the
1149 * <code>DecimalFormatSymbols</code> used, which also sets the
1150 * currency symbols on those symbols.
1152 public void setCurrency(Currency currency)
1154 symbols.setCurrency(currency);
1158 * Sets the symbols used by this instance. This method makes a copy of
1159 * the supplied symbols.
1161 * @param newSymbols the symbols (<code>null</code> not permitted).
1163 public void setDecimalFormatSymbols(DecimalFormatSymbols newSymbols)
1165 symbols = (DecimalFormatSymbols) newSymbols.clone();
1168 public void setDecimalSeparatorAlwaysShown (boolean newValue)
1170 decimalSeparatorAlwaysShown = newValue;
1173 public void setGroupingSize (int groupSize)
1175 groupingSize = (byte) groupSize;
1178 public void setMaximumFractionDigits (int newValue)
1180 super.setMaximumFractionDigits(Math.min(newValue, 340));
1183 public void setMaximumIntegerDigits (int newValue)
1185 super.setMaximumIntegerDigits(Math.min(newValue, 309));
1188 public void setMinimumFractionDigits (int newValue)
1190 super.setMinimumFractionDigits(Math.min(newValue, 340));
1193 public void setMinimumIntegerDigits (int newValue)
1195 super.setMinimumIntegerDigits(Math.min(newValue, 309));
1198 public void setMultiplier (int newValue)
1200 multiplier = newValue;
1203 public void setNegativePrefix (String newValue)
1205 negativePrefix = newValue;
1208 public void setNegativeSuffix (String newValue)
1210 negativeSuffix = newValue;
1213 public void setPositivePrefix (String newValue)
1215 positivePrefix = newValue;
1218 public void setPositiveSuffix (String newValue)
1220 positiveSuffix = newValue;
1223 private void quoteFix(StringBuffer buf, String text, String patChars)
1225 int len = text.length();
1226 for (int index = 0; index < len; ++index)
1228 char c = text.charAt(index);
1229 if (patChars.indexOf(c) != -1)
1231 buf.append('\'');
1232 buf.append(c);
1233 buf.append('\'');
1235 else
1236 buf.append(c);
1240 private String computePattern(DecimalFormatSymbols syms)
1242 StringBuffer mainPattern = new StringBuffer ();
1243 // We have to at least emit a zero for the minimum number of
1244 // digits. Past that we need hash marks up to the grouping
1245 // separator (and one beyond).
1246 int total_digits = Math.max(minimumIntegerDigits,
1247 groupingUsed ? groupingSize + 1: groupingSize);
1248 for (int i = 0; i < total_digits - minimumIntegerDigits; ++i)
1249 mainPattern.append(syms.getDigit());
1250 for (int i = total_digits - minimumIntegerDigits; i < total_digits; ++i)
1251 mainPattern.append(syms.getZeroDigit());
1252 // Inserting the gropuing operator afterwards is easier.
1253 if (groupingUsed)
1254 mainPattern.insert(mainPattern.length() - groupingSize,
1255 syms.getGroupingSeparator());
1256 // See if we need decimal info.
1257 if (minimumFractionDigits > 0 || maximumFractionDigits > 0
1258 || decimalSeparatorAlwaysShown)
1259 mainPattern.append(syms.getDecimalSeparator());
1260 for (int i = 0; i < minimumFractionDigits; ++i)
1261 mainPattern.append(syms.getZeroDigit());
1262 for (int i = minimumFractionDigits; i < maximumFractionDigits; ++i)
1263 mainPattern.append(syms.getDigit());
1264 if (useExponentialNotation)
1266 mainPattern.append(syms.getExponential());
1267 for (int i = 0; i < minExponentDigits; ++i)
1268 mainPattern.append(syms.getZeroDigit());
1269 if (minExponentDigits == 0)
1270 mainPattern.append(syms.getDigit());
1273 String main = mainPattern.toString();
1274 String patChars = patternChars (syms);
1275 mainPattern.setLength(0);
1277 quoteFix (mainPattern, positivePrefix, patChars);
1278 mainPattern.append(main);
1279 quoteFix (mainPattern, positiveSuffix, patChars);
1281 if (negativePrefix != null)
1283 quoteFix (mainPattern, negativePrefix, patChars);
1284 mainPattern.append(main);
1285 quoteFix (mainPattern, negativeSuffix, patChars);
1288 return mainPattern.toString();
1291 public String toLocalizedPattern ()
1293 return computePattern (symbols);
1296 public String toPattern ()
1298 return computePattern (nonLocalizedSymbols);
1301 private static final int MAXIMUM_INTEGER_DIGITS = 309;
1303 // These names are fixed by the serialization spec.
1304 private boolean decimalSeparatorAlwaysShown;
1305 private byte groupingSize;
1306 private byte minExponentDigits;
1307 private int exponentRound;
1308 private int multiplier;
1309 private String negativePrefix;
1310 private String negativeSuffix;
1311 private String positivePrefix;
1312 private String positiveSuffix;
1313 private int[] negativePrefixRanges, positivePrefixRanges;
1314 private HashMap[] negativePrefixAttrs, positivePrefixAttrs;
1315 private int[] negativeSuffixRanges, positiveSuffixRanges;
1316 private HashMap[] negativeSuffixAttrs, positiveSuffixAttrs;
1317 private int serialVersionOnStream = 1;
1318 private DecimalFormatSymbols symbols;
1319 private boolean useExponentialNotation;
1320 private static final long serialVersionUID = 864413376551465018L;
1322 private void readObject(ObjectInputStream stream)
1323 throws IOException, ClassNotFoundException
1325 stream.defaultReadObject();
1326 if (serialVersionOnStream < 1)
1328 useExponentialNotation = false;
1329 serialVersionOnStream = 1;
1333 // The locale-independent pattern symbols happen to be the same as
1334 // the US symbols.
1335 private static final DecimalFormatSymbols nonLocalizedSymbols
1336 = new DecimalFormatSymbols (Locale.US);
1339 * <p>
1340 * Substitutes the currency symbol into the given string,
1341 * based on the value used. Currency symbols can either
1342 * be a simple series of characters (e.g. '$'), which are
1343 * simply used as is, or they can be of a more complex
1344 * form:
1345 * </p>
1346 * <p>
1347 * (lower bound)|(mid value)|(upper bound)
1348 * </p>
1349 * <p>
1350 * where each bound has the syntax '(value)(# or <)(symbol)',
1351 * to indicate the bounding value and the symbol used.
1352 * </p>
1353 * <p>
1354 * The currency symbol replaces the currency specifier, '\u00a4',
1355 * an unlocalised character, which thus is used as such in all formats.
1356 * If this symbol occurs twice, the international currency code is used
1357 * instead.
1358 * </p>
1360 * @param string The string containing the currency specifier, '\u00a4'.
1361 * @param number the number being formatted.
1362 * @return a string formatted for the correct currency.
1364 private String substituteCurrency(String string, double number)
1366 int index;
1367 int length;
1368 char currentChar;
1369 StringBuffer buf;
1371 index = 0;
1372 length = string.length();
1373 buf = new StringBuffer();
1375 while (index < length)
1377 currentChar = string.charAt(index);
1378 if (string.charAt(index) == '\u00a4')
1380 if ((index + 1) < length && string.charAt(index + 1) == '\u00a4')
1382 buf.append(symbols.getInternationalCurrencySymbol());
1383 index += 2;
1385 else
1387 String symbol;
1389 symbol = symbols.getCurrencySymbol();
1390 if (symbol.startsWith("="))
1392 String[] bounds;
1393 int[] boundValues;
1394 String[] boundSymbols;
1396 bounds = symbol.substring(1).split("\\|");
1397 boundValues = new int[3];
1398 boundSymbols = new String[3];
1399 for (int a = 0; a < 3; ++a)
1401 String[] bound;
1403 bound = bounds[a].split("[#<]");
1404 boundValues[a] = Integer.parseInt(bound[0]);
1405 boundSymbols[a] = bound[1];
1407 if (number <= boundValues[0])
1409 buf.append(boundSymbols[0]);
1411 else if (number >= boundValues[2])
1413 buf.append(boundSymbols[2]);
1415 else
1417 buf.append(boundSymbols[1]);
1419 ++index;
1421 else
1423 buf.append(symbol);
1424 ++index;
1428 else
1430 buf.append(string.charAt(index));
1431 ++index;
1434 return buf.toString();