// DecimalFormat.java - Localized number formatting.

/* Copyright (C) 1999  Free Software Foundation

   This file is part of libgcj.

This software is copyrighted work licensed under the terms of the
Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
details.  */

package java.text;

import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

/**
 * @author Tom Tromey <tromey@cygnus.com>
 * @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, except serialization.
 * Note however that the docs are very unclear about how format parsing
 * should work.  No doubt there are problems here.
 */

public class DecimalFormat extends NumberFormat
{
  // This is a helper for applyPatternWithSymbols.  It reads a prefix
  // or a suffix.  It can cause some side-effects.
  private final int scanFix (String pattern, int index, StringBuffer buf,
			     String patChars, DecimalFormatSymbols syms,
			     boolean is_suffix)
    {
      int len = pattern.length();
      buf.setLength(0);
      boolean multiplierSet = false;
      while (index < len)
	{
	  char c = pattern.charAt(index);
	  if (c == '\'' && index + 1 < len
	      && pattern.charAt(index + 1) == '\'')
	    {
	      buf.append(c);
	      ++index;
	    }
	  else if (c == '\'' && index + 2 < len
		   && pattern.charAt(index + 2) == '\'')
	    {
	      buf.append(pattern.charAt(index + 1));
	      index += 2;
	    }
	  else if (c == '\u00a4')
	    {
	      if (index + 1 < len && pattern.charAt(index + 1) == '\u00a4')
		{
		  buf.append(syms.getInternationalCurrencySymbol());
		  ++index;
		}
	      else
		buf.append(syms.getCurrencySymbol());
	    }
	  else if (is_suffix && c == syms.getPercent())
	    {
	      if (multiplierSet)
		throw new IllegalArgumentException ("multiplier already set " +
						    "- index: " + index);
	      multiplierSet = true;
	      multiplier = 100;
	      buf.append(c);
	    }
	  else if (is_suffix && c == syms.getPerMill())
	    {
	      if (multiplierSet)
		throw new IllegalArgumentException ("multiplier already set " +
						    "- index: " + index);
	      multiplierSet = true;
	      multiplier = 1000;
	      buf.append(c);
	    }
	  else if (patChars.indexOf(c) != -1)
	    {
	      // This is a pattern character.
	      break;
	    }
	  else
	    buf.append(c);
	  ++index;
	}

      return index;
    }

