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:     if (arg == null)
 682:       {
 683:         genericFormat("null", flags, width, precision);
 684:         return;
 685:       }
 686: 
 687:     int theChar;
 688:     if (arg instanceof Character)
 689:       theChar = ((Character) arg).charValue();
 690:     else if (arg instanceof Byte)
 691:       theChar = (char) (((Byte) arg).byteValue ());
 692:     else if (arg instanceof Short)
 693:       theChar = (char) (((Short) arg).shortValue ());
 694:     else if (arg instanceof Integer)
 695:       {
 696:         theChar = ((Integer) arg).intValue();
 697:         if (! Character.isValidCodePoint(theChar))
 698:           throw new IllegalFormatCodePointException(theChar);
 699:       }
 700:     else
 701:       throw new IllegalFormatConversionException(conversion, arg.getClass());
 702:     String result = new String(Character.toChars(theChar));
 703:     genericFormat(result, flags, width, precision);
 704:   }
 705: 
 706:   /**
 707:    * Emit a '%'.
 708:    *
 709:    * @param flags the formatting flags to use.
 710:    * @param width the width to use.
 711:    * @param precision the precision to use.
 712:    * @throws IOException if the output stream throws an I/O error.
 713:    */
 714:   private void percentFormat(int flags, int width, int precision)
 715:     throws IOException
 716:   {
 717:     checkFlags(flags, FormattableFlags.LEFT_JUSTIFY, '%');
 718:     noPrecision(precision);
 719:     genericFormat("%", flags, width, precision);
 720:   }
 721: 
 722:   /**
 723:    * Emit a newline.
 724:    *
 725:    * @param flags the formatting flags to use.
 726:    * @param width the width to use.
 727:    * @param precision the precision to use.
 728:    * @throws IOException if the output stream throws an I/O error.
 729:    */
 730:   private void newLineFormat(int flags, int width, int precision)
 731:     throws IOException
 732:   {
 733:     checkFlags(flags, 0, 'n');
 734:     noPrecision(precision);
 735:     if (width != -1)
 736:       throw new IllegalFormatWidthException(width);
 737:     genericFormat(lineSeparator, flags, width, precision);
 738:   }
 739: 
 740:   /**
 741:    * Helper method to do initial formatting and checking for integral
 742:    * conversions.
 743:    *
 744:    * @param arg the formatted argument.
 745:    * @param flags the formatting flags to use.
 746:    * @param width the width to use.
 747:    * @param precision the precision to use.
 748:    * @param radix the radix of the number.
 749:    * @param conversion the conversion character.
 750:    * @return the result.
 751:    */
 752:   private CPStringBuilder basicIntegralConversion(Object arg, int flags,
 753:                                                   int width, int precision,
 754:                                                   int radix, char conversion)
 755:   {
 756:     assert radix == 8 || radix == 10 || radix == 16;
 757: 
 758:     if (arg == null)
 759:       {
 760:         return new CPStringBuilder("null");
 761:       }
 762: 
 763:     noPrecision(precision);
 764: 
 765:     // Some error checking.
 766:     if ((flags & FormattableFlags.PLUS) != 0
 767:         && (flags & FormattableFlags.SPACE) != 0)
 768:       throw new IllegalFormatFlagsException(getName(flags));
 769: 
 770:     if ((flags & FormattableFlags.LEFT_JUSTIFY) != 0 && width == -1)
 771:       throw new MissingFormatWidthException("fixme");
 772: 
 773:     // Do the base translation of the value to a string.
 774:     String result;
 775:     int basicFlags = (FormattableFlags.LEFT_JUSTIFY
 776:                       // We already handled any possible error when
 777:                       // parsing.
 778:                       | FormattableFlags.UPPERCASE
 779:                       | FormattableFlags.ZERO);
 780:     if (radix == 10)
 781:       basicFlags |= (FormattableFlags.PLUS
 782:                      | FormattableFlags.SPACE
 783:                      | FormattableFlags.COMMA
 784:                      | FormattableFlags.PAREN);
 785:     else
 786:       basicFlags |= FormattableFlags.ALTERNATE;
 787: 
 788:     if (arg instanceof BigInteger)
 789:       {
 790:         checkFlags(flags,
 791:                    (basicFlags
 792:                     | FormattableFlags.PLUS
 793:                     | FormattableFlags.SPACE
 794:                     | FormattableFlags.PAREN),
 795:                    conversion);
 796:         BigInteger bi = (BigInteger) arg;
 797:         result = bi.toString(radix);
 798:       }
 799:     else if (arg instanceof Number
 800:              && ! (arg instanceof Float)
 801:              && ! (arg instanceof Double))
 802:       {
 803:         checkFlags(flags, basicFlags, conversion);
 804:         long value = ((Number) arg).longValue ();
 805:         if (radix == 8)
 806:           result = Long.toOctalString(value);
 807:         else if (radix == 16)
 808:           result = Long.toHexString(value);
 809:         else
 810:           result = Long.toString(value);
 811:       }
 812:     else
 813:       throw new IllegalFormatConversionException(conversion, arg.getClass());
 814: 
 815:     return new CPStringBuilder(result);
 816:   }
 817: 
 818:   /**
 819:    * Emit a hex or octal value.
 820:    *
 821:    * @param arg the hexadecimal or octal value.
 822:    * @param flags the formatting flags to use.
 823:    * @param width the width to use.
 824:    * @param precision the precision to use.
 825:    * @param radix the radix of the number.
 826:    * @param conversion the conversion character.
 827:    * @throws IOException if the output stream throws an I/O error.
 828:    */
 829:   private void hexOrOctalConversion(Object arg, int flags, int width,
 830:                                     int precision, int radix,
 831:                                     char conversion)
 832:     throws IOException
 833:   {
 834:     assert radix == 8 || radix == 16;
 835: 
 836:     CPStringBuilder builder = basicIntegralConversion(arg, flags, width,
 837:                                                       precision, radix,
 838:                                                       conversion);
 839:     int insertPoint = 0;
 840: 
 841:     // Insert the sign.
 842:     if (builder.charAt(0) == '-')
 843:       {
 844:         // Already inserted.  Note that we don't insert a sign, since
 845:         // the only case where it is needed it BigInteger, and it has
 846:         // already been inserted by toString.
 847:         ++insertPoint;
 848:       }
 849:     else if ((flags & FormattableFlags.PLUS) != 0)
 850:       {
 851:         builder.insert(insertPoint, '+');
 852:         ++insertPoint;
 853:       }
 854:     else if ((flags & FormattableFlags.SPACE) != 0)
 855:       {
 856:         builder.insert(insertPoint, ' ');
 857:         ++insertPoint;
 858:       }
 859: 
 860:     // Insert the radix prefix.
 861:     if ((flags & FormattableFlags.ALTERNATE) != 0)
 862:       {
 863:         builder.insert(insertPoint, radix == 8 ? "0" : "0x");
 864:         insertPoint += radix == 8 ? 1 : 2;
 865:       }
 866: 
 867:     // Now justify the result.
 868:     int resultWidth = builder.length();
 869:     if (resultWidth < width)
 870:       {
 871:         char fill = ((flags & FormattableFlags.ZERO) != 0) ? '0' : ' ';
 872:         if ((flags & FormattableFlags.LEFT_JUSTIFY) != 0)
 873:           {
 874:             // Left justify.
 875:             if (fill == ' ')
 876:               insertPoint = builder.length();
 877:           }
 878:         else
 879:           {
 880:             // Right justify.  Insert spaces before the radix prefix
 881:             // and sign.
 882:             insertPoint = 0;
 883:           }
 884:         while (resultWidth++ < width)
 885:           builder.insert(insertPoint, fill);
 886:       }
 887: 
 888:     String result = builder.toString();
 889:     if ((flags & FormattableFlags.UPPERCASE) != 0)
 890:       {
 891:         if (fmtLocale == null)
 892:           result = result.toUpperCase();
 893:         else
 894:           result = result.toUpperCase(fmtLocale);
 895:       }
 896: 
 897:     out.append(result);
 898:   }
 899: 
 900:   /**
 901:    * Emit a decimal value.
 902:    *
 903:    * @param arg the hexadecimal or octal value.
 904:    * @param flags the formatting flags to use.
 905:    * @param width the width to use.
 906:    * @param precision the precision to use.
 907:    * @param conversion the conversion character.
 908:    * @throws IOException if the output stream throws an I/O error.
 909:    */
 910:   private void decimalConversion(Object arg, int flags, int width,
 911:                                  int precision, char conversion)
 912:     throws IOException
 913:   {
 914:     CPStringBuilder builder = basicIntegralConversion(arg, flags, width,
 915:                                                       precision, 10,
 916:                                                       conversion);
 917:     boolean isNegative = false;
 918:     if (builder.charAt(0) == '-')
 919:       {
 920:         // Sign handling is done during localization.
 921:         builder.deleteCharAt(0);
 922:         isNegative = true;
 923:       }
 924: 
 925:     applyLocalization(builder, flags, width, isNegative);
 926:     genericFormat(builder.toString(), flags, width, precision);
 927:   }
 928: 
 929:   /**
 930:    * Emit a single date or time conversion to a StringBuilder.
 931:    *
 932:    * @param builder the builder to write to.
 933:    * @param cal the calendar to use in the conversion.
 934:    * @param conversion the formatting character to specify the type of data.
 935:    * @param syms the date formatting symbols.
 936:    */
 937:   private void singleDateTimeConversion(CPStringBuilder builder, Calendar cal,
 938:                                         char conversion,
 939:                                         DateFormatSymbols syms)
 940:   {
 941:     int oldLen = builder.length();
 942:     int digits = -1;
 943:     switch (conversion)
 944:       {
 945:       case 'H':
 946:         builder.append(cal.get(Calendar.HOUR_OF_DAY));
 947:         digits = 2;
 948:         break;
 949:       case 'I':
 950:         builder.append(cal.get(Calendar.HOUR));
 951:         digits = 2;
 952:         break;
 953:       case 'k':
 954:         builder.append(cal.get(Calendar.HOUR_OF_DAY));
 955:         break;
 956:       case 'l':
 957:         builder.append(cal.get(Calendar.HOUR));
 958:         break;
 959:       case 'M':
 960:         builder.append(cal.get(Calendar.MINUTE));
 961:         digits = 2;
 962:         break;
 963:       case 'S':
 964:         builder.append(cal.get(Calendar.SECOND));
 965:         digits = 2;
 966:         break;
 967:       case 'N':
 968:         // FIXME: nanosecond ...
 969:         digits = 9;
 970:         break;
 971:       case 'p':
 972:         {
 973:           int ampm = cal.get(Calendar.AM_PM);
 974:           builder.append(syms.getAmPmStrings()[ampm]);
 975:         }
 976:         break;
 977:       case 'z':
 978:         {
 979:           int zone = cal.get(Calendar.ZONE_OFFSET) / (1000 * 60);
 980:           builder.append(zone);
 981:           digits = 4;
 982:           // Skip the '-' sign.
 983:           if (zone < 0)
 984:             ++oldLen;
 985:         }
 986:         break;
 987:       case 'Z':
 988:         {
 989:           // FIXME: DST?
 990:           int zone = cal.get(Calendar.ZONE_OFFSET) / (1000 * 60 * 60);
 991:           String[][] zs = syms.getZoneStrings();
 992:           builder.append(zs[zone + 12][1]);
 993:         }
 994:         break;
 995:       case 's':
 996:         {
 997:           long val = cal.getTime().getTime();
 998:           builder.append(val / 1000);
 999:         }
1000:         break;
1001:       case 'Q':
1002:         {
1003:           long val = cal.getTime().getTime();
1004:           builder.append(val);
1005:         }
1006:         break;
1007:       case 'B':
1008:         {
1009:           int month = cal.get(Calendar.MONTH);
1010:           builder.append(syms.getMonths()[month]);
1011:         }
1012:         break;
1013:       case 'b':
1014:       case 'h':
1015:         {
1016:           int month = cal.get(Calendar.MONTH);
1017:           builder.append(syms.getShortMonths()[month]);
1018:         }
1019:         break;
1020:       case 'A':
1021:         {
1022:           int day = cal.get(Calendar.DAY_OF_WEEK);
1023:           builder.append(syms.getWeekdays()[day]);
1024:         }
1025:         break;
1026:       case 'a':
1027:         {
1028:           int day = cal.get(Calendar.DAY_OF_WEEK);
1029:           builder.append(syms.getShortWeekdays()[day]);
1030:         }
1031:         break;
1032:       case 'C':
1033:         builder.append(cal.get(Calendar.YEAR) / 100);
1034:         digits = 2;
1035:         break;
1036:       case 'Y':
1037:         builder.append(cal.get(Calendar.YEAR));
1038:         digits = 4;
1039:         break;
1040:       case 'y':
1041:         builder.append(cal.get(Calendar.YEAR) % 100);
1042:         digits = 2;
1043:         break;
1044:       case 'j':
1045:         builder.append(cal.get(Calendar.DAY_OF_YEAR));
1046:         digits = 3;
1047:         break;
1048:       case 'm':
1049:         builder.append(cal.get(Calendar.MONTH) + 1);
1050:         digits = 2;
1051:         break;
1052:       case 'd':
1053:         builder.append(cal.get(Calendar.DAY_OF_MONTH));
1054:         digits = 2;
1055:         break;
1056:       case 'e':
1057:         builder.append(cal.get(Calendar.DAY_OF_MONTH));
1058:         break;
1059:       case 'R':
1060:         singleDateTimeConversion(builder, cal, 'H', syms);
1061:         builder.append(':');
1062:         singleDateTimeConversion(builder, cal, 'M', syms);
1063:         break;
1064:       case 'T':
1065:         singleDateTimeConversion(builder, cal, 'H', syms);
1066:         builder.append(':');
1067:         singleDateTimeConversion(builder, cal, 'M', syms);
1068:         builder.append(':');
1069:         singleDateTimeConversion(builder, cal, 'S', syms);
1070:         break;
1071:       case 'r':
1072:         singleDateTimeConversion(builder, cal, 'I', syms);
1073:         builder.append(':');
1074:         singleDateTimeConversion(builder, cal, 'M', syms);
1075:         builder.append(':');
1076:         singleDateTimeConversion(builder, cal, 'S', syms);
1077:         builder.append(' ');
1078:         singleDateTimeConversion(builder, cal, 'p', syms);
1079:         break;
1080:       case 'D':
1081:         singleDateTimeConversion(builder, cal, 'm', syms);
1082:         builder.append('/');
1083:         singleDateTimeConversion(builder, cal, 'd', syms);
1084:         builder.append('/');
1085:         singleDateTimeConversion(builder, cal, 'y', syms);
1086:         break;
1087:       case 'F':
1088:         singleDateTimeConversion(builder, cal, 'Y', syms);
1089:         builder.append('-');
1090:         singleDateTimeConversion(builder, cal, 'm', syms);
1091:         builder.append('-');
1092:         singleDateTimeConversion(builder, cal, 'd', syms);
1093:         break;
1094:       case 'c':
1095:         singleDateTimeConversion(builder, cal, 'a', syms);
1096:         builder.append(' ');
1097:         singleDateTimeConversion(builder, cal, 'b', syms);
1098:         builder.append(' ');
1099:         singleDateTimeConversion(builder, cal, 'd', syms);
1100:         builder.append(' ');
1101:         singleDateTimeConversion(builder, cal, 'T', syms);
1102:         builder.append(' ');
1103:         singleDateTimeConversion(builder, cal, 'Z', syms);
1104:         builder.append(' ');
1105:         singleDateTimeConversion(builder, cal, 'Y', syms);
1106:         break;
1107:       default:
1108:         throw new UnknownFormatConversionException(String.valueOf(conversion));
1109:       }
1110: 
1111:     if (digits > 0)
1112:       {
1113:         int newLen = builder.length();
1114:         int delta = newLen - oldLen;
1115:         while (delta++ < digits)
1116:           builder.insert(oldLen, '0');
1117:       }
1118:   }
1119: 
1120:   /**
1121:    * Emit a date or time value.
1122:    *
1123:    * @param arg the date or time value.
1124:    * @param flags the formatting flags to use.
1125:    * @param width the width to use.
1126:    * @param precision the precision to use.
1127:    * @param conversion the conversion character.
1128:    * @param subConversion the sub conversion character.
1129:    * @throws IOException if the output stream throws an I/O error.
1130:    */
1131:   private void dateTimeConversion(Object arg, int flags, int width,
1132:                                   int precision, char conversion,
1133:                                   char subConversion)
1134:     throws IOException
1135:   {
1136:     noPrecision(precision);
1137:     checkFlags(flags,
1138:                FormattableFlags.LEFT_JUSTIFY | FormattableFlags.UPPERCASE,
1139:                conversion);
1140: 
1141:     Calendar cal;
1142:     if (arg instanceof Calendar)
1143:       cal = (Calendar) arg;
1144:     else
1145:       {
1146:         Date date;
1147:         if (arg instanceof Date)
1148:           date = (Date) arg;
1149:         else if (arg instanceof Long)
1150:           date = new Date(((Long) arg).longValue());
1151:         else
1152:           throw new IllegalFormatConversionException(conversion,
1153:                                                      arg.getClass());
1154:         if (fmtLocale == null)
1155:           cal = Calendar.getInstance();
1156:         else
1157:           cal = Calendar.getInstance(fmtLocale);
1158:         cal.setTime(date);
1159:       }
1160: 
1161:     // We could try to be more efficient by computing this lazily.
1162:     DateFormatSymbols syms;
1163:     if (fmtLocale == null)
1164:       syms = new DateFormatSymbols();
1165:     else
1166:       syms = new DateFormatSymbols(fmtLocale);
1167: 
1168:     CPStringBuilder result = new CPStringBuilder();
1169:     singleDateTimeConversion(result, cal, subConversion, syms);
1170: 
1171:     genericFormat(result.toString(), flags, width, precision);
1172:   }
1173: 
1174:   /**
1175:    * Advance the internal parsing index, and throw an exception
1176:    * on overrun.
1177:    *
1178:    * @throws IllegalArgumentException on overrun.
1179:    */
1180:   private void advance()
1181:   {
1182:     ++index;
1183:     if (index >= length)
1184:       {
1185:         // FIXME: what exception here?
1186:         throw new IllegalArgumentException();
1187:       }
1188:   }
1189: 
1190:   /**
1191:    * Parse an integer appearing in the format string.  Will return -1
1192:    * if no integer was found.
1193:    *
1194:    * @return the parsed integer.
1195:    */
1196:   private int parseInt()
1197:   {
1198:     int start = index;
1199:     while (Character.isDigit(format.charAt(index)))
1200:       advance();
1201:     if (start == index)
1202:       return -1;
1203:     return Integer.parseInt(format.substring(start, index));
1204:   }
1205: 
1206:   /**
1207:    * Parse the argument index.  Returns -1 if there was no index, 0 if
1208:    * we should re-use the previous index, and a positive integer to
1209:    * indicate an absolute index.
1210:    *
1211:    * @return the parsed argument index.
1212:    */
1213:   private int parseArgumentIndex()
1214:   {
1215:     int result = -1;
1216:     int start = index;
1217:     if (format.charAt(index) == '<')
1218:       {
1219:         result = 0;
1220:         advance();
1221:       }
1222:     else if (Character.isDigit(format.charAt(index)))
1223:       {
1224:         result = parseInt();
1225:         if (format.charAt(index) == '$')
1226:           advance();
1227:         else
1228:           {
1229:             // Reset.
1230:             index = start;
1231:             result = -1;
1232:           }
1233:       }
1234:     return result;
1235:   }
1236: 
1237:   /**
1238:    * Parse a set of flags and return a bit mask of values from
1239:    * FormattableFlags.  Will throw an exception if a flag is
1240:    * duplicated.
1241:    *
1242:    * @return the parsed flags.
1243:    */
1244:   private int parseFlags()
1245:   {
1246:     int value = 0;
1247:     int start = index;
1248:     while (true)
1249:       {
1250:         int x = FLAGS.indexOf(format.charAt(index));
1251:         if (x == -1)
1252:           break;
1253:         int newValue = 1 << x;
1254:         if ((value & newValue) != 0)
1255:           throw new DuplicateFormatFlagsException(format.substring(start,
1256:                                                                    index + 1));
1257:         value |= newValue;
1258:         advance();
1259:       }
1260:     return value;
1261:   }
1262: 
1263:   /**
1264:    * Parse the width part of a format string.  Returns -1 if no width
1265:    * was specified.
1266:    *
1267:    * @return the parsed width.
1268:    */
1269:   private int parseWidth()
1270:   {
1271:     return parseInt();
1272:   }
1273: 
1274:   /**
1275:    * If the current character is '.', parses the precision part of a
1276:    * format string.  Returns -1 if no precision was specified.
1277:    *
1278:    * @return the parsed precision.
1279:    */
1280:   private int parsePrecision()
1281:   {
1282:     if (format.charAt(index) != '.')
1283:       return -1;
1284:     advance();
1285:     int precision = parseInt();
1286:     if (precision == -1)
1287:       // FIXME
1288:       throw new IllegalArgumentException();
1289:     return precision;
1290:   }
1291: 
1292:   /**
1293:    * Outputs a formatted string based on the supplied specification,
1294:    * <code>fmt</code>, and its arguments using the specified locale.
1295:    * The locale of the formatter does not change as a result; the
1296:    * specified locale is just used for this particular formatting
1297:    * operation.  If the locale is <code>null</code>, then no
1298:    * localization is applied.
1299:    *
1300:    * @param loc the locale to use for this format.
1301:    * @param fmt the format specification.
1302:    * @param args the arguments to apply to the specification.
1303:    * @throws IllegalFormatException if there is a problem with
1304:    *                                the syntax of the format
1305:    *                                specification or a mismatch
1306:    *                                between it and the arguments.
1307:    * @throws FormatterClosedException if the formatter is closed.
1308:    */
1309:   public Formatter format(Locale loc, String fmt, Object... args)
1310:   {
1311:     if (closed)
1312:       throw new FormatterClosedException();
1313: 
1314:     // Note the arguments are indexed starting at 1.
1315:     int implicitArgumentIndex = 1;
1316:     int previousArgumentIndex = 0;
1317: 
1318:     try
1319:       {
1320:         fmtLocale = loc;
1321:         format = fmt;
1322:         length = format.length();
1323:         for (index = 0; index < length; ++index)
1324:           {
1325:             char c = format.charAt(index);
1326:             if (c != '%')
1327:               {
1328:                 out.append(c);
1329:                 continue;
1330:               }
1331: 
1332:             int start = index;
1333:             advance();
1334: 
1335:             // We do the needed post-processing of this later, when we
1336:             // determine whether an argument is actually needed by
1337:             // this conversion.
1338:             int argumentIndex = parseArgumentIndex();
1339: 
1340:             int flags = parseFlags();
1341:             int width = parseWidth();
1342:             int precision = parsePrecision();
1343:             char origConversion = format.charAt(index);
1344:             char conversion = origConversion;
1345:             if (Character.isUpperCase(conversion))
1346:               {
1347:                 flags |= FormattableFlags.UPPERCASE;
1348:                 conversion = Character.toLowerCase(conversion);
1349:               }
1350: 
1351:             Object argument = null;
1352:             if (conversion == '%' || conversion == 'n')
1353:               {
1354:                 if (argumentIndex != -1)
1355:                   {
1356:                     // FIXME: not sure about this.
1357:                     throw new UnknownFormatConversionException("FIXME");
1358:                   }
1359:               }
1360:             else
1361:               {
1362:                 if (argumentIndex == -1)
1363:                   argumentIndex = implicitArgumentIndex++;
1364:                 else if (argumentIndex == 0)
1365:                   argumentIndex = previousArgumentIndex;
1366:                 // Argument indices start at 1 but array indices at 0.
1367:                 --argumentIndex;
1368:                 if (args != null)
1369:                   {
1370:                     if (argumentIndex < 0 || argumentIndex >= args.length)
1371:                       throw new MissingFormatArgumentException(format.substring(start, index));
1372:                     argument = args[argumentIndex];
1373:                   }
1374:               }
1375: 
1376:             switch (conversion)
1377:               {
1378:               case 'b':
1379:                 booleanFormat(argument, flags, width, precision,
1380:                               origConversion);
1381:                 break;
1382:               case 'h':
1383:                 hashCodeFormat(argument, flags, width, precision,
1384:                                origConversion);
1385:                 break;
1386:               case 's':
1387:                 stringFormat(argument, flags, width, precision,
1388:                              origConversion);
1389:                 break;
1390:               case 'c':
1391:                 characterFormat(argument, flags, width, precision,
1392:                                 origConversion);
1393:                 break;
1394:               case 'd':
1395:                 checkFlags(flags & FormattableFlags.UPPERCASE, 0, 'd');
1396:                 decimalConversion(argument, flags, width, precision,
1397:                                   origConversion);
1398:                 break;
1399:               case 'o':
1400:                 checkFlags(flags & FormattableFlags.UPPERCASE, 0, 'o');
1401:                 hexOrOctalConversion(argument, flags, width, precision, 8,
1402:                                      origConversion);
1403:                 break;
1404:               case 'x':
1405:                 hexOrOctalConversion(argument, flags, width, precision, 16,
1406:                                      origConversion);
1407:               case 'e':
1408:                 // scientificNotationConversion();
1409:                 break;
1410:               case 'f':
1411:                 // floatingDecimalConversion();
1412:                 break;
1413:               case 'g':
1414:                 // smartFloatingConversion();
1415:                 break;
1416:               case 'a':
1417:                 // hexFloatingConversion();
1418:                 break;
1419:               case 't':
1420:                 advance();
1421:                 char subConversion = format.charAt(index);
1422:                 dateTimeConversion(argument, flags, width, precision,
1423:                                    origConversion, subConversion);
1424:                 break;
1425:               case '%':
1426:                 percentFormat(flags, width, precision);
1427:                 break;
1428:               case 'n':
1429:                 newLineFormat(flags, width, precision);
1430:                 break;
1431:               default:
1432:                 throw new UnknownFormatConversionException(String.valueOf(origConversion));
1433:               }
1434:           }
1435:       }
1436:     catch (IOException exc)
1437:       {
1438:         ioException = exc;
1439:       }
1440:     return this;
1441:   }
1442: 
1443:   /**
1444:    * Outputs a formatted string based on the supplied specification,
1445:    * <code>fmt</code>, and its arguments using the formatter's locale.
1446:    *
1447:    * @param format the format specification.
1448:    * @param args the arguments to apply to the specification.
1449:    * @throws IllegalFormatException if there is a problem with
1450:    *                                the syntax of the format
1451:    *                                specification or a mismatch
1452:    *                                between it and the arguments.
1453:    * @throws FormatterClosedException if the formatter is closed.
1454:    */
1455:   public Formatter format(String format, Object... args)
1456:   {
1457:     return format(locale, format, args);
1458:   }
1459: 
1460:   /**
1461:    * Returns the last I/O exception thrown by the
1462:    * <code>append()</code> operation of the underlying
1463:    * output stream.
1464:    *
1465:    * @return the last I/O exception.
1466:    */
1467:   public IOException ioException()
1468:   {
1469:     return ioException;
1470:   }
1471: 
1472:   /**
1473:    * Returns the locale used by this formatter.
1474:    *
1475:    * @return the formatter's locale.
1476:    * @throws FormatterClosedException if the formatter is closed.
1477:    */
1478:   public Locale locale()
1479:   {
1480:     if (closed)
1481:       throw new FormatterClosedException();
1482:     return locale;
1483:   }
1484: 
1485:   /**
1486:    * Returns the output stream used by this formatter.
1487:    *
1488:    * @return the formatter's output stream.
1489:    * @throws FormatterClosedException if the formatter is closed.
1490:    */
1491:   public Appendable out()
1492:   {
1493:     if (closed)
1494:       throw new FormatterClosedException();
1495:     return out;
1496:   }
1497: 
1498:   /**
1499:    * Returns the result of applying {@link Object#toString()}
1500:    * to the underlying output stream.  The results returned
1501:    * depend on the particular {@link Appendable} being used.
1502:    * For example, a {@link StringBuilder} will return the
1503:    * formatted output but an I/O stream will not.
1504:    *
1505:    * @throws FormatterClosedException if the formatter is closed.
1506:    */
1507:   public String toString()
1508:   {
1509:     if (closed)
1510:       throw new FormatterClosedException();
1511:     return out.toString();
1512:   }
1513: }