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 Appendable 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:    * The type of numeric output format for a {@link BigDecimal}.
 147:    */
 148:   public enum BigDecimalLayoutForm
 149:   {
 150:     DECIMAL_FLOAT,
 151:     SCIENTIFIC
 152:   }
 153: 
 154:   /**
 155:    * Constructs a new <code>Formatter</code> using the default
 156:    * locale and a {@link StringBuilder} as the output stream.
 157:    */
 158:   public Formatter()
 159:   {
 160:     this(null, Locale.getDefault());
 161:   }
 162: 
 163:   /**
 164:    * Constructs a new <code>Formatter</code> using the specified
 165:    * locale and a {@link StringBuilder} as the output stream.
 166:    * If the locale is <code>null</code>, then no localization
 167:    * is applied.
 168:    *
 169:    * @param loc the locale to use.
 170:    */
 171:   public Formatter(Locale loc)
 172:   {
 173:     this(null, loc);
 174:   }
 175: 
 176:   /**
 177:    * Constructs a new <code>Formatter</code> using the default
 178:    * locale and the specified output stream.
 179:    *
 180:    * @param app the output stream to use.
 181:    */
 182:   public Formatter(Appendable app)
 183:   {
 184:     this(app, Locale.getDefault());
 185:   }
 186: 
 187:   /**
 188:    * Constructs a new <code>Formatter</code> using the specified
 189:    * locale and the specified output stream.  If the locale is
 190:    * <code>null</code>, then no localization is applied.
 191:    *
 192:    * @param app the output stream to use.
 193:    * @param loc the locale to use.
 194:    */
 195:   public Formatter(Appendable app, Locale loc)
 196:   {
 197:     this.out = app == null ? new StringBuilder() : app;
 198:     this.locale = loc;
 199:   }
 200: 
 201:   /**
 202:    * Constructs a new <code>Formatter</code> using the default
 203:    * locale and character set, with the specified file as the
 204:    * output stream.
 205:    *
 206:    * @param file the file to use for output.
 207:    * @throws FileNotFoundException if the file does not exist
 208:    *                               and can not be created.
 209:    * @throws SecurityException if a security manager is present
 210:    *                           and doesn't allow writing to the file.
 211:    */
 212:   public Formatter(File file) 
 213:     throws FileNotFoundException
 214:   {
 215:     this(new OutputStreamWriter(new FileOutputStream(file)));
 216:   }
 217: 
 218:   /**
 219:    * Constructs a new <code>Formatter</code> using the default
 220:    * locale, with the specified file as the output stream
 221:    * and the supplied character set.
 222:    *
 223:    * @param file the file to use for output.
 224:    * @param charset the character set to use for output.
 225:    * @throws FileNotFoundException if the file does not exist
 226:    *                               and can not be created.
 227:    * @throws SecurityException if a security manager is present
 228:    *                           and doesn't allow writing to the file.
 229:    * @throws UnsupportedEncodingException if the supplied character
 230:    *                                      set is not supported.
 231:    */
 232:   public Formatter(File file, String charset)
 233:     throws FileNotFoundException, UnsupportedEncodingException
 234:   {
 235:     this(file, charset, Locale.getDefault());
 236:   }
 237: 
 238:   /**
 239:    * Constructs a new <code>Formatter</code> using the specified
 240:    * file as the output stream with the supplied character set
 241:    * and locale.  If the locale is <code>null</code>, then no
 242:    * localization is applied.
 243:    *
 244:    * @param file the file to use for output.
 245:    * @param charset the character set to use for output.
 246:    * @param loc the locale to use.
 247:    * @throws FileNotFoundException if the file does not exist
 248:    *                               and can not be created.
 249:    * @throws SecurityException if a security manager is present
 250:    *                           and doesn't allow writing to the file.
 251:    * @throws UnsupportedEncodingException if the supplied character
 252:    *                                      set is not supported.
 253:    */
 254:   public Formatter(File file, String charset, Locale loc)
 255:     throws FileNotFoundException, UnsupportedEncodingException
 256:   {
 257:     this(new OutputStreamWriter(new FileOutputStream(file), charset),
 258:      loc);
 259:   }
 260: 
 261:   /**
 262:    * Constructs a new <code>Formatter</code> using the default
 263:    * locale and character set, with the specified output stream.
 264:    *
 265:    * @param out the output stream to use.
 266:    */
 267:   public Formatter(OutputStream out)
 268:   {
 269:     this(new OutputStreamWriter(out));
 270:   }
 271: 
 272:   /**
 273:    * Constructs a new <code>Formatter</code> using the default
 274:    * locale, with the specified file output stream and the
 275:    * supplied character set.
 276:    *
 277:    * @param out the output stream.
 278:    * @param charset the character set to use for output.
 279:    * @throws UnsupportedEncodingException if the supplied character
 280:    *                                      set is not supported.
 281:    */
 282:   public Formatter(OutputStream out, String charset)
 283:     throws UnsupportedEncodingException
 284:   {
 285:     this(out, charset, Locale.getDefault());
 286:   }
 287: 
 288:   /**
 289:    * Constructs a new <code>Formatter</code> using the specified
 290:    * output stream with the supplied character set and locale.
 291:    * If the locale is <code>null</code>, then no localization is
 292:    * applied.
 293:    *
 294:    * @param file the output stream.
 295:    * @param charset the character set to use for output.
 296:    * @param loc the locale to use.
 297:    * @throws UnsupportedEncodingException if the supplied character
 298:    *                                      set is not supported.
 299:    */
 300:   public Formatter(OutputStream out, String charset, Locale loc)
 301:     throws UnsupportedEncodingException
 302:   {
 303:     this(new OutputStreamWriter(out, charset), loc);
 304:   }
 305: 
 306:   /**
 307:    * Constructs a new <code>Formatter</code> using the default
 308:    * locale with the specified output stream.  The character
 309:    * set used is that of the output stream.
 310:    *
 311:    * @param out the output stream to use.
 312:    */
 313:   public Formatter(PrintStream out)
 314:   {
 315:     this((Appendable) out);
 316:   }
 317: 
 318:   /**
 319:    * Constructs a new <code>Formatter</code> using the default
 320:    * locale and character set, with the specified file as the
 321:    * output stream.
 322:    *
 323:    * @param file the file to use for output.
 324:    * @throws FileNotFoundException if the file does not exist
 325:    *                               and can not be created.
 326:    * @throws SecurityException if a security manager is present
 327:    *                           and doesn't allow writing to the file.
 328:    */
 329:   public Formatter(String file) throws FileNotFoundException
 330:   {
 331:     this(new OutputStreamWriter(new FileOutputStream(file)));
 332:   }
 333: 
 334:   /**
 335:    * Constructs a new <code>Formatter</code> using the default
 336:    * locale, with the specified file as the output stream
 337:    * and the supplied character set.
 338:    *
 339:    * @param file the file to use for output.
 340:    * @param charset the character set to use for output.
 341:    * @throws FileNotFoundException if the file does not exist
 342:    *                               and can not be created.
 343:    * @throws SecurityException if a security manager is present
 344:    *                           and doesn't allow writing to the file.
 345:    * @throws UnsupportedEncodingException if the supplied character
 346:    *                                      set is not supported.
 347:    */
 348:   public Formatter(String file, String charset)
 349:     throws FileNotFoundException, UnsupportedEncodingException
 350:   {
 351:     this(file, charset, Locale.getDefault());
 352:   }
 353: 
 354:   /**
 355:    * Constructs a new <code>Formatter</code> using the specified
 356:    * file as the output stream with the supplied character set
 357:    * and locale.  If the locale is <code>null</code>, then no
 358:    * localization is applied.
 359:    *
 360:    * @param file the file to use for output.
 361:    * @param charset the character set to use for output.
 362:    * @param loc the locale to use.
 363:    * @throws FileNotFoundException if the file does not exist
 364:    *                               and can not be created.
 365:    * @throws SecurityException if a security manager is present
 366:    *                           and doesn't allow writing to the file.
 367:    * @throws UnsupportedEncodingException if the supplied character
 368:    *                                      set is not supported.
 369:    */
 370:   public Formatter(String file, String charset, Locale loc)
 371:     throws FileNotFoundException, UnsupportedEncodingException
 372:   {
 373:     this(new OutputStreamWriter(new FileOutputStream(file), charset),
 374:      loc);
 375:   }
 376: 
 377:   /**
 378:    * Closes the formatter, so as to release used resources.
 379:    * If the underlying output stream supports the {@link Closeable}
 380:    * interface, then this is also closed.  Attempts to use
 381:    * a formatter instance, via any method other than
 382:    * {@link #ioException()}, after closure results in a
 383:    * {@link FormatterClosedException}.
 384:    */
 385:   public void close()
 386:   {
 387:     if (closed)
 388:       return;
 389:     try
 390:       {
 391:     if (out instanceof Closeable)
 392:       ((Closeable) out).close();
 393:       }
 394:     catch (IOException _)
 395:       {
 396:     // FIXME: do we ignore these or do we set ioException?
 397:     // The docs seem to indicate that we should ignore.
 398:       }
 399:     closed = true;
 400:   }
 401: 
 402:   /**
 403:    * Flushes the formatter, writing any cached data to the output
 404:    * stream.  If the underlying output stream supports the
 405:    * {@link Flushable} interface, it is also flushed.
 406:    *
 407:    * @throws FormatterClosedException if the formatter is closed.
 408:    */
 409:   public void flush()
 410:   {
 411:     if (closed)
 412:       throw new FormatterClosedException();
 413:     try
 414:       {
 415:     if (out instanceof Flushable)
 416:       ((Flushable) out).flush();
 417:       }
 418:     catch (IOException _)
 419:       {
 420:     // FIXME: do we ignore these or do we set ioException?
 421:     // The docs seem to indicate that we should ignore.
 422:       }
 423:   }
 424: 
 425:   /**
 426:    * Return the name corresponding to a flag.
 427:    *
 428:    * @param flags the flag to return the name of.
 429:    * @return the name of the flag.
 430:    */
 431:   private String getName(int flags)
 432:   {
 433:     // FIXME: do we want all the flags in here?
 434:     // Or should we redo how this is reported?
 435:     int bit = Integer.numberOfTrailingZeros(flags);
 436:     return FLAGS.substring(bit, bit + 1);
 437:   }
 438: 
 439:   /**
 440:    * Verify the flags passed to a conversion.
 441:    *
 442:    * @param flags the flags to verify.
 443:    * @param allowed the allowed flags mask.
 444:    * @param conversion the conversion character.
 445:    */
 446:   private void checkFlags(int flags, int allowed, char conversion)
 447:   {
 448:     flags &= ~allowed;
 449:     if (flags != 0)
 450:       throw new FormatFlagsConversionMismatchException(getName(flags),
 451:                                conversion);
 452:   }
 453: 
 454:   /**
 455:    * Throw an exception if a precision was specified.
 456:    *
 457:    * @param precision the precision value (-1 indicates not specified).
 458:    */
 459:   private void noPrecision(int precision)
 460:   {
 461:     if (precision != -1)
 462:       throw new IllegalFormatPrecisionException(precision);
 463:   }
 464: 
 465:   /**
 466:    * Apply the numeric localization algorithm to a StringBuilder.
 467:    *
 468:    * @param builder the builder to apply to.
 469:    * @param flags the formatting flags to use.
 470:    * @param width the width of the numeric value.
 471:    * @param isNegative true if the value is negative.
 472:    */
 473:   private void applyLocalization(StringBuilder builder, int flags, int width,
 474:                  boolean isNegative)
 475:   {
 476:     DecimalFormatSymbols dfsyms;
 477:     if (fmtLocale == null)
 478:       dfsyms = new DecimalFormatSymbols();
 479:     else
 480:       dfsyms = new DecimalFormatSymbols(fmtLocale);
 481: 
 482:     // First replace each digit.
 483:     char zeroDigit = dfsyms.getZeroDigit();
 484:     int decimalOffset = -1;
 485:     for (int i = builder.length() - 1; i >= 0; --i)
 486:       {
 487:     char c = builder.charAt(i);
 488:     if (c >= '0' && c <= '9')
 489:       builder.setCharAt(i, (char) (c - '0' + zeroDigit));
 490:     else if (c == '.')
 491:       {
 492:         assert decimalOffset == -1;
 493:         decimalOffset = i;
 494:       }
 495:       }
 496: 
 497:     // Localize the decimal separator.
 498:     if (decimalOffset != -1)
 499:       {
 500:     builder.deleteCharAt(decimalOffset);
 501:     builder.insert(decimalOffset, dfsyms.getDecimalSeparator());
 502:       }
 503:     
 504:     // Insert the grouping separators.
 505:     if ((flags & FormattableFlags.COMMA) != 0)
 506:       {
 507:     char groupSeparator = dfsyms.getGroupingSeparator();
 508:     int groupSize = 3;    // FIXME
 509:     int offset = (decimalOffset == -1) ? builder.length() : decimalOffset;
 510:     // We use '>' because we don't want to insert a separator
 511:     // before the first digit.
 512:     for (int i = offset - groupSize; i > 0; i -= groupSize)
 513:       builder.insert(i, groupSeparator);
 514:       }
 515: 
 516:     if ((flags & FormattableFlags.ZERO) != 0)
 517:       {
 518:     // Zero fill.  Note that according to the algorithm we do not
 519:     // insert grouping separators here.
 520:     for (int i = width - builder.length(); i > 0; --i)
 521:       builder.insert(0, zeroDigit);
 522:       }
 523: 
 524:     if (isNegative)
 525:       {
 526:     if ((flags & FormattableFlags.PAREN) != 0)
 527:       {
 528:         builder.insert(0, '(');
 529:         builder.append(')');
 530:       }
 531:     else
 532:       builder.insert(0, '-');
 533:       }
 534:     else if ((flags & FormattableFlags.PLUS) != 0)
 535:       builder.insert(0, '+');
 536:     else if ((flags & FormattableFlags.SPACE) != 0)
 537:       builder.insert(0, ' ');
 538:   }
 539: 
 540:   /**
 541:    * A helper method that handles emitting a String after applying
 542:    * precision, width, justification, and upper case flags.
 543:    *
 544:    * @param arg the string to emit.
 545:    * @param flags the formatting flags to use.
 546:    * @param width the width to use.
 547:    * @param precision the precision to use.
 548:    * @throws IOException if the output stream throws an I/O error.
 549:    */
 550:   private void genericFormat(String arg, int flags, int width, int precision)
 551:     throws IOException
 552:   {
 553:     if ((flags & FormattableFlags.UPPERCASE) != 0)
 554:       {
 555:     if (fmtLocale == null)
 556:       arg = arg.toUpperCase();
 557:     else
 558:       arg = arg.toUpperCase(fmtLocale);
 559:       }
 560: 
 561:     if (precision >= 0 && arg.length() > precision)
 562:       arg = arg.substring(0, precision);
 563: 
 564:     boolean leftJustify = (flags & FormattableFlags.LEFT_JUSTIFY) != 0;
 565:     if (leftJustify && width == -1)
 566:       throw new MissingFormatWidthException("fixme");
 567:     if (! leftJustify && arg.length() < width)
 568:       {
 569:     for (int i = width - arg.length(); i > 0; --i)
 570:       out.append(' ');
 571:       }
 572:     out.append(arg);
 573:     if (leftJustify && arg.length() < width)
 574:       {
 575:     for (int i = width - arg.length(); i > 0; --i)
 576:       out.append(' ');
 577:       }
 578:   }
 579: 
 580:   /** 
 581:    * Emit a boolean.  
 582:    *
 583:    * @param arg the boolean to emit.
 584:    * @param flags the formatting flags to use.
 585:    * @param width the width to use.
 586:    * @param precision the precision to use.
 587:    * @param conversion the conversion character.
 588:    * @throws IOException if the output stream throws an I/O error.
 589:    */
 590:   private void booleanFormat(Object arg, int flags, int width, int precision,
 591:                  char conversion)
 592:     throws IOException
 593:   {
 594:     checkFlags(flags,
 595:            FormattableFlags.LEFT_JUSTIFY | FormattableFlags.UPPERCASE,
 596:            conversion);
 597:     String result;
 598:     if (arg instanceof Boolean)
 599:       result = String.valueOf((Boolean) arg);
 600:     else
 601:       result = arg == null ? "false" : "true";
 602:     genericFormat(result, flags, width, precision);
 603:   }
 604: 
 605:   /** 
 606:    * Emit a hash code.  
 607:    *
 608:    * @param arg the hash code to emit.
 609:    * @param flags the formatting flags to use.
 610:    * @param width the width to use.
 611:    * @param precision the precision to use.
 612:    * @param conversion the conversion character.
 613:    * @throws IOException if the output stream throws an I/O error.
 614:    */
 615:   private void hashCodeFormat(Object arg, int flags, int width, int precision,
 616:                   char conversion)
 617:     throws IOException
 618:   {
 619:     checkFlags(flags,
 620:            FormattableFlags.LEFT_JUSTIFY | FormattableFlags.UPPERCASE,
 621:            conversion);
 622:     genericFormat(arg == null ? "null" : Integer.toHexString(arg.hashCode()),
 623:           flags, width, precision);
 624:   }
 625: 
 626:   /** 
 627:    * Emit a String or Formattable conversion.  
 628:    *
 629:    * @param arg the String or Formattable to emit.
 630:    * @param flags the formatting flags to use.
 631:    * @param width the width to use.
 632:    * @param precision the precision to use.
 633:    * @param conversion the conversion character.
 634:    * @throws IOException if the output stream throws an I/O error.
 635:    */
 636:   private void stringFormat(Object arg, int flags, int width, int precision,
 637:                 char conversion)
 638:     throws IOException
 639:   {
 640:     if (arg instanceof Formattable)
 641:       {
 642:     checkFlags(flags,
 643:            (FormattableFlags.LEFT_JUSTIFY
 644:             | FormattableFlags.UPPERCASE
 645:             | FormattableFlags.ALTERNATE),
 646:            conversion);
 647:     Formattable fmt = (Formattable) arg;
 648:     fmt.formatTo(this, flags, width, precision);
 649:       }
 650:     else
 651:       {
 652:     checkFlags(flags,
 653:            FormattableFlags.LEFT_JUSTIFY | FormattableFlags.UPPERCASE,
 654:            conversion);
 655:     genericFormat(arg == null ? "null" : arg.toString(), flags, width,
 656:               precision);
 657:       }
 658:   }
 659: 
 660:   /** 
 661:    * Emit a character.  
 662:    *
 663:    * @param arg the character to emit.
 664:    * @param flags the formatting flags to use.
 665:    * @param width the width to use.
 666:    * @param precision the precision to use.
 667:    * @param conversion the conversion character.
 668:    * @throws IOException if the output stream throws an I/O error.
 669:    */
 670:   private void characterFormat(Object arg, int flags, int width, int precision,
 671:                    char conversion)
 672:     throws IOException
 673:   {
 674:     checkFlags(flags,
 675:            FormattableFlags.LEFT_JUSTIFY | FormattableFlags.UPPERCASE,
 676:            conversion);
 677:     noPrecision(precision);
 678: 
 679:     int theChar;
 680:     if (arg instanceof Character)
 681:       theChar = ((Character) arg).charValue();
 682:     else if (arg instanceof Byte)
 683:       theChar = (char) (((Byte) arg).byteValue ());
 684:     else if (arg instanceof Short)
 685:       theChar = (char) (((Short) arg).shortValue ());
 686:     else if (arg instanceof Integer)
 687:       {
 688:     theChar = ((Integer) arg).intValue();
 689:     if (! Character.isValidCodePoint(theChar))
 690:       throw new IllegalFormatCodePointException(theChar);
 691:       }
 692:     else
 693:       throw new IllegalFormatConversionException(conversion, arg.getClass());
 694:     String result = new String(Character.toChars(theChar));
 695:     genericFormat(result, flags, width, precision);
 696:   }
 697: 
 698:   /** 
 699:    * Emit a '%'.
 700:    *
 701:    * @param flags the formatting flags to use.
 702:    * @param width the width to use.
 703:    * @param precision the precision to use.
 704:    * @throws IOException if the output stream throws an I/O error.
 705:    */
 706:   private void percentFormat(int flags, int width, int precision)
 707:     throws IOException
 708:   {
 709:     checkFlags(flags, FormattableFlags.LEFT_JUSTIFY, '%');
 710:     noPrecision(precision);
 711:     genericFormat("%", flags, width, precision);
 712:   }
 713: 
 714:   /** 
 715:    * Emit a newline.
 716:    *
 717:    * @param flags the formatting flags to use.
 718:    * @param width the width to use.
 719:    * @param precision the precision to use.
 720:    * @throws IOException if the output stream throws an I/O error.
 721:    */
 722:   private void newLineFormat(int flags, int width, int precision)
 723:     throws IOException
 724:   {
 725:     checkFlags(flags, 0, 'n');
 726:     noPrecision(precision);
 727:     if (width != -1)
 728:       throw new IllegalFormatWidthException(width);
 729:     genericFormat(lineSeparator, flags, width, precision);
 730:   }
 731: 
 732:   /**
 733:    * Helper method to do initial formatting and checking for integral
 734:    * conversions.
 735:    *
 736:    * @param arg the formatted argument.
 737:    * @param flags the formatting flags to use.
 738:    * @param width the width to use.
 739:    * @param precision the precision to use.
 740:    * @param radix the radix of the number.
 741:    * @param conversion the conversion character.
 742:    * @return the result.
 743:    */
 744:   private StringBuilder basicIntegralConversion(Object arg, int flags,
 745:                         int width, int precision,
 746:                         int radix, char conversion)
 747:   {
 748:     assert radix == 8 || radix == 10 || radix == 16;
 749:     noPrecision(precision);
 750: 
 751:     // Some error checking.
 752:     if ((flags & FormattableFlags.PLUS) != 0
 753:     && (flags & FormattableFlags.SPACE) != 0)
 754:       throw new IllegalFormatFlagsException(getName(flags));
 755: 
 756:     if ((flags & FormattableFlags.LEFT_JUSTIFY) != 0 && width == -1)
 757:       throw new MissingFormatWidthException("fixme");
 758: 
 759:     // Do the base translation of the value to a string.
 760:     String result;
 761:     int basicFlags = (FormattableFlags.LEFT_JUSTIFY
 762:               // We already handled any possible error when
 763:               // parsing.
 764:               | FormattableFlags.UPPERCASE
 765:               | FormattableFlags.ZERO);
 766:     if (radix == 10)
 767:       basicFlags |= (FormattableFlags.PLUS
 768:              | FormattableFlags.SPACE
 769:              | FormattableFlags.COMMA
 770:              | FormattableFlags.PAREN);
 771:     else
 772:       basicFlags |= FormattableFlags.ALTERNATE;
 773: 
 774:     if (arg instanceof BigInteger)
 775:       {
 776:     checkFlags(flags,
 777:            (basicFlags
 778:             | FormattableFlags.PLUS
 779:             | FormattableFlags.SPACE
 780:             | FormattableFlags.PAREN),
 781:            conversion);
 782:     BigInteger bi = (BigInteger) arg;
 783:     result = bi.toString(radix);
 784:       }
 785:     else if (arg instanceof Number
 786:          && ! (arg instanceof Float)
 787:          && ! (arg instanceof Double))
 788:       {
 789:     checkFlags(flags, basicFlags, conversion);
 790:     long value = ((Number) arg).longValue ();
 791:     if (radix == 8)
 792:       result = Long.toOctalString(value);
 793:     else if (radix == 16)
 794:       result = Long.toHexString(value);
 795:     else
 796:       result = Long.toString(value);
 797:       }
 798:     else
 799:       throw new IllegalFormatConversionException(conversion, arg.getClass());
 800: 
 801:     return new StringBuilder(result);
 802:   }
 803: 
 804:   /** 
 805:    * Emit a hex or octal value.  
 806:    * 
 807:    * @param arg the hexadecimal or octal value.
 808:    * @param flags the formatting flags to use.
 809:    * @param width the width to use.
 810:    * @param precision the precision to use.
 811:    * @param radix the radix of the number.
 812:    * @param conversion the conversion character.
 813:    * @throws IOException if the output stream throws an I/O error.
 814:    */
 815:   private void hexOrOctalConversion(Object arg, int flags, int width,
 816:                     int precision, int radix,
 817:                     char conversion)
 818:     throws IOException
 819:   {
 820:     assert radix == 8 || radix == 16;
 821: 
 822:     StringBuilder builder = basicIntegralConversion(arg, flags, width,
 823:                             precision, radix,
 824:                             conversion);
 825:     int insertPoint = 0;
 826: 
 827:     // Insert the sign.
 828:     if (builder.charAt(0) == '-')
 829:       {
 830:     // Already inserted.  Note that we don't insert a sign, since
 831:     // the only case where it is needed it BigInteger, and it has
 832:     // already been inserted by toString.
 833:     ++insertPoint;
 834:       }
 835:     else if ((flags & FormattableFlags.PLUS) != 0)
 836:       {
 837:     builder.insert(insertPoint, '+');
 838:     ++insertPoint;
 839:       }
 840:     else if ((flags & FormattableFlags.SPACE) != 0)
 841:       {
 842:     builder.insert(insertPoint, ' ');
 843:     ++insertPoint;
 844:       }
 845: 
 846:     // Insert the radix prefix.
 847:     if ((flags & FormattableFlags.ALTERNATE) != 0)
 848:       {
 849:     builder.insert(insertPoint, radix == 8 ? "0" : "0x");
 850:     insertPoint += radix == 8 ? 1 : 2;
 851:       }
 852: 
 853:     // Now justify the result.
 854:     int resultWidth = builder.length();
 855:     if (resultWidth < width)
 856:       {
 857:     char fill = ((flags & FormattableFlags.ZERO) != 0) ? '0' : ' ';
 858:     if ((flags & FormattableFlags.LEFT_JUSTIFY) != 0)
 859:       {
 860:         // Left justify.  
 861:         if (fill == ' ')
 862:           insertPoint = builder.length();
 863:       }
 864:     else
 865:       {
 866:         // Right justify.  Insert spaces before the radix prefix
 867:         // and sign.
 868:         insertPoint = 0;
 869:       }
 870:     while (resultWidth++ < width)
 871:       builder.insert(insertPoint, fill);
 872:       }
 873: 
 874:     String result = builder.toString();
 875:     if ((flags & FormattableFlags.UPPERCASE) != 0)
 876:       {
 877:     if (fmtLocale == null)
 878:       result = result.toUpperCase();
 879:     else
 880:       result = result.toUpperCase(fmtLocale);
 881:       }
 882: 
 883:     out.append(result);
 884:   }
 885: 
 886:   /** 
 887:    * Emit a decimal value.  
 888:    * 
 889:    * @param arg the hexadecimal or octal value.
 890:    * @param flags the formatting flags to use.
 891:    * @param width the width to use.
 892:    * @param precision the precision to use.
 893:    * @param conversion the conversion character.
 894:    * @throws IOException if the output stream throws an I/O error.
 895:    */
 896:   private void decimalConversion(Object arg, int flags, int width,
 897:                  int precision, char conversion)
 898:     throws IOException
 899:   {
 900:     StringBuilder builder = basicIntegralConversion(arg, flags, width,
 901:                             precision, 10,
 902:                             conversion);
 903:     boolean isNegative = false;
 904:     if (builder.charAt(0) == '-')
 905:       {
 906:     // Sign handling is done during localization.
 907:     builder.deleteCharAt(0);
 908:     isNegative = true;
 909:       }
 910: 
 911:     applyLocalization(builder, flags, width, isNegative);
 912:     genericFormat(builder.toString(), flags, width, precision);
 913:   }
 914: 
 915:   /** 
 916:    * Emit a single date or time conversion to a StringBuilder.  
 917:    *
 918:    * @param builder the builder to write to.
 919:    * @param cal the calendar to use in the conversion.
 920:    * @param conversion the formatting character to specify the type of data.
 921:    * @param syms the date formatting symbols.
 922:    */
 923:   private void singleDateTimeConversion(StringBuilder builder, Calendar cal,
 924:                     char conversion,
 925:                     DateFormatSymbols syms)
 926:   {
 927:     int oldLen = builder.length();
 928:     int digits = -1;
 929:     switch (conversion)
 930:       {
 931:       case 'H':
 932:     builder.append(cal.get(Calendar.HOUR_OF_DAY));
 933:     digits = 2;
 934:     break;
 935:       case 'I':
 936:     builder.append(cal.get(Calendar.HOUR));
 937:     digits = 2;
 938:     break;
 939:       case 'k':
 940:     builder.append(cal.get(Calendar.HOUR_OF_DAY));
 941:     break;
 942:       case 'l':
 943:     builder.append(cal.get(Calendar.HOUR));
 944:     break;
 945:       case 'M':
 946:     builder.append(cal.get(Calendar.MINUTE));
 947:     digits = 2;
 948:     break;
 949:       case 'S':
 950:     builder.append(cal.get(Calendar.SECOND));
 951:     digits = 2;
 952:     break;
 953:       case 'N':
 954:     // FIXME: nanosecond ...
 955:     digits = 9;
 956:     break;
 957:       case 'p':
 958:     {
 959:       int ampm = cal.get(Calendar.AM_PM);
 960:       builder.append(syms.getAmPmStrings()[ampm]);
 961:     }
 962:     break;
 963:       case 'z':
 964:     {
 965:       int zone = cal.get(Calendar.ZONE_OFFSET) / (1000 * 60);
 966:       builder.append(zone);
 967:       digits = 4;
 968:       // Skip the '-' sign.
 969:       if (zone < 0)
 970:         ++oldLen;
 971:     }
 972:     break;
 973:       case 'Z':
 974:     {
 975:       // FIXME: DST?
 976:       int zone = cal.get(Calendar.ZONE_OFFSET) / (1000 * 60 * 60);
 977:       String[][] zs = syms.getZoneStrings();
 978:       builder.append(zs[zone + 12][1]);
 979:     }
 980:     break;
 981:       case 's':
 982:     {
 983:       long val = cal.getTime().getTime();
 984:       builder.append(val / 1000);
 985:     }
 986:     break;
 987:       case 'Q':
 988:     {
 989:       long val = cal.getTime().getTime();
 990:       builder.append(val);
 991:     }
 992:     break;
 993:       case 'B':
 994:     {
 995:       int month = cal.get(Calendar.MONTH);
 996:       builder.append(syms.getMonths()[month]);
 997:     }
 998:     break;
 999:       case 'b':
1000:       case 'h':
1001:     {
1002:       int month = cal.get(Calendar.MONTH);
1003:       builder.append(syms.getShortMonths()[month]);
1004:     }
1005:     break;
1006:       case 'A':
1007:     {
1008:       int day = cal.get(Calendar.DAY_OF_WEEK);
1009:       builder.append(syms.getWeekdays()[day]);
1010:     }
1011:     break;
1012:       case 'a':
1013:     {
1014:       int day = cal.get(Calendar.DAY_OF_WEEK);
1015:       builder.append(syms.getShortWeekdays()[day]);
1016:     }
1017:     break;
1018:       case 'C':
1019:     builder.append(cal.get(Calendar.YEAR) / 100);
1020:     digits = 2;
1021:     break;
1022:       case 'Y':
1023:     builder.append(cal.get(Calendar.YEAR));
1024:     digits = 4;
1025:     break;
1026:       case 'y':
1027:     builder.append(cal.get(Calendar.YEAR) % 100);
1028:     digits = 2;
1029:     break;
1030:       case 'j':
1031:     builder.append(cal.get(Calendar.DAY_OF_YEAR));
1032:     digits = 3;
1033:     break;
1034:       case 'm':
1035:     builder.append(cal.get(Calendar.MONTH) + 1);
1036:     digits = 2;
1037:     break;
1038:       case 'd':
1039:     builder.append(cal.get(Calendar.DAY_OF_MONTH));
1040:     digits = 2;
1041:     break;
1042:       case 'e':
1043:     builder.append(cal.get(Calendar.DAY_OF_MONTH));
1044:     break;
1045:       case 'R':
1046:     singleDateTimeConversion(builder, cal, 'H', syms);
1047:     builder.append(':');
1048:     singleDateTimeConversion(builder, cal, 'M', syms);
1049:     break;
1050:       case 'T':
1051:     singleDateTimeConversion(builder, cal, 'H', syms);
1052:     builder.append(':');
1053:     singleDateTimeConversion(builder, cal, 'M', syms);
1054:     builder.append(':');
1055:     singleDateTimeConversion(builder, cal, 'S', syms);
1056:     break;
1057:       case 'r':
1058:     singleDateTimeConversion(builder, cal, 'I', syms);
1059:     builder.append(':');
1060:     singleDateTimeConversion(builder, cal, 'M', syms);
1061:     builder.append(':');
1062:     singleDateTimeConversion(builder, cal, 'S', syms);
1063:     builder.append(' ');
1064:     singleDateTimeConversion(builder, cal, 'p', syms);
1065:     break;
1066:       case 'D':
1067:     singleDateTimeConversion(builder, cal, 'm', syms);
1068:     builder.append('/');
1069:         singleDateTimeConversion(builder, cal, 'd', syms);
1070:     builder.append('/');
1071:     singleDateTimeConversion(builder, cal, 'y', syms);
1072:     break;
1073:       case 'F':
1074:     singleDateTimeConversion(builder, cal, 'Y', syms);
1075:     builder.append('-');
1076:     singleDateTimeConversion(builder, cal, 'm', syms);
1077:     builder.append('-');
1078:     singleDateTimeConversion(builder, cal, 'd', syms);
1079:     break;
1080:       case 'c':
1081:     singleDateTimeConversion(builder, cal, 'a', syms);
1082:     builder.append(' ');
1083:     singleDateTimeConversion(builder, cal, 'b', syms);
1084:     builder.append(' ');
1085:     singleDateTimeConversion(builder, cal, 'd', syms);
1086:     builder.append(' ');
1087:     singleDateTimeConversion(builder, cal, 'T', syms);
1088:     builder.append(' ');
1089:     singleDateTimeConversion(builder, cal, 'Z', syms);
1090:     builder.append(' ');
1091:     singleDateTimeConversion(builder, cal, 'Y', syms);
1092:     break;
1093:       default:
1094:     throw new UnknownFormatConversionException(String.valueOf(conversion));
1095:       }
1096: 
1097:     if (digits > 0)
1098:       {
1099:     int newLen = builder.length();
1100:     int delta = newLen - oldLen;
1101:     while (delta++ < digits)
1102:       builder.insert(oldLen, '0');
1103:       }
1104:   }
1105: 
1106:   /**
1107:    * Emit a date or time value.
1108:    *
1109:    * @param arg the date or time value.
1110:    * @param flags the formatting flags to use.
1111:    * @param width the width to use.
1112:    * @param precision the precision to use.
1113:    * @param conversion the conversion character.
1114:    * @param subConversion the sub conversion character.
1115:    * @throws IOException if the output stream throws an I/O error.
1116:    */
1117:   private void dateTimeConversion(Object arg, int flags, int width,
1118:                   int precision, char conversion,
1119:                   char subConversion)
1120:     throws IOException
1121:   {
1122:     noPrecision(precision);
1123:     checkFlags(flags,
1124:            FormattableFlags.LEFT_JUSTIFY | FormattableFlags.UPPERCASE,
1125:            conversion);
1126: 
1127:     Calendar cal;
1128:     if (arg instanceof Calendar)
1129:       cal = (Calendar) arg;
1130:     else
1131:       {
1132:     Date date;
1133:     if (arg instanceof Date)
1134:       date = (Date) arg;
1135:     else if (arg instanceof Long)
1136:       date = new Date(((Long) arg).longValue());
1137:     else
1138:       throw new IllegalFormatConversionException(conversion,
1139:                              arg.getClass());
1140:     if (fmtLocale == null)
1141:       cal = Calendar.getInstance();
1142:     else
1143:       cal = Calendar.getInstance(fmtLocale);
1144:     cal.setTime(date);
1145:       }
1146: 
1147:     // We could try to be more efficient by computing this lazily.
1148:     DateFormatSymbols syms;
1149:     if (fmtLocale == null)
1150:       syms = new DateFormatSymbols();
1151:     else
1152:       syms = new DateFormatSymbols(fmtLocale);
1153: 
1154:     StringBuilder result = new StringBuilder();
1155:     singleDateTimeConversion(result, cal, subConversion, syms);
1156: 
1157:     genericFormat(result.toString(), flags, width, precision);
1158:   }
1159: 
1160:   /**
1161:    * Advance the internal parsing index, and throw an exception
1162:    * on overrun.
1163:    *
1164:    * @throws IllegalArgumentException on overrun.
1165:    */
1166:   private void advance()
1167:   {
1168:     ++index;
1169:     if (index >= length)
1170:       {
1171:     // FIXME: what exception here?
1172:     throw new IllegalArgumentException();
1173:       }
1174:   }
1175: 
1176:   /**
1177:    * Parse an integer appearing in the format string.  Will return -1
1178:    * if no integer was found.
1179:    *
1180:    * @return the parsed integer.
1181:    */
1182:   private int parseInt()
1183:   {
1184:     int start = index;
1185:     while (Character.isDigit(format.charAt(index)))
1186:       advance();
1187:     if (start == index)
1188:       return -1;
1189:     return Integer.decode(format.substring(start, index));
1190:   }
1191: 
1192:   /**
1193:    * Parse the argument index.  Returns -1 if there was no index, 0 if
1194:    * we should re-use the previous index, and a positive integer to
1195:    * indicate an absolute index.
1196:    *
1197:    * @return the parsed argument index.
1198:    */
1199:   private int parseArgumentIndex()
1200:   {
1201:     int result = -1;
1202:     int start = index;
1203:     if (format.charAt(index) == '<')
1204:       {
1205:     result = 0;
1206:     advance();
1207:       }
1208:     else if (Character.isDigit(format.charAt(index)))
1209:       {
1210:     result = parseInt();
1211:     if (format.charAt(index) == '$')
1212:       advance();
1213:     else
1214:       {
1215:         // Reset.
1216:         index = start;
1217:         result = -1;
1218:       }
1219:       }
1220:     return result;
1221:   }
1222: 
1223:   /**
1224:    * Parse a set of flags and return a bit mask of values from
1225:    * FormattableFlags.  Will throw an exception if a flag is
1226:    * duplicated.
1227:    *
1228:    * @return the parsed flags.
1229:    */
1230:   private int parseFlags()
1231:   {
1232:     int value = 0;
1233:     int start = index;
1234:     while (true)
1235:       {
1236:     int x = FLAGS.indexOf(format.charAt(index));
1237:     if (x == -1)
1238:       break;
1239:     int newValue = 1 << x;
1240:     if ((value & newValue) != 0)
1241:       throw new DuplicateFormatFlagsException(format.substring(start,
1242:                                    index + 1));
1243:     value |= newValue;
1244:     advance();
1245:       }
1246:     return value;
1247:   }
1248: 
1249:   /**
1250:    * Parse the width part of a format string.  Returns -1 if no width
1251:    * was specified.
1252:    *
1253:    * @return the parsed width.
1254:    */
1255:   private int parseWidth()
1256:   {
1257:     return parseInt();
1258:   }
1259: 
1260:   /**
1261:    * If the current character is '.', parses the precision part of a
1262:    * format string.  Returns -1 if no precision was specified.
1263:    *
1264:    * @return the parsed precision.
1265:    */
1266:   private int parsePrecision()
1267:   {
1268:     if (format.charAt(index) != '.')
1269:       return -1;
1270:     advance();
1271:     int precision = parseInt();
1272:     if (precision == -1)
1273:       // FIXME
1274:       throw new IllegalArgumentException();
1275:     return precision;
1276:   }
1277: 
1278:   /**
1279:    * Outputs a formatted string based on the supplied specification,
1280:    * <code>fmt</code>, and its arguments using the specified locale.
1281:    * The locale of the formatter does not change as a result; the
1282:    * specified locale is just used for this particular formatting
1283:    * operation.  If the locale is <code>null</code>, then no
1284:    * localization is applied.
1285:    *
1286:    * @param loc the locale to use for this format.
1287:    * @param fmt the format specification.
1288:    * @param args the arguments to apply to the specification.
1289:    * @throws IllegalFormatException if there is a problem with
1290:    *                                the syntax of the format
1291:    *                                specification or a mismatch
1292:    *                                between it and the arguments.
1293:    * @throws FormatterClosedException if the formatter is closed.
1294:    */ 
1295:   public Formatter format(Locale loc, String fmt, Object... args)
1296:   {
1297:     if (closed)
1298:       throw new FormatterClosedException();
1299: 
1300:     // Note the arguments are indexed starting at 1.
1301:     int implicitArgumentIndex = 1;
1302:     int previousArgumentIndex = 0;
1303: 
1304:     try
1305:       {
1306:     fmtLocale = loc;
1307:     format = fmt;
1308:     length = format.length();
1309:     for (index = 0; index < length; ++index)
1310:       {
1311:         char c = format.charAt(index);
1312:         if (c != '%')
1313:           {
1314:         out.append(c);
1315:         continue;
1316:           }
1317: 
1318:         int start = index;
1319:         advance();
1320: 
1321:         // We do the needed post-processing of this later, when we
1322:         // determine whether an argument is actually needed by
1323:         // this conversion.
1324:         int argumentIndex = parseArgumentIndex();
1325: 
1326:         int flags = parseFlags();
1327:         int width = parseWidth();
1328:         int precision = parsePrecision();
1329:         char origConversion = format.charAt(index);
1330:         char conversion = origConversion;
1331:         if (Character.isUpperCase(conversion))
1332:           {
1333:         flags |= FormattableFlags.UPPERCASE;
1334:         conversion = Character.toLowerCase(conversion);
1335:           }
1336: 
1337:         Object argument = null;
1338:         if (conversion == '%' || conversion == 'n')
1339:           {
1340:         if (argumentIndex != -1)
1341:           {
1342:             // FIXME: not sure about this.
1343:             throw new UnknownFormatConversionException("FIXME");
1344:           }
1345:           }
1346:         else
1347:           {
1348:         if (argumentIndex == -1)
1349:           argumentIndex = implicitArgumentIndex++;
1350:         else if (argumentIndex == 0)
1351:           argumentIndex = previousArgumentIndex;
1352:         // Argument indices start at 1 but array indices at 0.
1353:         --argumentIndex;
1354:         if (argumentIndex < 0 || argumentIndex >= args.length)
1355:           throw new MissingFormatArgumentException(format.substring(start, index));
1356:         argument = args[argumentIndex];
1357:           }
1358: 
1359:         switch (conversion)
1360:           {
1361:           case 'b':
1362:         booleanFormat(argument, flags, width, precision,
1363:                   origConversion);
1364:         break;
1365:           case 'h':
1366:         hashCodeFormat(argument, flags, width, precision,
1367:                    origConversion);
1368:         break;
1369:           case 's':
1370:         stringFormat(argument, flags, width, precision,
1371:                  origConversion);
1372:         break;
1373:           case 'c':
1374:         characterFormat(argument, flags, width, precision,
1375:                 origConversion);
1376:         break;
1377:           case 'd':
1378:         checkFlags(flags & FormattableFlags.UPPERCASE, 0, 'd');
1379:         decimalConversion(argument, flags, width, precision,
1380:                   origConversion);
1381:         break;
1382:           case 'o':
1383:         checkFlags(flags & FormattableFlags.UPPERCASE, 0, 'o');
1384:         hexOrOctalConversion(argument, flags, width, precision, 8,
1385:                      origConversion);
1386:         break;
1387:           case 'x':
1388:         hexOrOctalConversion(argument, flags, width, precision, 16,
1389:                      origConversion);
1390:           case 'e':
1391:         // scientificNotationConversion();
1392:         break;
1393:           case 'f':
1394:         // floatingDecimalConversion();
1395:         break;
1396:           case 'g':
1397:         // smartFloatingConversion();
1398:         break;
1399:           case 'a':
1400:         // hexFloatingConversion();
1401:         break;
1402:           case 't':
1403:         advance();
1404:         char subConversion = format.charAt(index);
1405:         dateTimeConversion(argument, flags, width, precision,
1406:                    origConversion, subConversion);
1407:         break;
1408:           case '%':
1409:         percentFormat(flags, width, precision);
1410:         break;
1411:           case 'n':
1412:         newLineFormat(flags, width, precision);
1413:         break;
1414:           default:
1415:         throw new UnknownFormatConversionException(String.valueOf(origConversion));
1416:           }
1417:       }
1418:       }
1419:     catch (IOException exc)
1420:       {
1421:     ioException = exc;
1422:       }
1423:     return this;
1424:   }
1425: 
1426:   /**
1427:    * Outputs a formatted string based on the supplied specification,
1428:    * <code>fmt</code>, and its arguments using the formatter's locale.
1429:    *
1430:    * @param fmt the format specification.
1431:    * @param args the arguments to apply to the specification.
1432:    * @throws IllegalFormatException if there is a problem with
1433:    *                                the syntax of the format
1434:    *                                specification or a mismatch
1435:    *                                between it and the arguments.
1436:    * @throws FormatterClosedException if the formatter is closed.
1437:    */
1438:   public Formatter format(String format, Object... args)
1439:   {
1440:     return format(locale, format, args);
1441:   }
1442: 
1443:   /**
1444:    * Returns the last I/O exception thrown by the
1445:    * <code>append()</code> operation of the underlying
1446:    * output stream.
1447:    *
1448:    * @return the last I/O exception.
1449:    */
1450:   public IOException ioException()
1451:   {
1452:     return ioException;
1453:   }
1454: 
1455:   /**
1456:    * Returns the locale used by this formatter.
1457:    *
1458:    * @return the formatter's locale.
1459:    * @throws FormatterClosedException if the formatter is closed.
1460:    */
1461:   public Locale locale()
1462:   {
1463:     if (closed)
1464:       throw new FormatterClosedException();
1465:     return locale;
1466:   }
1467: 
1468:   /**
1469:    * Returns the output stream used by this formatter.
1470:    *
1471:    * @return the formatter's output stream.
1472:    * @throws FormatterClosedException if the formatter is closed.
1473:    */
1474:   public Appendable out()
1475:   {
1476:     if (closed)
1477:       throw new FormatterClosedException();
1478:     return out;
1479:   }
1480: 
1481:   /**
1482:    * Returns the result of applying {@link Object#toString()}
1483:    * to the underlying output stream.  The results returned
1484:    * depend on the particular {@link Appendable} being used.
1485:    * For example, a {@link StringBuilder} will return the
1486:    * formatted output but an I/O stream will not.
1487:    *
1488:    * @throws FormatterClosedException if the formatter is closed.
1489:    */
1490:   public String toString()
1491:   {
1492:     if (closed)
1493:       throw new FormatterClosedException();
1494:     return out.toString();
1495:   }
1496: }