  // A helper which reads a number format.
  private final int scanFormat (String pattern, int index,
				String patChars, DecimalFormatSymbols syms,
				boolean is_positive)
    {
      int max = pattern.length();

      int countSinceGroup = 0;
      int zeroCount = 0;
      boolean saw_group = false;

      //
      // Scan integer part.
      //
      while (index < max)
	{
	  char c = pattern.charAt(index);

	  if (c == syms.getDigit())
	    {
	      if (zeroCount > 0)
		throw new IllegalArgumentException ("digit mark following " +
						    "zero - index: " + index);
	      ++countSinceGroup;
	    }
	  else if (c == syms.getZeroDigit())
	    {
	      ++zeroCount;
	      ++countSinceGroup;
	    }
	  else if (c == syms.getGroupingSeparator())
	    {
	      countSinceGroup = 0;
	      saw_group = true;
	    }
	  else
	    break;

	  ++index;
	}

      // We can only side-effect when parsing the positive format.
      if (is_positive)
	{
	  groupingUsed = saw_group;
	  groupingSize = (byte) countSinceGroup;
	  minimumIntegerDigits = zeroCount;
	}

      // Early termination.
      if (index == max || pattern.charAt(index) == syms.getGroupingSeparator())
	{
	  if (is_positive)
	    decimalSeparatorAlwaysShown = false;
	  return index;
	}

      if (pattern.charAt(index) == syms.getDecimalSeparator())
	{
	  ++index;

	  //
	  // Scan fractional part.
	  //
	  int hashCount = 0;
	  zeroCount = 0;
	  while (index < max)
	    {
	      char c = pattern.charAt(index);
	      if (c == syms.getZeroDigit())
		{
		  if (hashCount > 0)
		    throw new IllegalArgumentException ("zero mark " +
					"following digit - index: " + index);
		  ++zeroCount;
		}
	      else if (c == syms.getDigit())
		{
		  ++hashCount;
		}
	      else if (c != syms.getExponential()
		       && c != syms.getPatternSeparator()
		       && patChars.indexOf(c) != -1)
		throw new IllegalArgumentException ("unexpected special " +
						"character - index: " + index);
	      else
		break;

	      ++index;
	    }

	  if (is_positive)
	    {
	      maximumFractionDigits = hashCount + zeroCount;
	      minimumFractionDigits = zeroCount;
	    }

	  if (index == max)
	    return index;
	}

      if (pattern.charAt(index) == syms.getExponential())
	{
	  //
	  // Scan exponential format.
	  //
	  zeroCount = 0;
	  ++index;
	  while (index < max)
	    {
	      char c = pattern.charAt(index);
	      if (c == syms.getZeroDigit())
		++zeroCount;
	      else if (c == syms.getDigit())
		{
		  if (zeroCount > 0)
		    throw new
		      IllegalArgumentException ("digit mark following zero " +
						"in exponent - index: " +
						index);
		}
	      else if (patChars.indexOf(c) != -1)
		throw new IllegalArgumentException ("unexpected special " +
						    "character - index: " +
						    index);
	      else
		break;

	      ++index;
	    }

	  if (is_positive)
	    {
	      useExponentialNotation = true;
	      minExponentDigits = (byte) zeroCount;
	    }
	}

      return index;
    }

  // This helper function creates a string consisting of all the
  // characters which can appear in a pattern and must be quoted.
  private final String patternChars (DecimalFormatSymbols syms)
    {
      StringBuffer buf = new StringBuffer ();
      buf.append(syms.getDecimalSeparator());
      buf.append(syms.getDigit());
      buf.append(syms.getExponential());
      buf.append(syms.getGroupingSeparator());
      // Adding this one causes pattern application to fail.
      // Of course, omitting is causes toPattern to fail.
      // ... but we already have bugs there.  FIXME.
      // buf.append(syms.getMinusSign());
      buf.append(syms.getPatternSeparator());
      buf.append(syms.getPercent());
      buf.append(syms.getPerMill());
      buf.append(syms.getZeroDigit());
      buf.append('\u00a4');
      return buf.toString();
    }

  private final void applyPatternWithSymbols (String pattern,
					      DecimalFormatSymbols syms)
    {
      // Initialize to the state the parser expects.
      negativePrefix = "";
      negativeSuffix = "";
      positivePrefix = "";
      positiveSuffix = "";
      decimalSeparatorAlwaysShown = false;
      groupingSize = 0;
      minExponentDigits = 0;
      multiplier = 1;
      useExponentialNotation = false;
      groupingUsed = false;
      maximumFractionDigits = 0;
      maximumIntegerDigits = 309;
      minimumFractionDigits = 0;
      minimumIntegerDigits = 1;

      StringBuffer buf = new StringBuffer ();
      String patChars = patternChars (syms);

      int max = pattern.length();
      int index = scanFix (pattern, 0, buf, patChars, syms, false);
      positivePrefix = buf.toString();

      index = scanFormat (pattern, index, patChars, syms, true);

      index = scanFix (pattern, index, buf, patChars, syms, true);
      positiveSuffix = buf.toString();

      if (index == pattern.length())
	{
	  // No negative info.
	  negativePrefix = null;
	  negativeSuffix = null;
	}
      else
	{
	  if (pattern.charAt(index) != syms.getPatternSeparator())
	    throw new IllegalArgumentException ("separator character " +
						"expected - index: " + index);

	  index = scanFix (pattern, index + 1, buf, patChars, syms, false);
	  negativePrefix = buf.toString();

	  // We parse the negative format for errors but we don't let
	  // it side-effect this object.
	  index = scanFormat (pattern, index, patChars, syms, false);

	  index = scanFix (pattern, index, buf, patChars, syms, true);
	  negativeSuffix = buf.toString();

	  if (index != pattern.length())
	    throw new IllegalArgumentException ("end of pattern expected " +
						"- index: " + index);
	}
    }

