OSDN Git Service

* java/text/SimpleDateFormat.java: Re-merged with Classpath.
[pf3gnuchains/gcc-fork.git] / libjava / java / text / DecimalFormat.java
1 /* DecimalFormat.java -- Formats and parses numbers
2    Copyright (C) 1999, 2000, 2001 Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
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.
10  
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.
15
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., 59 Temple Place, Suite 330, Boston, MA
19 02111-1307 USA.
20
21 As a special exception, if you link this library with other files to
22 produce an executable, this library does not by itself cause the
23 resulting executable to be covered by the GNU General Public License.
24 This exception does not however invalidate any other reasons why the
25 executable file might be covered by the GNU General Public License. */
26
27 package java.text;
28
29 import java.util.Locale;
30 import java.util.MissingResourceException;
31 import java.util.ResourceBundle;
32 import java.io.ObjectInputStream;
33 import java.io.IOException;
34
35 /**
36  * @author Tom Tromey <tromey@cygnus.com>
37  * @date March 4, 1999
38  */
39 /* Written using "Java Class Libraries", 2nd edition, plus online
40  * API docs for JDK 1.2 from http://www.javasoft.com.
41  * Status:  Believed complete and correct to 1.2.
42  * Note however that the docs are very unclear about how format parsing
43  * should work.  No doubt there are problems here.
44  */
45 public class DecimalFormat extends NumberFormat
46 {
47   // This is a helper for applyPatternWithSymbols.  It reads a prefix
48   // or a suffix.  It can cause some side-effects.
49   private final int scanFix (String pattern, int index, StringBuffer buf,
50                              String patChars, DecimalFormatSymbols syms,
51                              boolean is_suffix)
52   {
53     int len = pattern.length();
54     buf.setLength(0);
55     boolean multiplierSet = false;
56     while (index < len)
57       {
58         char c = pattern.charAt(index);
59         if (c == '\'' && index + 1 < len
60             && pattern.charAt(index + 1) == '\'')
61           {
62             buf.append(c);
63             ++index;
64           }
65         else if (c == '\'' && index + 2 < len
66                  && pattern.charAt(index + 2) == '\'')
67           {
68             buf.append(pattern.charAt(index + 1));
69             index += 2;
70           }
71         else if (c == '\u00a4')
72           {
73             if (index + 1 < len && pattern.charAt(index + 1) == '\u00a4')
74               {
75                 buf.append(syms.getInternationalCurrencySymbol());
76                 ++index;
77               }
78             else
79               buf.append(syms.getCurrencySymbol());
80           }
81         else if (is_suffix && c == syms.getPercent())
82           {
83             if (multiplierSet)
84               throw new IllegalArgumentException ("multiplier already set " +
85                                                   "- index: " + index);
86             multiplierSet = true;
87             multiplier = 100;
88             buf.append(c);
89           }
90         else if (is_suffix && c == syms.getPerMill())
91           {
92             if (multiplierSet)
93               throw new IllegalArgumentException ("multiplier already set " +
94                                                   "- index: " + index);
95             multiplierSet = true;
96             multiplier = 1000;
97             buf.append(c);
98           }
99         else if (patChars.indexOf(c) != -1)
100           {
101             // This is a pattern character.
102             break;
103           }
104         else
105           buf.append(c);
106         ++index;
107       }
108
109     return index;
110   }
111
112   // A helper which reads a number format.
113   private final int scanFormat (String pattern, int index,
114                                 String patChars, DecimalFormatSymbols syms,
115                                 boolean is_positive)
116   {
117     int max = pattern.length();
118
119     int countSinceGroup = 0;
120     int zeroCount = 0;
121     boolean saw_group = false;
122
123     //
124     // Scan integer part.
125     //
126     while (index < max)
127       {
128         char c = pattern.charAt(index);
129
130         if (c == syms.getDigit())
131           {
132             if (zeroCount > 0)
133               throw new IllegalArgumentException ("digit mark following " +
134                                                   "zero - index: " + index);
135             ++countSinceGroup;
136           }
137         else if (c == syms.getZeroDigit())
138           {
139             ++zeroCount;
140             ++countSinceGroup;
141           }
142         else if (c == syms.getGroupingSeparator())
143           {
144             countSinceGroup = 0;
145             saw_group = true;
146           }
147         else
148           break;
149
150         ++index;
151       }
152
153     // We can only side-effect when parsing the positive format.
154     if (is_positive)
155       {
156         groupingUsed = saw_group;
157         groupingSize = (byte) countSinceGroup;
158         minimumIntegerDigits = zeroCount;
159       }
160
161     // Early termination.
162     if (index == max || pattern.charAt(index) == syms.getGroupingSeparator())
163       {
164         if (is_positive)
165           decimalSeparatorAlwaysShown = false;
166         return index;
167       }
168
169     if (pattern.charAt(index) == syms.getDecimalSeparator())
170       {
171         ++index;
172
173         //
174         // Scan fractional part.
175         //
176         int hashCount = 0;
177         zeroCount = 0;
178         while (index < max)
179           {
180             char c = pattern.charAt(index);
181             if (c == syms.getZeroDigit())
182               {
183                 if (hashCount > 0)
184                   throw new IllegalArgumentException ("zero mark " +
185                                                       "following digit - index: " + index);
186                 ++zeroCount;
187               }
188             else if (c == syms.getDigit())
189               {
190                 ++hashCount;
191               }
192             else if (c != syms.getExponential()
193                      && c != syms.getPatternSeparator()
194                      && patChars.indexOf(c) != -1)
195               throw new IllegalArgumentException ("unexpected special " +
196                                                   "character - index: " + index);
197             else
198               break;
199
200             ++index;
201           }
202
203         if (is_positive)
204           {
205             maximumFractionDigits = hashCount + zeroCount;
206             minimumFractionDigits = zeroCount;
207           }
208
209         if (index == max)
210           return index;
211       }
212
213     if (pattern.charAt(index) == syms.getExponential())
214       {
215         //
216         // Scan exponential format.
217         //
218         zeroCount = 0;
219         ++index;
220         while (index < max)
221           {
222             char c = pattern.charAt(index);
223             if (c == syms.getZeroDigit())
224               ++zeroCount;
225             else if (c == syms.getDigit())
226               {
227                 if (zeroCount > 0)
228                   throw new
229                     IllegalArgumentException ("digit mark following zero " +
230                                               "in exponent - index: " +
231                                               index);
232               }
233             else if (patChars.indexOf(c) != -1)
234               throw new IllegalArgumentException ("unexpected special " +
235                                                   "character - index: " +
236                                                   index);
237             else
238               break;
239
240             ++index;
241           }
242
243         if (is_positive)
244           {
245             useExponentialNotation = true;
246             minExponentDigits = (byte) zeroCount;
247           }
248       }
249
250     return index;
251   }
252
253   // This helper function creates a string consisting of all the
254   // characters which can appear in a pattern and must be quoted.
255   private final String patternChars (DecimalFormatSymbols syms)
256   {
257     StringBuffer buf = new StringBuffer ();
258     buf.append(syms.getDecimalSeparator());
259     buf.append(syms.getDigit());
260     buf.append(syms.getExponential());
261     buf.append(syms.getGroupingSeparator());
262     // Adding this one causes pattern application to fail.
263     // Of course, omitting is causes toPattern to fail.
264     // ... but we already have bugs there.  FIXME.
265     // buf.append(syms.getMinusSign());
266     buf.append(syms.getPatternSeparator());
267     buf.append(syms.getPercent());
268     buf.append(syms.getPerMill());
269     buf.append(syms.getZeroDigit());
270     buf.append('\u00a4');
271     return buf.toString();
272   }
273
274   private final void applyPatternWithSymbols (String pattern,
275                                               DecimalFormatSymbols syms)
276   {
277     // Initialize to the state the parser expects.
278     negativePrefix = "";
279     negativeSuffix = "";
280     positivePrefix = "";
281     positiveSuffix = "";
282     decimalSeparatorAlwaysShown = false;
283     groupingSize = 0;
284     minExponentDigits = 0;
285     multiplier = 1;
286     useExponentialNotation = false;
287     groupingUsed = false;
288     maximumFractionDigits = 0;
289     maximumIntegerDigits = 309;
290     minimumFractionDigits = 0;
291     minimumIntegerDigits = 1;
292
293     StringBuffer buf = new StringBuffer ();
294     String patChars = patternChars (syms);
295
296     int max = pattern.length();
297     int index = scanFix (pattern, 0, buf, patChars, syms, false);
298     positivePrefix = buf.toString();
299
300     index = scanFormat (pattern, index, patChars, syms, true);
301
302     index = scanFix (pattern, index, buf, patChars, syms, true);
303     positiveSuffix = buf.toString();
304
305     if (index == pattern.length())
306       {
307         // No negative info.
308         negativePrefix = null;
309         negativeSuffix = null;
310       }
311     else
312       {
313         if (pattern.charAt(index) != syms.getPatternSeparator())
314           throw new IllegalArgumentException ("separator character " +
315                                               "expected - index: " + index);
316
317         index = scanFix (pattern, index + 1, buf, patChars, syms, false);
318         negativePrefix = buf.toString();
319
320         // We parse the negative format for errors but we don't let
321         // it side-effect this object.
322         index = scanFormat (pattern, index, patChars, syms, false);
323
324         index = scanFix (pattern, index, buf, patChars, syms, true);
325         negativeSuffix = buf.toString();
326
327         if (index != pattern.length())
328           throw new IllegalArgumentException ("end of pattern expected " +
329                                               "- index: " + index);
330       }
331   }
332
333   public void applyLocalizedPattern (String pattern)
334   {
335     // JCL p. 638 claims this throws a ParseException but p. 629
336     // contradicts this.  Empirical tests with patterns of "0,###.0"
337     // and "#.#.#" corroborate the p. 629 statement that an
338     // IllegalArgumentException is thrown.
339     applyPatternWithSymbols (pattern, symbols);
340   }
341
342   public void applyPattern (String pattern)
343   {
344     // JCL p. 638 claims this throws a ParseException but p. 629
345     // contradicts this.  Empirical tests with patterns of "0,###.0"
346     // and "#.#.#" corroborate the p. 629 statement that an
347     // IllegalArgumentException is thrown.
348     applyPatternWithSymbols (pattern, nonLocalizedSymbols);
349   }
350
351   public Object clone ()
352   {
353     return new DecimalFormat (this);
354   }
355
356   private DecimalFormat (DecimalFormat dup)
357   {
358     decimalSeparatorAlwaysShown = dup.decimalSeparatorAlwaysShown;
359     groupingSize = dup.groupingSize;
360     minExponentDigits = dup.minExponentDigits;
361     multiplier = dup.multiplier;
362     negativePrefix = dup.negativePrefix;
363     negativeSuffix = dup.negativeSuffix;
364     positivePrefix = dup.positivePrefix;
365     positiveSuffix = dup.positiveSuffix;
366     symbols = (DecimalFormatSymbols) dup.symbols.clone();
367     useExponentialNotation = dup.useExponentialNotation;
368   }
369
370   public DecimalFormat ()
371   {
372     this ("#,##0.###");
373   }
374
375   public DecimalFormat (String pattern)
376   {
377     this (pattern, new DecimalFormatSymbols ());
378   }
379
380   public DecimalFormat (String pattern, DecimalFormatSymbols symbols)
381   {
382     this.symbols = symbols;
383     applyPattern (pattern);
384   }
385
386   private final boolean equals (String s1, String s2)
387   {
388     if (s1 == null || s2 == null)
389       return s1 == s2;
390     return s1.equals(s2);
391   }
392
393   public boolean equals (Object obj)
394   {
395     if (! (obj instanceof DecimalFormat))
396       return false;
397     DecimalFormat dup = (DecimalFormat) obj;
398     return (decimalSeparatorAlwaysShown == dup.decimalSeparatorAlwaysShown
399             && groupingSize == dup.groupingSize
400             && minExponentDigits == dup.minExponentDigits
401             && multiplier == dup.multiplier
402             && equals(negativePrefix, dup.negativePrefix)
403             && equals(negativeSuffix, dup.negativeSuffix)
404             && equals(positivePrefix, dup.positivePrefix)
405             && equals(positiveSuffix, dup.positiveSuffix)
406             && symbols.equals(dup.symbols)
407             && useExponentialNotation == dup.useExponentialNotation);
408   }
409
410   public StringBuffer format (double number, StringBuffer dest,
411                               FieldPosition fieldPos)
412   {
413     // A very special case.
414     if (Double.isNaN(number))
415       {
416         dest.append(symbols.getNaN());
417         if (fieldPos != null && fieldPos.getField() == INTEGER_FIELD)
418           {
419             int index = dest.length();
420             fieldPos.setBeginIndex(index - symbols.getNaN().length());
421             fieldPos.setEndIndex(index);
422           }
423         return dest;
424       }
425
426     boolean is_neg = number < 0;
427     if (is_neg)
428       {
429         if (negativePrefix != null)
430           dest.append(negativePrefix);
431         else
432           {
433             dest.append(symbols.getMinusSign());
434             dest.append(positivePrefix);
435           }
436         number = - number;
437       }
438     else
439       dest.append(positivePrefix);
440
441     int integerBeginIndex = dest.length();
442     int integerEndIndex = 0;
443     if (Double.isInfinite (number))
444       {
445         dest.append(symbols.getInfinity());
446         integerEndIndex = dest.length();
447       }
448     else
449       {
450         number *= multiplier;
451
452         // Compute exponent.
453         long exponent = 0;
454         double baseNumber;
455         if (useExponentialNotation)
456           {
457             exponent = (long) (Math.log(number) / Math.log(10));
458             if (minimumIntegerDigits > 0)
459               exponent -= minimumIntegerDigits - 1;
460             baseNumber = (long) (number / Math.pow(10.0, exponent));
461           }
462         else
463           baseNumber = number;
464
465         // Round to the correct number of digits.
466         baseNumber += 5 * Math.pow(10.0, - maximumFractionDigits - 1);
467
468         int index = dest.length();
469         double intPart = Math.floor(baseNumber);
470         int count = 0;
471         while (count < maximumIntegerDigits
472                && (intPart > 0 || count < minimumIntegerDigits))
473           {
474             long dig = (long) (intPart % 10);
475             intPart = Math.floor(intPart / 10);
476
477             // Append group separator if required.
478             if (groupingUsed && count > 0 && count % groupingSize == 0)
479               dest.insert(index, symbols.getGroupingSeparator());
480
481             dest.insert(index, (char) (symbols.getZeroDigit() + dig));
482
483             ++count;
484           }
485
486         integerEndIndex = dest.length();
487
488         int decimal_index = integerEndIndex;
489         int consecutive_zeros = 0;
490         int total_digits = 0;
491
492         // Strip integer part from NUMBER.
493         double fracPart = baseNumber - Math.floor(baseNumber);
494         for (count = 0;
495              count < maximumFractionDigits
496                && (fracPart != 0 || count < minimumFractionDigits);
497              ++count)
498           {
499             ++total_digits;
500             fracPart *= 10;
501             long dig = (long) fracPart;
502             if (dig == 0)
503               ++consecutive_zeros;
504             else
505               consecutive_zeros = 0;
506             dest.append((char) (symbols.getZeroDigit() + dig));
507
508             // Strip integer part from FRACPART.
509             fracPart = fracPart - Math.floor (fracPart);
510           }
511
512         // Strip extraneous trailing `0's.  We can't always detect
513         // these in the loop.
514         int extra_zeros = Math.min (consecutive_zeros,
515                                     total_digits - minimumFractionDigits);
516         if (extra_zeros > 0)
517           {
518             dest.setLength(dest.length() - extra_zeros);
519             total_digits -= extra_zeros;
520           }
521
522         // If required, add the decimal symbol.
523         if (decimalSeparatorAlwaysShown
524             || total_digits > 0)
525           {
526             dest.insert(decimal_index, symbols.getDecimalSeparator());
527             if (fieldPos != null && fieldPos.getField() == FRACTION_FIELD)
528               {
529                 fieldPos.setBeginIndex(decimal_index + 1);
530                 fieldPos.setEndIndex(dest.length());
531               }
532           }
533
534         // Finally, print the exponent.
535         if (useExponentialNotation)
536           {
537             dest.append(symbols.getExponential());
538             dest.append(exponent < 0 ? '-' : '+');
539             index = dest.length();
540             for (count = 0;
541                  exponent > 0 || count < minExponentDigits;
542                  ++count)
543               {
544                 long dig = exponent % 10;
545                 exponent /= 10;
546                 dest.insert(index, (char) (symbols.getZeroDigit() + dig));
547               }
548           }
549       }
550
551     if (fieldPos != null && fieldPos.getField() == INTEGER_FIELD)
552       {
553         fieldPos.setBeginIndex(integerBeginIndex);
554         fieldPos.setEndIndex(integerEndIndex);
555       }
556
557     dest.append((is_neg && negativeSuffix != null)
558                 ? negativeSuffix
559                 : positiveSuffix);
560     return dest;
561   }
562
563   public StringBuffer format (long number, StringBuffer dest,
564                               FieldPosition fieldPos)
565   {
566     // If using exponential notation, we just format as a double.
567     if (useExponentialNotation)
568       return format ((double) number, dest, fieldPos);
569
570     boolean is_neg = number < 0;
571     if (is_neg)
572       {
573         if (negativePrefix != null)
574           dest.append(negativePrefix);
575         else
576           {
577             dest.append(symbols.getMinusSign());
578             dest.append(positivePrefix);
579           }
580         number = - number;
581       }
582     else
583       dest.append(positivePrefix);
584
585     int integerBeginIndex = dest.length();
586     int index = dest.length();
587     int count = 0;
588     while (count < maximumIntegerDigits
589            && (number > 0 || count < minimumIntegerDigits))
590       {
591         long dig = number % 10;
592         number /= 10;
593         // NUMBER and DIG will be less than 0 if the original number
594         // was the most negative long.
595         if (dig < 0)
596           {
597             dig = - dig;
598             number = - number;
599           }
600
601         // Append group separator if required.
602         if (groupingUsed && count > 0 && count % groupingSize == 0)
603           dest.insert(index, symbols.getGroupingSeparator());
604
605         dest.insert(index, (char) (symbols.getZeroDigit() + dig));
606
607         ++count;
608       }
609
610     if (fieldPos != null && fieldPos.getField() == INTEGER_FIELD)
611       {
612         fieldPos.setBeginIndex(integerBeginIndex);
613         fieldPos.setEndIndex(dest.length());
614       }
615
616     if (decimalSeparatorAlwaysShown || minimumFractionDigits > 0)
617       {
618         dest.append(symbols.getDecimalSeparator());
619         if (fieldPos != null && fieldPos.getField() == FRACTION_FIELD)
620           {
621             fieldPos.setBeginIndex(dest.length());
622             fieldPos.setEndIndex(dest.length() + minimumFractionDigits);
623           }
624       }
625
626     for (count = 0; count < minimumFractionDigits; ++count)
627       dest.append(symbols.getZeroDigit());
628
629     dest.append((is_neg && negativeSuffix != null)
630                 ? negativeSuffix
631                 : positiveSuffix);
632     return dest;
633   }
634
635   public DecimalFormatSymbols getDecimalFormatSymbols ()
636   {
637     return symbols;
638   }
639
640   public int getGroupingSize ()
641   {
642     return groupingSize;
643   }
644
645   public int getMultiplier ()
646   {
647     return multiplier;
648   }
649
650   public String getNegativePrefix ()
651   {
652     return negativePrefix;
653   }
654
655   public String getNegativeSuffix ()
656   {
657     return negativeSuffix;
658   }
659
660   public String getPositivePrefix ()
661   {
662     return positivePrefix;
663   }
664
665   public String getPositiveSuffix ()
666   {
667     return positiveSuffix;
668   }
669
670   public int hashCode ()
671   {
672     int hash = (negativeSuffix.hashCode() ^ negativePrefix.hashCode()
673                 ^positivePrefix.hashCode() ^ positiveSuffix.hashCode());
674     // FIXME.
675     return hash;
676   }
677
678   public boolean isDecimalSeparatorAlwaysShown ()
679   {
680     return decimalSeparatorAlwaysShown;
681   }
682
683   public Number parse (String str, ParsePosition pos)
684   {
685     // Our strategy is simple: copy the text into a buffer,
686     // translating or omitting locale-specific information.  Then
687     // let Double or Long convert the number for us.
688
689     boolean is_neg = false;
690     int index = pos.getIndex();
691     StringBuffer buf = new StringBuffer ();
692
693       // We have to check both prefixes, because one might be empty.
694       // We want to pick the longest prefix that matches.
695     boolean got_pos = str.startsWith(positivePrefix, index);
696     String np = (negativePrefix != null
697                  ? negativePrefix
698                  : positivePrefix + symbols.getMinusSign());
699     boolean got_neg = str.startsWith(np, index);
700
701     if (got_pos && got_neg)
702       {
703         // By checking this way, we preserve ambiguity in the case
704         // where the negative format differs only in suffix.  We
705         // check this again later.
706         if (np.length() > positivePrefix.length())
707           {
708             is_neg = true;
709             index += np.length();
710           }
711         else
712           index += positivePrefix.length();
713       }
714     else if (got_neg)
715       {
716         is_neg = true;
717         index += np.length();
718       }
719     else if (got_pos)
720       index += positivePrefix.length();
721     else
722       {
723         pos.setErrorIndex (index);
724         return null;
725       }
726
727     // FIXME: handle Inf and NaN.
728
729       // FIXME: do we have to respect minimum/maxmimum digit stuff?
730       // What about leading zeros?  What about multiplier?
731
732     int start_index = index;
733     int max = str.length();
734     char zero = symbols.getZeroDigit();
735     int last_group = -1;
736     boolean int_part = true;
737     boolean exp_part = false;
738     for (; index < max; ++index)
739       {
740         char c = str.charAt(index);
741
742         // FIXME: what about grouping size?
743         if (groupingUsed && c == symbols.getGroupingSeparator())
744           {
745             if (last_group != -1
746                 && (index - last_group) % groupingSize != 0)
747               {
748                 pos.setErrorIndex(index);
749                 return null;
750               }
751             last_group = index;
752           }
753         else if (c >= zero && c <= zero + 9)
754           {
755             buf.append((char) (c - zero + '0'));
756             exp_part = false;
757           }
758         else if (parseIntegerOnly)
759           break;
760         else if (c == symbols.getDecimalSeparator())
761           {
762             if (last_group != -1
763                 && (index - last_group) % groupingSize != 0)
764               {
765                 pos.setErrorIndex(index);
766                 return null;
767               }
768             buf.append('.');
769             int_part = false;
770           }
771         else if (c == symbols.getExponential())
772           {
773             buf.append('E');
774             int_part = false;
775             exp_part = true;
776           }
777         else if (exp_part
778                  && (c == '+' || c == '-' || c == symbols.getMinusSign()))
779           {
780             // For exponential notation.
781             buf.append(c);
782           }
783         else
784           break;
785       }
786
787     if (index == start_index)
788       {
789         // Didn't see any digits.
790         pos.setErrorIndex(index);
791         return null;
792       }
793
794     // Check the suffix.  We must do this before converting the
795     // buffer to a number to handle the case of a number which is
796     // the most negative Long.
797     boolean got_pos_suf = str.startsWith(positiveSuffix, index);
798     String ns = (negativePrefix == null ? positiveSuffix : negativeSuffix);
799     boolean got_neg_suf = str.startsWith(ns, index);
800     if (is_neg)
801       {
802         if (! got_neg_suf)
803           {
804             pos.setErrorIndex(index);
805             return null;
806           }
807       }
808     else if (got_pos && got_neg && got_neg_suf)
809       {
810         is_neg = true;
811       }
812     else if (got_pos != got_pos_suf && got_neg != got_neg_suf)
813       {
814         pos.setErrorIndex(index);
815         return null;
816       }
817
818     String suffix = is_neg ? ns : positiveSuffix;
819     if (is_neg)
820       buf.insert(0, '-');
821
822     String t = buf.toString();
823     Number result = null;
824     try
825       {
826         result = new Long (t);
827       }
828     catch (NumberFormatException x1)
829       {
830         try
831           {
832             result = new Double (t);
833           }
834         catch (NumberFormatException x2)
835           {
836           }
837       }
838     if (result == null)
839       {
840         pos.setErrorIndex(index);
841         return null;
842       }
843
844     pos.setIndex(index + suffix.length());
845
846     return result;
847   }
848
849   public void setDecimalFormatSymbols (DecimalFormatSymbols newSymbols)
850   {
851     symbols = newSymbols;
852   }
853
854   public void setDecimalSeparatorAlwaysShown (boolean newValue)
855   {
856     decimalSeparatorAlwaysShown = newValue;
857   }
858
859   public void setGroupingSize (int groupSize)
860   {
861     groupingSize = (byte) groupSize;
862   }
863
864   public void setMaximumFractionDigits (int newValue)
865   {
866     maximumFractionDigits = Math.min(newValue, 340);
867   }
868
869   public void setMaximumIntegerDigits (int newValue)
870   {
871     maximumIntegerDigits = Math.min(newValue, 309);
872   }
873
874   public void setMinimumFractionDigits (int newValue)
875   {
876     minimumFractionDigits = Math.min(newValue, 340);
877   }
878
879   public void setMinimumIntegerDigits (int newValue)
880   {
881     minimumIntegerDigits = Math.min(newValue, 309);
882   }
883
884   public void setMultiplier (int newValue)
885   {
886     multiplier = newValue;
887   }
888
889   public void setNegativePrefix (String newValue)
890   {
891     negativePrefix = newValue;
892   }
893
894   public void setNegativeSuffix (String newValue)
895   {
896     negativeSuffix = newValue;
897   }
898
899   public void setPositivePrefix (String newValue)
900   {
901     positivePrefix = newValue;
902   }
903
904   public void setPositiveSuffix (String newValue)
905   {
906     positiveSuffix = newValue;
907   }
908
909   private final void quoteFix (StringBuffer buf, String text, String patChars)
910   {
911     int len = text.length();
912     for (int index = 0; index < len; ++index)
913       {
914         char c = text.charAt(index);
915         if (patChars.indexOf(c) != -1)
916           {
917             buf.append('\'');
918             buf.append(c);
919             buf.append('\'');
920           }
921         else
922           buf.append(c);
923       }
924   }
925
926   private final String computePattern (DecimalFormatSymbols syms)
927   {
928     StringBuffer mainPattern = new StringBuffer ();
929     // We have to at least emit a zero for the minimum number of
930     // digits.  Past that we need hash marks up to the grouping
931     // separator (and one beyond).
932     int total_digits = Math.max(minimumIntegerDigits,
933                                 groupingUsed ? groupingSize + 1: 0);
934     for (int i = 0; i < total_digits - minimumIntegerDigits; ++i)
935       mainPattern.append(syms.getDigit());
936     for (int i = total_digits - minimumIntegerDigits; i < total_digits; ++i)
937       mainPattern.append(syms.getZeroDigit());
938     // Inserting the gropuing operator afterwards is easier.
939     if (groupingUsed)
940       mainPattern.insert(mainPattern.length() - groupingSize,
941                          syms.getGroupingSeparator());
942     // See if we need decimal info.
943     if (minimumFractionDigits > 0 || maximumFractionDigits > 0
944         || decimalSeparatorAlwaysShown)
945       mainPattern.append(syms.getDecimalSeparator());
946     for (int i = 0; i < minimumFractionDigits; ++i)
947       mainPattern.append(syms.getZeroDigit());
948     for (int i = minimumFractionDigits; i < maximumFractionDigits; ++i)
949       mainPattern.append(syms.getDigit());
950     if (useExponentialNotation)
951       {
952         mainPattern.append(syms.getExponential());
953         for (int i = 0; i < minExponentDigits; ++i)
954           mainPattern.append(syms.getZeroDigit());
955         if (minExponentDigits == 0)
956           mainPattern.append(syms.getDigit());
957       }
958
959     String main = mainPattern.toString();
960     String patChars = patternChars (syms);
961     mainPattern.setLength(0);
962
963     quoteFix (mainPattern, positivePrefix, patChars);
964     mainPattern.append(main);
965     quoteFix (mainPattern, positiveSuffix, patChars);
966
967     if (negativePrefix != null)
968       {
969         quoteFix (mainPattern, negativePrefix, patChars);
970         mainPattern.append(main);
971         quoteFix (mainPattern, negativeSuffix, patChars);
972       }
973
974     return mainPattern.toString();
975   }
976
977   public String toLocalizedPattern ()
978   {
979     return computePattern (symbols);
980   }
981
982   public String toPattern ()
983   {
984     return computePattern (nonLocalizedSymbols);
985   }
986
987   // These names are fixed by the serialization spec.
988   private boolean decimalSeparatorAlwaysShown;
989   private byte groupingSize;
990   private byte minExponentDigits;
991   private int multiplier;
992   private String negativePrefix;
993   private String negativeSuffix;
994   private String positivePrefix;
995   private String positiveSuffix;
996   private int serialVersionOnStream = 1;
997   private DecimalFormatSymbols symbols;
998   private boolean useExponentialNotation;
999   private static final long serialVersionUID = 864413376551465018L;
1000
1001   private void readObject(ObjectInputStream stream)
1002     throws IOException, ClassNotFoundException
1003   {
1004     stream.defaultReadObject();
1005     if (serialVersionOnStream < 1)
1006       {
1007         useExponentialNotation = false;
1008         serialVersionOnStream = 1;
1009       }
1010   }
1011
1012   // The locale-independent pattern symbols happen to be the same as
1013   // the US symbols.
1014   private static final DecimalFormatSymbols nonLocalizedSymbols
1015     = new DecimalFormatSymbols (Locale.US);
1016 }