Formatter.java 46 KB

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