  public void applyLocalizedPattern (String pattern)
    {
      // JCL p. 638 claims this throws a ParseException but p. 629
      // contradicts this.  Empirical tests with patterns of "0,###.0"
      // and "#.#.#" corroborate the p. 629 statement that an
      // IllegalArgumentException is thrown.
      applyPatternWithSymbols (pattern, symbols);
    }

  public void applyPattern (String pattern)
    {
      // JCL p. 638 claims this throws a ParseException but p. 629
      // contradicts this.  Empirical tests with patterns of "0,###.0"
      // and "#.#.#" corroborate the p. 629 statement that an
      // IllegalArgumentException is thrown.
      applyPatternWithSymbols (pattern, nonLocalizedSymbols);
    }

  public Object clone ()
    {
      return new DecimalFormat (this);
    }

  private DecimalFormat (DecimalFormat dup)
    {
      decimalSeparatorAlwaysShown = dup.decimalSeparatorAlwaysShown;
      groupingSize = dup.groupingSize;
      minExponentDigits = dup.minExponentDigits;
      multiplier = dup.multiplier;
      negativePrefix = dup.negativePrefix;
      negativeSuffix = dup.negativeSuffix;
      positivePrefix = dup.positivePrefix;
      positiveSuffix = dup.positiveSuffix;
      symbols = (DecimalFormatSymbols) dup.symbols.clone();
      useExponentialNotation = dup.useExponentialNotation;
    }

  public DecimalFormat ()
    {
      this ("#,##0.###");
    }

  public DecimalFormat (String pattern)
    {
      this (pattern, new DecimalFormatSymbols ());
    }

  public DecimalFormat (String pattern, DecimalFormatSymbols symbols)
    {
      this.symbols = symbols;
      applyPattern (pattern);
    }

  private final boolean equals (String s1, String s2)
    {
      if (s1 == null || s2 == null)
	return s1 == s2;
      return s1.equals(s2);
    }

  public boolean equals (Object obj)
    {
      if (! (obj instanceof DecimalFormat))
	return false;
      DecimalFormat dup = (DecimalFormat) obj;
      return (decimalSeparatorAlwaysShown == dup.decimalSeparatorAlwaysShown
	      && groupingSize == dup.groupingSize
	      && minExponentDigits == dup.minExponentDigits
	      && multiplier == dup.multiplier
	      && equals(negativePrefix, dup.negativePrefix)
	      && equals(negativeSuffix, dup.negativeSuffix)
	      && equals(positivePrefix, dup.positivePrefix)
	      && equals(positiveSuffix, dup.positiveSuffix)
	      && symbols.equals(dup.symbols)
	      && useExponentialNotation == dup.useExponentialNotation);
    }

