Source for java.util.Formatter

   1: /* Formatter.java -- printf-style formatting
   2:    Copyright (C) 2005 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., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  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.
  25: 
  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. */
  37: 
  38: 
  39: package java.util;
  40: 
  41: import java.io.Closeable;
  42: import java.io.File;
  43: import java.io.FileNotFoundException;
  44: import java.io.FileOutputStream;
  45: import java.io.Flushable;
  46: import java.io.IOException;
  47: import java.io.OutputStream;
  48: import java.io.OutputStreamWriter;
  49: import java.io.PrintStream;
  50: import java.io.UnsupportedEncodingException;
  51: import java.math.BigInteger;
  52: import java.text.DateFormatSymbols;
  53: import java.text.DecimalFormatSymbols;
  54: 
  55: import gnu.classpath.SystemProperties;
  56: 
  57: /** 
  58:  * <p>
  59:  * A Java formatter for <code>printf</code>-style format strings,
  60:  * as seen in the C programming language.   This differs from the
  61:  * C interpretation of such strings by performing much stricter
  62:  * checking of format specifications and their corresponding
  63:  * arguments.  While unknown conversions will be ignored in C,
  64:  * and invalid conversions will only produce compiler warnings,
  65:  * the Java version utilises a full range of run-time exceptions to
  66:  * handle these cases.  The Java version is also more customisable
  67:  * by virtue of the provision of the {@link Formattable} interface,
  68:  * which allows an arbitrary class to be formatted by the formatter.
  69:  * </p>
  70:  * <p>
  71:  * The formatter is accessible by more convienient static methods.
  72:  * For example, streams now have appropriate format methods
  73:  * (the equivalent of <code>fprintf</code>) as do <code>String</code>
  74:  * objects (the equivalent of <code>sprintf</code>).
  75:  * </p>
  76:  * <p>
  77:  * <strong>Note</strong>: the formatter is not thread-safe.  For
  78:  * multi-threaded access, external synchronization should be provided.
  79:  * </p>
  80:  *  
  81:  * @author Tom Tromey (tromey@redhat.com)
  82:  * @author Andrew John Hughes (gnu_andrew@member.fsf.org)
  83:  * @since 1.5 
  84:  */
  85: public final class Formatter 
  86:   implements Closeable, Flushable
  87: {
  88: 
  89:   /**
  90:    * The output of the formatter.
  91:    */
  92:   private StringBuilder out;
  93: 
  94:   /**
  95:    * The locale used by the formatter.
  96:    */
  97:   private Locale locale;
  98: 
  99:   /**
 100:    * Whether or not the formatter is closed.
 101:    */
 102:   private boolean closed;
 103: 
 104:   /**
 105:    * The last I/O exception thrown by the output stream.
 106:    */
 107:   private IOException ioException;
 108: 
 109:   // Some state used when actually formatting.
 110:   /**
 111:    * The format string.
 112:    */
 113:   private String format;
 114: 
 115:   /**
 116:    * The current index into the string.
 117:    */
 118:   private int index;
 119: 
 120:   /**
 121:    * The length of the format string.
 122:    */
 123:   private int length;
 124: 
 125:   /**
 126:    * The formatting locale.
 127:    */
 128:   private Locale fmtLocale;
 129: 
 130:   // Note that we include '-' twice.  The flags are ordered to
 131:   // correspond to the values in FormattableFlags, and there is no
 132:   // flag (in the sense of this field used when parsing) for
 133:   // UPPERCASE; the second '-' serves as a placeholder.
 134:   /**
 135:    * A string used to index into the formattable flags.
 136:    */
 137:   private static final String FLAGS = "--#+ 0,(";
 138: 
 139:   /**
 140:    * The system line separator.
 141:    */
 142:   private static final String lineSeparator
 143:     = SystemProperties.getProperty("line.separator");
 144: 
 145:   /**
 146:    * Constructs a new <code>Formatter</code> using the default
 147:    * locale and a {@link StringBuilder} as the output stream.
 148:    */
 149:   public Formatter()
 150:   {
 151:     this(null, Locale.getDefault());
 152:   }
 153: 
 154:   /**
 155:    * Constructs a new <code>Formatter</code> using the specified
 156:    * locale and a {@link StringBuilder} as the output stream.
 157:    * If the locale is <code>null</code>, then no localization
 158:    * is applied.
 159:    *
 160:    * @param loc the locale to use.
 161:    */
 162:   public Formatter(Locale loc)
 163:   {
 164:     this(null, loc);
 165:   }
 166: 
 167:   /**
 168:    * Constructs a new <code>Formatter</code> using the default
 169:    * locale and the specified output stream.
 170:    *
 171:    * @param app the output stream to use.
 172:    */
 173:   public Formatter(StringBuilder app)
 174:   {
 175:     this(app, Locale.getDefault());
 176:   }
 177: 
 178:   /**
 179:    * Constructs a new <code>Formatter</code> using the specified
 180:    * locale and the specified output stream.  If the locale is
 181:    * <code>null</code>, then no localization is applied.
 182:    *
 183:    * @param app the output stream to use.
 184:    * @param loc the locale to use.
 185:    */
 186:   public Formatter(StringBuilder app, Locale loc)
 187:   {
 188:     this.out = app == null ? new StringBuilder() : app;
 189:     this.locale = loc;
 190:   }
 191: 
 192:   /**
 193:    * Closes the formatter, so as to release used resources.
 194:    * If the underlying output stream supports the {@link Closeable}
 195:    * interface, then this is also closed.  Attempts to use
 196:    * a formatter instance, via any method other than
 197:    * {@link #ioException()}, after closure results in a
 198:    * {@link FormatterClosedException}.
 199:    */
 200:   public void close()
 201:   {
 202:     if (closed)
 203:       return;
 204:     closed = true;
 205:   }
 206: 
 207:   /**
 208:    * Flushes the formatter, writing any cached data to the output
 209:    * stream.  If the underlying output stream supports the
 210:    * {@link Flushable} interface, it is also flushed.
 211:    *
 212:    * @throws FormatterClosedException if the formatter is closed.
 213:    */
 214:   public void flush()
 215:   {
 216:     if (closed)
 217:       throw new FormatterClosedException();
 218:   }
 219: 
 220:   /**
 221:    * Return the name corresponding to a flag.
 222:    *
 223:    * @param flags the flag to return the name of.
 224:    * @return the name of the flag.
 225:    */
 226:   private String getName(int flags)
 227:   {
 228:     // FIXME: do we want all the flags in here?
 229:     // Or should we redo how this is reported?
 230:     int bit = Integer.numberOfTrailingZeros(flags);
 231:     return FLAGS.substring(bit, bit + 1);
 232:   }
 233: 
 234:   /**
 235:    * Verify the flags passed to a conversion.
 236:    *
 237:    * @param flags the flags to verify.
 238:    * @param allowed the allowed flags mask.
 239:    * @param conversion the conversion character.
 240:    */
 241:   private void checkFlags(int flags, int allowed, char conversion)
 242:   {
 243:     flags &= ~allowed;
 244:     if (flags != 0)
 245:       throw new FormatFlagsConversionMismatchException(getName(flags),
 246:                                conversion);
 247:   }
 248: 
 249:   /**
 250:    * Throw an exception if a precision was specified.
 251:    *
 252:    * @param precision the precision value (-1 indicates not specified).
 253:    */
 254:   private void noPrecision(int precision)
 255:   {
 256:     if (precision != -1)
 257:       throw new IllegalFormatPrecisionException(precision);
 258:   }
 259: 
 260:   /**
 261:    * Apply the numeric localization algorithm to a StringBuilder.
 262:    *
 263:    * @param builder the builder to apply to.
 264:    * @param flags the formatting flags to use.
 265:    * @param width the width of the numeric value.
 266:    * @param isNegative true if the value is negative.
 267:    */
 268:   private void applyLocalization(StringBuilder builder, int flags, int width,
 269:                  boolean isNegative)
 270:   {
 271:     DecimalFormatSymbols dfsyms;
 272:     if (fmtLocale == null)
 273:       dfsyms = new DecimalFormatSymbols();
 274:     else
 275:       dfsyms = new DecimalFormatSymbols(fmtLocale);
 276: 
 277:     // First replace each digit.
 278:     char zeroDigit = dfsyms.getZeroDigit();
 279:     int decimalOffset = -1;
 280:     for (int i = builder.length() - 1; i >= 0; --i)
 281:       {
 282:     char c = builder.charAt(i);
 283:     if (c >= '0' && c <= '9')
 284:       builder.setCharAt(i, (char) (c - '0' + zeroDigit));
 285:     else if (c == '.')
 286:       {
 287:         assert decimalOffset == -1;
 288:         decimalOffset = i;
 289:       }
 290:       }
 291: 
 292:     // Localize the decimal separator.
 293:     if (decimalOffset != -1)
 294:       {
 295:     builder.deleteCharAt(decimalOffset);
 296:     builder.insert(decimalOffset, dfsyms.getDecimalSeparator());
 297:       }
 298:     
 299:     // Insert the grouping separators.
 300:     if ((flags & FormattableFlags.COMMA) != 0)
 301:       {
 302:     char groupSeparator = dfsyms.getGroupingSeparator();
 303:     int groupSize = 3;    // FIXME
 304:     int offset = (decimalOffset == -1) ? builder.length() : decimalOffset;
 305:     // We use '>' because we don't want to insert a separator
 306:     // before the first digit.
 307:     for (int i = offset - groupSize; i > 0; i -= groupSize)
 308:       builder.insert(i, groupSeparator);
 309:       }
 310: 
 311:     if ((flags & FormattableFlags.ZERO) != 0)
 312:       {
 313:     // Zero fill.  Note that according to the algorithm we do not
 314:     // insert grouping separators here.
 315:     for (int i = width - builder.length(); i > 0; --i)
 316:       builder.insert(0, zeroDigit);
 317:       }
 318: 
 319:     if (isNegative)
 320:       {
 321:     if ((flags & FormattableFlags.PAREN) != 0)
 322:       {
 323:         builder.insert(0, '(');
 324:         builder.append(')');
 325:       }
 326:     else
 327:       builder.insert(0, '-');
 328:       }
 329:     else if ((flags & FormattableFlags.PLUS) != 0)
 330:       builder.insert(0, '+');
 331:     else if ((flags & FormattableFlags.SPACE) != 0)
 332:       builder.insert(0, ' ');
 333:   }
 334: 
 335:   /**
 336:    * A helper method that handles emitting a String after applying
 337:    * precision, width, justification, and upper case flags.
 338:    *
 339:    * @param arg the string to emit.
 340:    * @param flags the formatting flags to use.
 341:    * @param width the width to use.
 342:    * @param precision the precision to use.
 343:    * @throws IOException if the output stream throws an I/O error.
 344:    */
 345:   private void genericFormat(String arg, int flags, int width, int precision)
 346:     throws IOException
 347:   {
 348:     if ((flags & FormattableFlags.UPPERCASE) != 0)
 349:       {
 350:     if (fmtLocale == null)
 351:       arg = arg.toUpperCase();
 352:     else
 353:       arg = arg.toUpperCase(fmtLocale);
 354:       }
 355: 
 356:     if (precision >= 0 && arg.length() > precision)
 357:       arg = arg.substring(0, precision);
 358: 
 359:     boolean leftJustify = (flags & FormattableFlags.LEFT_JUSTIFY) != 0;
 360:     if (leftJustify && width == -1)
 361:       throw new MissingFormatWidthException("fixme");
 362:     if (! leftJustify && arg.length() < width)
 363:       {
 364:     for (int i = width - arg.length(); i > 0; --i)
 365:       out.append(' ');
 366:       }
 367:     out.append(arg);
 368:     if (leftJustify && arg.length() < width)
 369:       {
 370:     for (int i = width - arg.length(); i > 0; --i)
 371:       out.append(' ');
 372:       }
 373:   }
 374: 
 375:   /** 
 376:    * Emit a boolean.  
 377:    *
 378:    * @param arg the boolean to emit.
 379:    * @param flags the formatting flags to use.
 380:    * @param width the width to use.
 381:    * @param precision the precision to use.
 382:    * @param conversion the conversion character.
 383:    * @throws IOException if the output stream throws an I/O error.
 384:    */
 385:   private void booleanFormat(Object arg, int flags, int width, int precision,
 386:                  char conversion)
 387:     throws IOException
 388:   {
 389:     checkFlags(flags,
 390:            FormattableFlags.LEFT_JUSTIFY | FormattableFlags.UPPERCASE,
 391:            conversion);
 392:     String result;
 393:     if (arg instanceof Boolean)
 394:       result = String.valueOf((Boolean) arg);
 395:     else
 396:       result = arg == null ? "false" : "true";
 397:     genericFormat(result, flags, width, precision);
 398:   }
 399: 
 400:   /** 
 401:    * Emit a hash code.  
 402:    *
 403:    * @param arg the hash code to emit.
 404:    * @param flags the formatting flags to use.
 405:    * @param width the width to use.
 406:    * @param precision the precision to use.
 407:    * @param conversion the conversion character.
 408:    * @throws IOException if the output stream throws an I/O error.
 409:    */
 410:   private void hashCodeFormat(Object arg, int flags, int width, int precision,
 411:                   char conversion)
 412:     throws IOException
 413:   {
 414:     checkFlags(flags,
 415:            FormattableFlags.LEFT_JUSTIFY | FormattableFlags.UPPERCASE,
 416:            conversion);
 417:     genericFormat(arg == null ? "null" : Integer.toHexString(arg.hashCode()),
 418:           flags, width, precision);
 419:   }
 420: 
 421:   /** 
 422:    * Emit a String or Formattable conversion.  
 423:    *
 424:    * @param arg the String or Formattable to emit.
 425:    * @param flags the formatting flags to use.
 426:    * @param width the width to use.
 427:    * @param precision the precision to use.
 428:    * @param conversion the conversion character.
 429:    * @throws IOException if the output stream throws an I/O error.
 430:    */
 431:   private void stringFormat(Object arg, int flags, int width, int precision,
 432:                 char conversion)
 433:     throws IOException
 434:   {
 435:     if (arg instanceof Formattable)
 436:       {
 437:     checkFlags(flags,
 438:            (FormattableFlags.LEFT_JUSTIFY
 439:             | FormattableFlags.UPPERCASE
 440:             | FormattableFlags.ALTERNATE),
 441:            conversion);
 442:     Formattable fmt = (Formattable) arg;
 443:     fmt.formatTo(this, flags, width, precision);
 444:       }
 445:     else
 446:       {
 447:     checkFlags(flags,
 448:            FormattableFlags.LEFT_JUSTIFY | FormattableFlags.UPPERCASE,
 449:            conversion);
 450:     genericFormat(arg == null ? "null" : arg.toString(), flags, width,
 451:               precision);
 452:       }
 453:   }
 454: 
 455:   /** 
 456:    * Emit a character.  
 457:    *
 458:    * @param arg the character to emit.
 459:    * @param flags the formatting flags to use.
 460:    * @param width the width to use.
 461:    * @param precision the precision to use.
 462:    * @param conversion the conversion character.
 463:    * @throws IOException if the output stream throws an I/O error.
 464:    */
 465:   private void characterFormat(Object arg, int flags, int width, int precision,
 466:                    char conversion)
 467:     throws IOException
 468:   {
 469:     checkFlags(flags,
 470:            FormattableFlags.LEFT_JUSTIFY | FormattableFlags.UPPERCASE,
 471:            conversion);
 472:     noPrecision(precision);
 473: 
 474:     int theChar;
 475:     if (arg instanceof Character)
 476:       theChar = ((Character) arg).charValue();
 477:     else if (arg instanceof Byte)
 478:       theChar = (char) (((Byte) arg).byteValue ());
 479:     else if (arg instanceof Short)
 480:       theChar = (char) (((Short) arg).shortValue ());
 481:     else if (arg instanceof Integer)
 482:       {
 483:     theChar = ((Integer) arg).intValue();
 484:     if (! Character.isValidCodePoint(theChar))
 485:       throw new IllegalFormatCodePointException(theChar);
 486:       }
 487:     else
 488:       throw new IllegalFormatConversionException(conversion, arg.getClass());
 489:     String result = new String(Character.toChars(theChar));
 490:     genericFormat(result, flags, width, precision);
 491:   }
 492: 
 493:   /** 
 494:    * Emit a '%'.
 495:    *
 496:    * @param flags the formatting flags to use.
 497:    * @param width the width to use.
 498:    * @param precision the precision to use.
 499:    * @throws IOException if the output stream throws an I/O error.
 500:    */
 501:   private void percentFormat(int flags, int width, int precision)
 502:     throws IOException
 503:   {
 504:     checkFlags(flags, FormattableFlags.LEFT_JUSTIFY, '%');
 505:     noPrecision(precision);
 506:     genericFormat("%", flags, width, precision);
 507:   }
 508: 
 509:   /** 
 510:    * Emit a newline.
 511:    *
 512:    * @param flags the formatting flags to use.
 513:    * @param width the width to use.
 514:    * @param precision the precision to use.
 515:    * @throws IOException if the output stream throws an I/O error.
 516:    */
 517:   private void newLineFormat(int flags, int width, int precision)
 518:     throws IOException
 519:   {
 520:     checkFlags(flags, 0, 'n');
 521:     noPrecision(precision);
 522:     if (width != -1)
 523:       throw new IllegalFormatWidthException(width);
 524:     genericFormat(lineSeparator, flags, width, precision);
 525:   }
 526: 
 527:   /**
 528:    * Helper method to do initial formatting and checking for integral
 529:    * conversions.
 530:    *
 531:    * @param arg the formatted argument.
 532:    * @param flags the formatting flags to use.
 533:    * @param width the width to use.
 534:    * @param precision the precision to use.
 535:    * @param radix the radix of the number.
 536:    * @param conversion the conversion character.
 537:    * @return the result.
 538:    */
 539:   private StringBuilder basicIntegralConversion(Object arg, int flags,
 540:                         int width, int precision,
 541:                         int radix, char conversion)
 542:   {
 543:     assert radix == 8 || radix == 10 || radix == 16;
 544:     noPrecision(precision);
 545: 
 546:     // Some error checking.
 547:     if ((flags & FormattableFlags.ZERO) != 0
 548:     && (flags & FormattableFlags.LEFT_JUSTIFY) == 0)
 549:       throw new IllegalFormatFlagsException(getName(flags));
 550:     if ((flags & FormattableFlags.PLUS) != 0
 551:     && (flags & FormattableFlags.SPACE) != 0)
 552:       throw new IllegalFormatFlagsException(getName(flags));
 553: 
 554:     if ((flags & FormattableFlags.LEFT_JUSTIFY) != 0 && width == -1)
 555:       throw new MissingFormatWidthException("fixme");
 556: 
 557:     // Do the base translation of the value to a string.
 558:     String result;
 559:     int basicFlags = (FormattableFlags.LEFT_JUSTIFY
 560:               // We already handled any possible error when
 561:               // parsing.
 562:               | FormattableFlags.UPPERCASE
 563:               | FormattableFlags.ZERO);
 564:     if (radix == 10)
 565:       basicFlags |= (FormattableFlags.PLUS
 566:              | FormattableFlags.SPACE
 567:              | FormattableFlags.COMMA
 568:              | FormattableFlags.PAREN);
 569:     else
 570:       basicFlags |= FormattableFlags.ALTERNATE;
 571: 
 572:     if (arg instanceof BigInteger)
 573:       {
 574:     checkFlags(flags,
 575:            (basicFlags
 576:             | FormattableFlags.PLUS
 577:             | FormattableFlags.SPACE
 578:             | FormattableFlags.PAREN),
 579:            conversion);
 580:     BigInteger bi = (BigInteger) arg;
 581:     result = bi.toString(radix);
 582:       }
 583:     else if (arg instanceof Number
 584:          && ! (arg instanceof Float)
 585:          && ! (arg instanceof Double))
 586:       {
 587:     checkFlags(flags, basicFlags, conversion);
 588:     long value = ((Number) arg).longValue ();
 589:     if (radix == 8)
 590:       result = Long.toOctalString(value);
 591:     else if (radix == 16)
 592:       result = Long.toHexString(value);
 593:     else
 594:       result = Long.toString(value);
 595:       }
 596:     else
 597:       throw new IllegalFormatConversionException(conversion, arg.getClass());
 598: 
 599:     return new StringBuilder(result);
 600:   }
 601: 
 602:   /** 
 603:    * Emit a hex or octal value.  
 604:    * 
 605:    * @param arg the hexadecimal or octal value.
 606:    * @param flags the formatting flags to use.
 607:    * @param width the width to use.
 608:    * @param precision the precision to use.
 609:    * @param radix the radix of the number.
 610:    * @param conversion the conversion character.
 611:    * @throws IOException if the output stream throws an I/O error.
 612:    */
 613:   private void hexOrOctalConversion(Object arg, int flags, int width,
 614:                     int precision, int radix,
 615:                     char conversion)
 616:     throws IOException
 617:   {
 618:     assert radix == 8 || radix == 16;
 619: 
 620:     StringBuilder builder = basicIntegralConversion(arg, flags, width,
 621:                             precision, radix,
 622:                             conversion);
 623:     int insertPoint = 0;
 624: 
 625:     // Insert the sign.
 626:     if (builder.charAt(0) == '-')
 627:       {
 628:     // Already inserted.  Note that we don't insert a sign, since
 629:     // the only case where it is needed it BigInteger, and it has
 630:     // already been inserted by toString.
 631:     ++insertPoint;
 632:       }
 633:     else if ((flags & FormattableFlags.PLUS) != 0)
 634:       {
 635:     builder.insert(insertPoint, '+');
 636:     ++insertPoint;
 637:       }
 638:     else if ((flags & FormattableFlags.SPACE) != 0)
 639:       {
 640:     builder.insert(insertPoint, ' ');
 641:     ++insertPoint;
 642:       }
 643: 
 644:     // Insert the radix prefix.
 645:     if ((flags & FormattableFlags.ALTERNATE) != 0)
 646:       {
 647:     builder.insert(insertPoint, radix == 8 ? "0" : "0x");
 648:     insertPoint += radix == 8 ? 1 : 2;
 649:       }
 650: 
 651:     // Now justify the result.
 652:     int resultWidth = builder.length();
 653:     if (resultWidth < width)
 654:       {
 655:     char fill = ((flags & FormattableFlags.ZERO) != 0) ? '0' : ' ';
 656:     if ((flags & FormattableFlags.LEFT_JUSTIFY) != 0)
 657:       {
 658:         // Left justify.  
 659:         if (fill == ' ')
 660:           insertPoint = builder.length();
 661:       }
 662:     else
 663:       {
 664:         // Right justify.  Insert spaces before the radix prefix
 665:         // and sign.
 666:         insertPoint = 0;
 667:       }
 668:     while (resultWidth++ < width)
 669:       builder.insert(insertPoint, fill);
 670:       }
 671: 
 672:     String result = builder.toString();
 673:     if ((flags & FormattableFlags.UPPERCASE) != 0)
 674:       {
 675:     if (fmtLocale == null)
 676:       result = result.toUpperCase();
 677:     else
 678:       result = result.toUpperCase(fmtLocale);
 679:       }
 680: 
 681:     out.append(result);
 682:   }
 683: 
 684:   /** 
 685:    * Emit a decimal value.  
 686:    * 
 687:    * @param arg the hexadecimal or octal value.
 688:    * @param flags the formatting flags to use.
 689:    * @param width the width to use.
 690:    * @param precision the precision to use.
 691:    * @param conversion the conversion character.
 692:    * @throws IOException if the output stream throws an I/O error.
 693:    */
 694:   private void decimalConversion(Object arg, int flags, int width,
 695:                  int precision, char conversion)
 696:     throws IOException
 697:   {
 698:     StringBuilder builder = basicIntegralConversion(arg, flags, width,
 699:                             precision, 10,
 700:                             conversion);
 701:     boolean isNegative = false;
 702:     if (builder.charAt(0) == '-')
 703:       {
 704:     // Sign handling is done during localization.
 705:     builder.deleteCharAt(0);
 706:     isNegative = true;
 707:       }
 708: 
 709:     applyLocalization(builder, flags, width, isNegative);
 710:     genericFormat(builder.toString(), flags, width, precision);
 711:   }
 712: 
 713:   /** 
 714:    * Emit a single date or time conversion to a StringBuilder.  
 715:    *
 716:    * @param builder the builder to write to.
 717:    * @param cal the calendar to use in the conversion.
 718:    * @param conversion the formatting character to specify the type of data.
 719:    * @param syms the date formatting symbols.
 720:    */
 721:   private void singleDateTimeConversion(StringBuilder builder, Calendar cal,
 722:                     char conversion,
 723:                     DateFormatSymbols syms)
 724:   {
 725:     int oldLen = builder.length();
 726:     int digits = -1;
 727:     switch (conversion)
 728:       {
 729:       case 'H':
 730:     builder.append(cal.get(Calendar.HOUR_OF_DAY));
 731:     digits = 2;
 732:     break;
 733:       case 'I':
 734:     builder.append(cal.get(Calendar.HOUR));
 735:     digits = 2;
 736:     break;
 737:       case 'k':
 738:     builder.append(cal.get(Calendar.HOUR_OF_DAY));
 739:     break;
 740:       case 'l':
 741:     builder.append(cal.get(Calendar.HOUR));
 742:     break;
 743:       case 'M':
 744:     builder.append(cal.get(Calendar.MINUTE));
 745:     digits = 2;
 746:     break;
 747:       case 'S':
 748:     builder.append(cal.get(Calendar.SECOND));
 749:     digits = 2;
 750:     break;
 751:       case 'N':
 752:     // FIXME: nanosecond ...
 753:     digits = 9;
 754:     break;
 755:       case 'p':
 756:     {
 757:       int ampm = cal.get(Calendar.AM_PM);
 758:       builder.append(syms.getAmPmStrings()[ampm]);
 759:     }
 760:     break;
 761:       case 'z':
 762:     {
 763:       int zone = cal.get(Calendar.ZONE_OFFSET) / (1000 * 60);
 764:       builder.append(zone);
 765:       digits = 4;
 766:       // Skip the '-' sign.
 767:       if (zone < 0)
 768:         ++oldLen;
 769:     }
 770:     break;
 771:       case 'Z':
 772:     {
 773:       // FIXME: DST?
 774:       int zone = cal.get(Calendar.ZONE_OFFSET) / (1000 * 60 * 60);
 775:       String[][] zs = syms.getZoneStrings();
 776:       builder.append(zs[zone + 12][1]);
 777:     }
 778:     break;
 779:       case 's':
 780:     {
 781:       long val = cal.getTime().getTime();
 782:       builder.append(val / 1000);
 783:     }
 784:     break;
 785:       case 'Q':
 786:     {
 787:       long val = cal.getTime().getTime();
 788:       builder.append(val);
 789:     }
 790:     break;
 791:       case 'B':
 792:     {
 793:       int month = cal.get(Calendar.MONTH);
 794:       builder.append(syms.getMonths()[month]);
 795:     }
 796:     break;
 797:       case 'b':
 798:       case 'h':
 799:     {
 800:       int month = cal.get(Calendar.MONTH);
 801:       builder.append(syms.getShortMonths()[month]);
 802:     }
 803:     break;
 804:       case 'A':
 805:     {
 806:       int day = cal.get(Calendar.DAY_OF_WEEK);
 807:       builder.append(syms.getWeekdays()[day]);
 808:     }
 809:     break;
 810:       case 'a':
 811:     {
 812:       int day = cal.get(Calendar.DAY_OF_WEEK);
 813:       builder.append(syms.getShortWeekdays()[day]);
 814:     }
 815:     break;
 816:       case 'C':
 817:     builder.append(cal.get(Calendar.YEAR) / 100);
 818:     digits = 2;
 819:     break;
 820:       case 'Y':
 821:     builder.append(cal.get(Calendar.YEAR));
 822:     digits = 4;
 823:     break;
 824:       case 'y':
 825:     builder.append(cal.get(Calendar.YEAR) % 100);
 826:     digits = 2;
 827:     break;
 828:       case 'j':
 829:     builder.append(cal.get(Calendar.DAY_OF_YEAR));
 830:     digits = 3;
 831:     break;
 832:       case 'm':
 833:     builder.append(cal.get(Calendar.MONTH) + 1);
 834:     digits = 2;
 835:     break;
 836:       case 'd':
 837:     builder.append(cal.get(Calendar.DAY_OF_MONTH));
 838:     digits = 2;
 839:     break;
 840:       case 'e':
 841:     builder.append(cal.get(Calendar.DAY_OF_MONTH));
 842:     break;
 843:       case 'R':
 844:     singleDateTimeConversion(builder, cal, 'H', syms);
 845:     builder.append(':');
 846:     singleDateTimeConversion(builder, cal, 'M', syms);
 847:     break;
 848:       case 'T':
 849:     singleDateTimeConversion(builder, cal, 'H', syms);
 850:     builder.append(':');
 851:     singleDateTimeConversion(builder, cal, 'M', syms);
 852:     builder.append(':');
 853:     singleDateTimeConversion(builder, cal, 'S', syms);
 854:     break;
 855:       case 'r':
 856:     singleDateTimeConversion(builder, cal, 'I', syms);
 857:     builder.append(':');
 858:     singleDateTimeConversion(builder, cal, 'M', syms);
 859:     builder.append(':');
 860:     singleDateTimeConversion(builder, cal, 'S', syms);
 861:     builder.append(' ');
 862:     singleDateTimeConversion(builder, cal, 'p', syms);
 863:     break;
 864:       case 'D':
 865:     singleDateTimeConversion(builder, cal, 'm', syms);
 866:     builder.append('/');
 867:         singleDateTimeConversion(builder, cal, 'd', syms);
 868:     builder.append('/');
 869:     singleDateTimeConversion(builder, cal, 'y', syms);
 870:     break;
 871:       case 'F':
 872:     singleDateTimeConversion(builder, cal, 'Y', syms);
 873:     builder.append('-');
 874:     singleDateTimeConversion(builder, cal, 'm', syms);
 875:     builder.append('-');
 876:     singleDateTimeConversion(builder, cal, 'd', syms);
 877:     break;
 878:       case 'c':
 879:     singleDateTimeConversion(builder, cal, 'a', syms);
 880:     builder.append(' ');
 881:     singleDateTimeConversion(builder, cal, 'b', syms);
 882:     builder.append(' ');
 883:     singleDateTimeConversion(builder, cal, 'd', syms);
 884:     builder.append(' ');
 885:     singleDateTimeConversion(builder, cal, 'T', syms);
 886:     builder.append(' ');
 887:     singleDateTimeConversion(builder, cal, 'Z', syms);
 888:     builder.append(' ');
 889:     singleDateTimeConversion(builder, cal, 'Y', syms);
 890:     break;
 891:       default:
 892:     throw new UnknownFormatConversionException(String.valueOf(conversion));
 893:       }
 894: 
 895:     if (digits > 0)
 896:       {
 897:     int newLen = builder.length();
 898:     int delta = newLen - oldLen;
 899:     while (delta++ < digits)
 900:       builder.insert(oldLen, '0');
 901:       }
 902:   }
 903: 
 904:   /**
 905:    * Emit a date or time value.
 906:    *
 907:    * @param arg the date or time value.
 908:    * @param flags the formatting flags to use.
 909:    * @param width the width to use.
 910:    * @param precision the precision to use.
 911:    * @param conversion the conversion character.
 912:    * @param subConversion the sub conversion character.
 913:    * @throws IOException if the output stream throws an I/O error.
 914:    */
 915:   private void dateTimeConversion(Object arg, int flags, int width,
 916:                   int precision, char conversion,
 917:                   char subConversion)
 918:     throws IOException
 919:   {
 920:     noPrecision(precision);
 921:     checkFlags(flags,
 922:            FormattableFlags.LEFT_JUSTIFY | FormattableFlags.UPPERCASE,
 923:            conversion);
 924: 
 925:     Calendar cal;
 926:     if (arg instanceof Calendar)
 927:       cal = (Calendar) arg;
 928:     else
 929:       {
 930:     Date date;
 931:     if (arg instanceof Date)
 932:       date = (Date) arg;
 933:     else if (arg instanceof Long)
 934:       date = new Date(((Long) arg).longValue());
 935:     else
 936:       throw new IllegalFormatConversionException(conversion,
 937:                              arg.getClass());
 938:     if (fmtLocale == null)
 939:       cal = Calendar.getInstance();
 940:     else
 941:       cal = Calendar.getInstance(fmtLocale);
 942:     cal.setTime(date);
 943:       }
 944: 
 945:     // We could try to be more efficient by computing this lazily.
 946:     DateFormatSymbols syms;
 947:     if (fmtLocale == null)
 948:       syms = new DateFormatSymbols();
 949:     else
 950:       syms = new DateFormatSymbols(fmtLocale);
 951: 
 952:     StringBuilder result = new StringBuilder();
 953:     singleDateTimeConversion(result, cal, subConversion, syms);
 954: 
 955:     genericFormat(result.toString(), flags, width, precision);
 956:   }
 957: 
 958:   /**
 959:    * Advance the internal parsing index, and throw an exception
 960:    * on overrun.
 961:    *
 962:    * @throws IllegalArgumentException on overrun.
 963:    */
 964:   private void advance()
 965:   {
 966:     ++index;
 967:     if (index >= length)
 968:       {
 969:     // FIXME: what exception here?
 970:     throw new IllegalArgumentException();
 971:       }
 972:   }
 973: 
 974:   /**
 975:    * Parse an integer appearing in the format string.  Will return -1
 976:    * if no integer was found.
 977:    *
 978:    * @return the parsed integer.
 979:    */
 980:   private int parseInt()
 981:   {
 982:     int start = index;
 983:     while (Character.isDigit(format.charAt(index)))
 984:       advance();
 985:     if (start == index)
 986:       return -1;
 987:     return Integer.decode(format.substring(start, index)).intValue();
 988:   }
 989: 
 990:   /**
 991:    * Parse the argument index.  Returns -1 if there was no index, 0 if
 992:    * we should re-use the previous index, and a positive integer to
 993:    * indicate an absolute index.
 994:    *
 995:    * @return the parsed argument index.
 996:    */
 997:   private int parseArgumentIndex()
 998:   {
 999:     int result = -1;
1000:     int start = index;
1001:     if (format.charAt(index) == '<')
1002:       {
1003:     result = 0;
1004:     advance();
1005:       }
1006:     else if (Character.isDigit(format.charAt(index)))
1007:       {
1008:     result = parseInt();
1009:     if (format.charAt(index) == '$')
1010:       advance();
1011:     else
1012:       {
1013:         // Reset.
1014:         index = start;
1015:         result = -1;
1016:       }
1017:       }
1018:     return result;
1019:   }
1020: 
1021:   /**
1022:    * Parse a set of flags and return a bit mask of values from
1023:    * FormattableFlags.  Will throw an exception if a flag is
1024:    * duplicated.
1025:    *
1026:    * @return the parsed flags.
1027:    */
1028:   private int parseFlags()
1029:   {
1030:     int value = 0;
1031:     int start = index;
1032:     while (true)
1033:       {
1034:     int x = FLAGS.indexOf(format.charAt(index));
1035:     if (x == -1)
1036:       break;
1037:     int newValue = 1 << x;
1038:     if ((value & newValue) != 0)
1039:       throw new DuplicateFormatFlagsException(format.substring(start,
1040:                                    index + 1));
1041:     value |= newValue;
1042:     advance();
1043:       }
1044:     return value;
1045:   }
1046: 
1047:   /**
1048:    * Parse the width part of a format string.  Returns -1 if no width
1049:    * was specified.
1050:    *
1051:    * @return the parsed width.
1052:    */
1053:   private int parseWidth()
1054:   {
1055:     return parseInt();
1056:   }
1057: 
1058:   /**
1059:    * If the current character is '.', parses the precision part of a
1060:    * format string.  Returns -1 if no precision was specified.
1061:    *
1062:    * @return the parsed precision.
1063:    */
1064:   private int parsePrecision()
1065:   {
1066:     if (format.charAt(index) != '.')
1067:       return -1;
1068:     advance();
1069:     int precision = parseInt();
1070:     if (precision == -1)
1071:       // FIXME
1072:       throw new IllegalArgumentException();
1073:     return precision;
1074:   }
1075: 
1076:   /**
1077:    * Outputs a formatted string based on the supplied specification,
1078:    * <code>fmt</code>, and its arguments using the specified locale.
1079:    * The locale of the formatter does not change as a result; the
1080:    * specified locale is just used for this particular formatting
1081:    * operation.  If the locale is <code>null</code>, then no
1082:    * localization is applied.
1083:    *
1084:    * @param loc the locale to use for this format.
1085:    * @param fmt the format specification.
1086:    * @param args the arguments to apply to the specification.
1087:    * @throws IllegalFormatException if there is a problem with
1088:    *                                the syntax of the format
1089:    *                                specification or a mismatch
1090:    *                                between it and the arguments.
1091:    * @throws FormatterClosedException if the formatter is closed.
1092:    */ 
1093:   public Formatter format(Locale loc, String fmt, Object[] args)
1094:   {
1095:     if (closed)
1096:       throw new FormatterClosedException();
1097: 
1098:     // Note the arguments are indexed starting at 1.
1099:     int implicitArgumentIndex = 1;
1100:     int previousArgumentIndex = 0;
1101: 
1102:     try
1103:       {
1104:     fmtLocale = loc;
1105:     format = fmt;
1106:     length = format.length();
1107:     for (index = 0; index < length; ++index)
1108:       {
1109:         char c = format.charAt(index);
1110:         if (c != '%')
1111:           {
1112:         out.append(c);
1113:         continue;
1114:           }
1115: 
1116:         int start = index;
1117:         advance();
1118: 
1119:         // We do the needed post-processing of this later, when we
1120:         // determine whether an argument is actually needed by
1121:         // this conversion.
1122:         int argumentIndex = parseArgumentIndex();
1123: 
1124:         int flags = parseFlags();
1125:         int width = parseWidth();
1126:         int precision = parsePrecision();
1127:         char origConversion = format.charAt(index);
1128:         char conversion = origConversion;
1129:         if (Character.isUpperCase(conversion))
1130:           {
1131:         flags |= FormattableFlags.UPPERCASE;
1132:         conversion = Character.toLowerCase(conversion);
1133:           }
1134: 
1135:         Object argument = null;
1136:         if (conversion == '%' || conversion == 'n')
1137:           {
1138:         if (argumentIndex != -1)
1139:           {
1140:             // FIXME: not sure about this.
1141:             throw new UnknownFormatConversionException("FIXME");
1142:           }
1143:           }
1144:         else
1145:           {
1146:         if (argumentIndex == -1)
1147:           argumentIndex = implicitArgumentIndex++;
1148:         else if (argumentIndex == 0)
1149:           argumentIndex = previousArgumentIndex;
1150:         // Argument indices start at 1 but array indices at 0.
1151:         --argumentIndex;
1152:         if (argumentIndex < 0 || argumentIndex >= args.length)
1153:           throw new MissingFormatArgumentException(format.substring(start, index));
1154:         argument = args[argumentIndex];
1155:           }
1156: 
1157:         switch (conversion)
1158:           {
1159:           case 'b':
1160:         booleanFormat(argument, flags, width, precision,
1161:                   origConversion);
1162:         break;
1163:           case 'h':
1164:         hashCodeFormat(argument, flags, width, precision,
1165:                    origConversion);
1166:         break;
1167:           case 's':
1168:         stringFormat(argument, flags, width, precision,
1169:                  origConversion);
1170:         break;
1171:           case 'c':
1172:         characterFormat(argument, flags, width, precision,
1173:                 origConversion);
1174:         break;
1175:           case 'd':
1176:         checkFlags(flags & FormattableFlags.UPPERCASE, 0, 'd');
1177:         decimalConversion(argument, flags, width, precision,
1178:                   origConversion);
1179:         break;
1180:           case 'o':
1181:         checkFlags(flags & FormattableFlags.UPPERCASE, 0, 'o');
1182:         hexOrOctalConversion(argument, flags, width, precision, 8,
1183:                      origConversion);
1184:         break;
1185:           case 'x':
1186:         hexOrOctalConversion(argument, flags, width, precision, 16,
1187:                      origConversion);
1188:           case 'e':
1189:         // scientificNotationConversion();
1190:         break;
1191:           case 'f':
1192:         // floatingDecimalConversion();
1193:         break;
1194:           case 'g':
1195:         // smartFloatingConversion();
1196:         break;
1197:           case 'a':
1198:         // hexFloatingConversion();
1199:         break;
1200:           case 't':
1201:         advance();
1202:         char subConversion = format.charAt(index);
1203:         dateTimeConversion(argument, flags, width, precision,
1204:                    origConversion, subConversion);
1205:         break;
1206:           case '%':
1207:         percentFormat(flags, width, precision);
1208:         break;
1209:           case 'n':
1210:         newLineFormat(flags, width, precision);
1211:         break;
1212:           default:
1213:         throw new UnknownFormatConversionException(String.valueOf(origConversion));
1214:           }
1215:       }
1216:       }
1217:     catch (IOException exc)
1218:       {
1219:     ioException = exc;
1220:       }
1221:     return this;
1222:   }
1223: 
1224:   /**
1225:    * Outputs a formatted string based on the supplied specification,
1226:    * <code>fmt</code>, and its arguments using the formatter's locale.
1227:    *
1228:    * @param fmt the format specification.
1229:    * @param args the arguments to apply to the specification.
1230:    * @throws IllegalFormatException if there is a problem with
1231:    *                                the syntax of the format
1232:    *                                specification or a mismatch
1233:    *                                between it and the arguments.
1234:    * @throws FormatterClosedException if the formatter is closed.
1235:    */
1236:   public Formatter format(String format, Object[] args)
1237:   {
1238:     return format(locale, format, args);
1239:   }
1240: 
1241:   /**
1242:    * Returns the last I/O exception thrown by the
1243:    * <code>append()</code> operation of the underlying
1244:    * output stream.
1245:    *
1246:    * @return the last I/O exception.
1247:    */
1248:   public IOException ioException()
1249:   {
1250:     return ioException;
1251:   }
1252: 
1253:   /**
1254:    * Returns the locale used by this formatter.
1255:    *
1256:    * @return the formatter's locale.
1257:    * @throws FormatterClosedException if the formatter is closed.
1258:    */
1259:   public Locale locale()
1260:   {
1261:     if (closed)
1262:       throw new FormatterClosedException();
1263:     return locale;
1264:   }
1265: 
1266:   /**
1267:    * Returns the output stream used by this formatter.
1268:    *
1269:    * @return the formatter's output stream.
1270:    * @throws FormatterClosedException if the formatter is closed.
1271:    */
1272:   public StringBuilder out()
1273:   {
1274:     if (closed)
1275:       throw new FormatterClosedException();
1276:     return out;
1277:   }
1278: 
1279:   /**
1280:    * Returns the result of applying {@link Object#toString()}
1281:    * to the underlying output stream.  The results returned
1282:    * depend on the particular {@link Appendable} being used.
1283:    * For example, a {@link StringBuilder} will return the
1284:    * formatted output but an I/O stream will not.
1285:    *
1286:    * @throws FormatterClosedException if the formatter is closed.
1287:    */
1288:   public String toString()
1289:   {
1290:     if (closed)
1291:       throw new FormatterClosedException();
1292:     return out.toString();
1293:   }
1294: }