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