  public StringBuffer format (double number, StringBuffer dest,
			      FieldPosition fieldPos)
    {
      // A very special case.
      if (Double.isNaN(number))
	{
	  dest.append(symbols.getNaN());
	  if (fieldPos != null && fieldPos.getField() == INTEGER_FIELD)
	    {
	      int index = dest.length();
	      fieldPos.setBeginIndex(index - symbols.getNaN().length());
	      fieldPos.setEndIndex(index);
	    }
	  return dest;
	}

      boolean is_neg = number < 0;
      if (is_neg)
	{
	  if (negativePrefix != null)
	    dest.append(negativePrefix);
	  else
	    {
	      dest.append(symbols.getMinusSign());
	      dest.append(positivePrefix);
	    }
	  number = - number;
	}
      else
	dest.append(positivePrefix);

      int integerBeginIndex = dest.length();
      int integerEndIndex = 0;
      if (Double.isInfinite (number))
	{
	  dest.append(symbols.getInfinity());
	  integerEndIndex = dest.length();
	}
      else
	{
	  number *= multiplier;

	  // Compute exponent.
	  long exponent = 0;
	  double baseNumber;
	  if (useExponentialNotation)
	    {
	      exponent = (long) (Math.log(number) / Math.log(10));
	      if (minimumIntegerDigits > 0)
		exponent -= minimumIntegerDigits - 1;
	      baseNumber = (long) (number / Math.pow(10.0, exponent));
	    }
	  else
	    baseNumber = number;

	  // Round to the correct number of digits.
	  baseNumber += 5 * Math.pow(10.0, - maximumFractionDigits - 1);

	  int index = dest.length();
	  double intPart = Math.floor(baseNumber);
	  int count = 0;
	  while (count < maximumIntegerDigits
		 && (intPart > 0 || count < minimumIntegerDigits))
	    {
	      long dig = (long) (intPart % 10);
	      intPart = Math.floor(intPart / 10);

	      // Append group separator if required.
	      if (groupingUsed && count > 0 && count % groupingSize == 0)
		dest.insert(index, symbols.getGroupingSeparator());

	      dest.insert(index, (char) (symbols.getZeroDigit() + dig));

	      ++count;
	    }

	  integerEndIndex = dest.length();

	  int decimal_index = integerEndIndex;
	  int consecutive_zeros = 0;
	  int total_digits = 0;

	  // Strip integer part from NUMBER.
	  double fracPart = baseNumber - Math.floor(baseNumber);
	  for (count = 0;
	       count < maximumFractionDigits
		 && (fracPart != 0 || count < minimumFractionDigits);
	       ++count)
	    {
	      ++total_digits;
	      fracPart *= 10;
	      long dig = (long) fracPart;
	      if (dig == 0)
		++consecutive_zeros;
	      else
		consecutive_zeros = 0;
	      dest.append((char) (symbols.getZeroDigit() + dig));

	      // Strip integer part from FRACPART.
	      fracPart = fracPart - Math.floor (fracPart);
	    }

	  // Strip extraneous trailing `0's.  We can't always detect
	  // these in the loop.
	  int extra_zeros = Math.min (consecutive_zeros,
				      total_digits - minimumFractionDigits);
	  if (extra_zeros > 0)
	    {
	      dest.setLength(dest.length() - extra_zeros);
	      total_digits -= extra_zeros;
	    }

	  // If required, add the decimal symbol.
	  if (decimalSeparatorAlwaysShown
	      || total_digits > 0)
	    {
	      dest.insert(decimal_index, symbols.getDecimalSeparator());
	      if (fieldPos != null && fieldPos.getField() == FRACTION_FIELD)
		{
		  fieldPos.setBeginIndex(decimal_index + 1);
		  fieldPos.setEndIndex(dest.length());
		}
	    }

	  // Finally, print the exponent.
	  if (useExponentialNotation)
	    {
	      dest.append(symbols.getExponential());
	      dest.append(exponent < 0 ? '-' : '+');
	      index = dest.length();
	      for (count = 0;
		   exponent > 0 || count < minExponentDigits;
		   ++count)
		{
		  long dig = exponent % 10;
		  exponent /= 10;
		  dest.insert(index, (char) (symbols.getZeroDigit() + dig));
		}
	    }
	}

      if (fieldPos != null && fieldPos.getField() == INTEGER_FIELD)
	{
	  fieldPos.setBeginIndex(integerBeginIndex);
	  fieldPos.setEndIndex(integerEndIndex);
	}

      dest.append((is_neg && negativeSuffix != null)
		  ? negativeSuffix
		  : positiveSuffix);
      return dest;
    }

  public StringBuffer format (long number, StringBuffer dest,
			      FieldPosition fieldPos)
    {
      // If using exponential notation, we just format as a double.
      if (useExponentialNotation)
	return format ((double) number, dest, fieldPos);

      boolean is_neg = number < 0;
      if (is_neg)
	{
	  if (negativePrefix != null)
	    dest.append(negativePrefix);
	  else
	    {
	      dest.append(symbols.getMinusSign());
	      dest.append(positivePrefix);
	    }
	  number = - number;
	}
      else
	dest.append(positivePrefix);

      int integerBeginIndex = dest.length();
      int index = dest.length();
      int count = 0;
      while (count < maximumIntegerDigits
	     && (number > 0 || count < minimumIntegerDigits))
	{
	  long dig = number % 10;
	  number /= 10;
	  // NUMBER and DIG will be less than 0 if the original number
	  // was the most negative long.
	  if (dig < 0)
	    {
	      dig = - dig;
	      number = - number;
	    }

	  // Append group separator if required.
	  if (groupingUsed && count > 0 && count % groupingSize == 0)
	    dest.insert(index, symbols.getGroupingSeparator());

	  dest.insert(index, (char) (symbols.getZeroDigit() + dig));

	  ++count;
	}

      if (fieldPos != null && fieldPos.getField() == INTEGER_FIELD)
	{
	  fieldPos.setBeginIndex(integerBeginIndex);
	  fieldPos.setEndIndex(dest.length());
	}

      if (decimalSeparatorAlwaysShown || minimumFractionDigits > 0)
	{
	  dest.append(symbols.getDecimalSeparator());
	  if (fieldPos != null && fieldPos.getField() == FRACTION_FIELD)
	    {
	      fieldPos.setBeginIndex(dest.length());
	      fieldPos.setEndIndex(dest.length() + minimumFractionDigits);
	    }
	}

      for (count = 0; count < minimumFractionDigits; ++count)
	dest.append(symbols.getZeroDigit());

      dest.append((is_neg && negativeSuffix != null)
		  ? negativeSuffix
		  : positiveSuffix);
      return dest;
    }

  public DecimalFormatSymbols getDecimalFormatSymbols ()
    {
      return symbols;
    }

  public int getGroupingSize ()
    {
      return groupingSize;
    }

  public int getMultiplier ()
    {
      return multiplier;
    }

  public String getNegativePrefix ()
    {
      return negativePrefix;
    }

  public String getNegativeSuffix ()
    {
      return negativeSuffix;
    }

  public String getPositivePrefix ()
    {
      return positivePrefix;
    }

  public String getPositiveSuffix ()
    {
      return positiveSuffix;
    }

  public int hashCode ()
    {
      int hash = (negativeSuffix.hashCode() ^ negativePrefix.hashCode()
		  ^positivePrefix.hashCode() ^ positiveSuffix.hashCode());
      // FIXME.
      return hash;
    }

  public boolean isDecimalSeparatorAlwaysShown ()
    {
      return decimalSeparatorAlwaysShown;
    }

  public Number parse (String str, ParsePosition pos)
    {
      // Our strategy is simple: copy the text into a buffer,
      // translating or omitting locale-specific information.  Then
      // let Double or Long convert the number for us.

      boolean is_neg = false;
      int index = pos.getIndex();
      StringBuffer buf = new StringBuffer ();

      // We have to check both prefixes, because one might be empty.
      // We want to pick the longest prefix that matches.
      boolean got_pos = str.startsWith(positivePrefix, index);
      String np = (negativePrefix != null
		   ? negativePrefix
		   : positivePrefix + symbols.getMinusSign());
      boolean got_neg = str.startsWith(np, index);

      if (got_pos && got_neg)
	{
	  // By checking this way, we preserve ambiguity in the case
	  // where the negative format differs only in suffix.  We
	  // check this again later.
	  if (np.length() > positivePrefix.length())
	    {
	      is_neg = true;
	      index += np.length();
	    }
	  else
	    index += positivePrefix.length();
	}
      else if (got_neg)
	{
	  is_neg = true;
	  index += np.length();
	}
      else if (got_pos)
	index += positivePrefix.length();
      else
	{
	  pos.setErrorIndex (index);
	  return null;
	}

      // FIXME: handle Inf and NaN.

      // FIXME: do we have to respect minimum/maxmimum digit stuff?
      // What about leading zeros?  What about multiplier?

      int start_index = index;
      int max = str.length();
      char zero = symbols.getZeroDigit();
      int last_group = -1;
      boolean int_part = true;
      boolean exp_part = false;
      for (; index < max; ++index)
	{
	  char c = str.charAt(index);

	  // FIXME: what about grouping size?
	  if (groupingUsed && c == symbols.getGroupingSeparator())
	    {
	      if (last_group != -1
		  && (index - last_group) % groupingSize != 0)
		{
		  pos.setErrorIndex(index);
		  return null;
		}
	      last_group = index;
	    }
	  else if (c >= zero && c <= zero + 9)
	    {
	      buf.append((char) (c - zero + '0'));
	      exp_part = false;
	    }
	  else if (parseIntegerOnly)
	    break;
	  else if (c == symbols.getDecimalSeparator())
	    {
	      if (last_group != -1
		  && (index - last_group) % groupingSize != 0)
		{
		  pos.setErrorIndex(index);
		  return null;
		}
	      buf.append('.');
	      int_part = false;
	    }
	  else if (c == symbols.getExponential())
	    {
	      buf.append('E');
	      int_part = false;
	      exp_part = true;
	    }
	  else if (exp_part
		   && (c == '+' || c == '-' || c == symbols.getMinusSign()))
	    {
	      // For exponential notation.
	      buf.append(c);
	    }
	  else
	    break;
	}

      if (index == start_index)
	{
	  // Didn't see any digits.
	  pos.setErrorIndex(index);
	  return null;
	}

      // Check the suffix.  We must do this before converting the
      // buffer to a number to handle the case of a number which is
      // the most negative Long.
      boolean got_pos_suf = str.startsWith(positiveSuffix, index);
      String ns = (negativePrefix == null ? positiveSuffix : negativeSuffix);
      boolean got_neg_suf = str.startsWith(ns, index);
      if (is_neg)
	{
	  if (! got_neg_suf)
	    {
	      pos.setErrorIndex(index);
	      return null;
	    }
	}
      else if (got_pos && got_neg && got_neg_suf)
	{
	  is_neg = true;
	}
      else if (got_pos != got_pos_suf && got_neg != got_neg_suf)
	{
	  pos.setErrorIndex(index);
	  return null;
	}

      String suffix = is_neg ? ns : positiveSuffix;
      if (is_neg)
	buf.insert(0, '-');

      String t = buf.toString();
      Number result = null;
      try
	{
	  result = new Long (t);
	}
      catch (NumberFormatException x1)
	{
	  try
	    {
	      result = new Double (t);
	    }
	  catch (NumberFormatException x2)
	    {
	    }
	}
      if (result == null)
	{
	  pos.setErrorIndex(index);
	  return null;
	}

      pos.setIndex(index + suffix.length());

      return result;
    }

  public void setDecimalFormatSymbols (DecimalFormatSymbols newSymbols)
    {
      symbols = newSymbols;
    }

  public void setDecimalSeparatorAlwaysShown (boolean newValue)
    {
      decimalSeparatorAlwaysShown = newValue;
    }

  public void setGroupingSize (int groupSize)
    {
      groupingSize = (byte) groupSize;
    }

  public void setMaximumFractionDigits (int newValue)
    {
      maximumFractionDigits = Math.min(newValue, 340);
    }

  public void setMaximumIntegerDigits (int newValue)
    {
      maximumIntegerDigits = Math.min(newValue, 309);
    }

  public void setMinimumFractionDigits (int newValue)
    {
      minimumFractionDigits = Math.min(newValue, 340);
    }

  public void setMinimumIntegerDigits (int newValue)
    {
      minimumIntegerDigits = Math.min(newValue, 309);
    }

  public void setMultiplier (int newValue)
    {
      multiplier = newValue;
    }

  public void setNegativePrefix (String newValue)
    {
      negativePrefix = newValue;
    }

  public void setNegativeSuffix (String newValue)
    {
      negativeSuffix = newValue;
    }

  public void setPositivePrefix (String newValue)
    {
      positivePrefix = newValue;
    }

  public void setPositiveSuffix (String newValue)
    {
      positiveSuffix = newValue;
    }

  private final void quoteFix (StringBuffer buf, String text, String patChars)
    {
      int len = text.length();
      for (int index = 0; index < len; ++index)
	{
	  char c = text.charAt(index);
	  if (patChars.indexOf(c) != -1)
	    {
	      buf.append('\'');
	      buf.append(c);
	      buf.append('\'');
	    }
	  else
	    buf.append(c);
	}
    }

  private final String computePattern (DecimalFormatSymbols syms)
    {
      StringBuffer mainPattern = new StringBuffer ();
      // 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 total_digits = Math.max(minimumIntegerDigits,
				  groupingUsed ? groupingSize + 1: 0);
      for (int i = 0; i < total_digits - minimumIntegerDigits; ++i)
	mainPattern.append(syms.getDigit());
      for (int i = total_digits - minimumIntegerDigits; i < total_digits; ++i)
	mainPattern.append(syms.getZeroDigit());
      // Inserting the gropuing operator afterwards is easier.
      if (groupingUsed)
	mainPattern.insert(mainPattern.length() - groupingSize,
			   syms.getGroupingSeparator());
      // See if we need decimal info.
      if (minimumFractionDigits > 0 || maximumFractionDigits > 0
	  || decimalSeparatorAlwaysShown)
	mainPattern.append(syms.getDecimalSeparator());
      for (int i = 0; i < minimumFractionDigits; ++i)
	mainPattern.append(syms.getZeroDigit());
      for (int i = minimumFractionDigits; i < maximumFractionDigits; ++i)
	mainPattern.append(syms.getDigit());
      if (useExponentialNotation)
	{
	  mainPattern.append(syms.getExponential());
	  for (int i = 0; i < minExponentDigits; ++i)
	    mainPattern.append(syms.getZeroDigit());
	  if (minExponentDigits == 0)
	    mainPattern.append(syms.getDigit());
	}

      String main = mainPattern.toString();
      String patChars = patternChars (syms);
      mainPattern.setLength(0);

      quoteFix (mainPattern, positivePrefix, patChars);
      mainPattern.append(main);
      quoteFix (mainPattern, positiveSuffix, patChars);

      if (negativePrefix != null)
	{
	  quoteFix (mainPattern, negativePrefix, patChars);
	  mainPattern.append(main);
	  quoteFix (mainPattern, negativeSuffix, patChars);
	}

      return mainPattern.toString();
    }

  public String toLocalizedPattern ()
    {
      return computePattern (symbols);
    }

  public String toPattern ()
    {
      return computePattern (nonLocalizedSymbols);
    }

  // These names are fixed by the serialization spec.
  private boolean decimalSeparatorAlwaysShown;
  private byte groupingSize;
  private byte minExponentDigits;
  private int multiplier;
  private String negativePrefix;
  private String negativeSuffix;
  private String positivePrefix;
  private String positiveSuffix;
  private DecimalFormatSymbols symbols;
  private boolean useExponentialNotation;

  // The locale-independent pattern symbols happen to be the same as
  // the US symbols.
  private static final DecimalFormatSymbols nonLocalizedSymbols
    = new DecimalFormatSymbols (Locale.US);
}