1. /*
  2. * @(#)MessageFormat.java 1.38 00/01/19
  3. *
  4. * Copyright 1996-2000 Sun Microsystems, Inc. All Rights Reserved.
  5. *
  6. * This software is the proprietary information of Sun Microsystems, Inc.
  7. * Use is subject to license terms.
  8. *
  9. */
  10. /*
  11. * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
  12. * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved
  13. *
  14. * The original version of this source code and documentation is copyrighted
  15. * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
  16. * materials are provided under terms of a License Agreement between Taligent
  17. * and Sun. This technology is protected by multiple US and International
  18. * patents. This notice and attribution to Taligent may not be removed.
  19. * Taligent is a registered trademark of Taligent, Inc.
  20. *
  21. */
  22. package java.text;
  23. import java.util.Date;
  24. import java.util.Locale;
  25. import java.text.DecimalFormat;
  26. import java.text.Utility;
  27. import java.io.ObjectInputStream;
  28. import java.io.IOException;
  29. import java.io.InvalidObjectException;
  30. /**
  31. * <code>MessageFormat</code> provides a means to produce concatenated
  32. * messages in language-neutral way. Use this to construct messages
  33. * displayed for end users.
  34. *
  35. * <p>
  36. * <code>MessageFormat</code> takes a set of objects, formats them, then
  37. * inserts the formatted strings into the pattern at the appropriate places.
  38. *
  39. * <p>
  40. * <strong>Note:</strong>
  41. * <code>MessageFormat</code> differs from the other <code>Format</code>
  42. * classes in that you create a <code>MessageFormat</code> object with one
  43. * of its constructors (not with a <code>getInstance</code> style factory
  44. * method). The factory methods aren't necessary because <code>MessageFormat</code>
  45. * doesn't require any complex setup for a given locale. In fact,
  46. * <code>MessageFormat</code> doesn't implement any locale specific behavior
  47. * at all. It just needs to be set up on a sentence by sentence basis.
  48. *
  49. * <p>
  50. * Here are some examples of usage:
  51. * <blockquote>
  52. * <pre>
  53. * Object[] arguments = {
  54. * new Integer(7),
  55. * new Date(System.currentTimeMillis()),
  56. * "a disturbance in the Force"
  57. * };
  58. *
  59. * String result = MessageFormat.format(
  60. * "At {1,time} on {1,date}, there was {2} on planet {0,number,integer}.",
  61. * arguments);
  62. *
  63. * <em>output</em>: At 12:30 PM on Jul 3, 2053, there was a disturbance
  64. * in the Force on planet 7.
  65. *
  66. * </pre>
  67. * </blockquote>
  68. * Typically, the message format will come from resources, and the
  69. * arguments will be dynamically set at runtime.
  70. *
  71. * <p>
  72. * Example 2:
  73. * <blockquote>
  74. * <pre>
  75. * Object[] testArgs = {new Long(3), "MyDisk"};
  76. *
  77. * MessageFormat form = new MessageFormat(
  78. * "The disk \"{1}\" contains {0} file(s).");
  79. *
  80. * System.out.println(form.format(testArgs));
  81. *
  82. * // output, with different testArgs
  83. * <em>output</em>: The disk "MyDisk" contains 0 file(s).
  84. * <em>output</em>: The disk "MyDisk" contains 1 file(s).
  85. * <em>output</em>: The disk "MyDisk" contains 1,273 file(s).
  86. * </pre>
  87. * </blockquote>
  88. *
  89. * <p>
  90. * The pattern is of the form:
  91. * <blockquote>
  92. * <pre>
  93. * messageFormatPattern := string ( "{" messageFormatElement "}" string )*
  94. *
  95. * messageFormatElement := argument { "," elementFormat }
  96. *
  97. * elementFormat := "time" { "," datetimeStyle }
  98. * | "date" { "," datetimeStyle }
  99. * | "number" { "," numberStyle }
  100. * | "choice" { "," choiceStyle }
  101. *
  102. * datetimeStyle := "short"
  103. * | "medium"
  104. * | "long"
  105. * | "full"
  106. * | dateFormatPattern
  107. *
  108. * numberStyle := "currency"
  109. * | "percent"
  110. * | "integer"
  111. * | numberFormatPattern
  112. *
  113. * choiceStyle := choiceFormatPattern
  114. * </pre>
  115. * </blockquote>
  116. * If there is no <code>elementFormat</code>,
  117. * then the argument must be a string, which is substituted. If there is
  118. * no <code>dateTimeStyle</code> or <code>numberStyle</code>, then the
  119. * default format is used (for example, <code>NumberFormat.getInstance</code>,
  120. * <code>DateFormat.getTimeInstance</code>, or <code>DateFormat.getInstance</code>).
  121. *
  122. * <p>
  123. * In strings, single quotes can be used to quote the "{"
  124. * (curly brace) if necessary. A real single quote is represented by ''.
  125. * Inside a <code>messageFormatElement</code>, quotes are <strong>not</strong>
  126. * removed. For example, {1,number,$'#',##} will produce a number format
  127. * with the pound-sign quoted, with a result such as: "$#31,45".
  128. *
  129. * <p>
  130. * If a pattern is used, then unquoted braces in the pattern, if any, must match:
  131. * that is, "ab {0} de" and "ab '}' de" are ok, but "ab {0'}' de" and "ab } de" are
  132. * not.
  133. *
  134. * <p>
  135. * The argument is a number from 0 to 9, which corresponds to the
  136. * arguments presented in an array to be formatted.
  137. *
  138. * <p>
  139. * It is ok to have unused arguments in the array.
  140. * With missing arguments or arguments that are not of the right class for
  141. * the specified format, a <code>ParseException</code> is thrown.
  142. * First, <code>format</code> checks to see if a <code>Format</code> object has been
  143. * specified for the argument with the <code>setFormats</code> method.
  144. * If so, then <code>format</code> uses that <code>Format</code> object to format the
  145. * argument. Otherwise, the argument is formatted based on the object's
  146. * type. If the argument is a <code>Number</code>, then <code>format</code>
  147. * uses <code>NumberFormat.getInstance</code> to format the argument; if the
  148. * argument is a <code>Date</code>, then <code>format</code> uses
  149. * <code>DateFormat.getDateTimeInstance</code> to format the argument.
  150. * Otherwise, it uses the <code>toString</code> method.
  151. *
  152. * <p>
  153. * For more sophisticated patterns, you can use a <code>ChoiceFormat</code> to get
  154. * output such as:
  155. * <blockquote>
  156. * <pre>
  157. * MessageFormat form = new MessageFormat("The disk \"{1}\" contains {0}.");
  158. * double[] filelimits = {0,1,2};
  159. * String[] filepart = {"no files","one file","{0,number} files"};
  160. * ChoiceFormat fileform = new ChoiceFormat(filelimits, filepart);
  161. * form.setFormat(1,fileform); // NOT zero, see below
  162. *
  163. * Object[] testArgs = {new Long(12373), "MyDisk"};
  164. *
  165. * System.out.println(form.format(testArgs));
  166. *
  167. * // output, with different testArgs
  168. * output: The disk "MyDisk" contains no files.
  169. * output: The disk "MyDisk" contains one file.
  170. * output: The disk "MyDisk" contains 1,273 files.
  171. * </pre>
  172. * </blockquote>
  173. * You can either do this programmatically, as in the above example,
  174. * or by using a pattern (see
  175. * {@link ChoiceFormat}
  176. * for more information) as in:
  177. * <blockquote>
  178. * <pre>
  179. * form.applyPattern(
  180. * "There {0,choice,0#are no files|1#is one file|1#are {0,number,integer} files}.");
  181. * </pre>
  182. * </blockquote>
  183. * <p>
  184. * <strong>Note:</strong> As we see above, the string produced
  185. * by a <code>ChoiceFormat</code> in <code>MessageFormat</code> is treated specially;
  186. * occurances of '{' are used to indicated subformats, and cause recursion.
  187. * If you create both a <code>MessageFormat</code> and <code>ChoiceFormat</code>
  188. * programmatically (instead of using the string patterns), then be careful not to
  189. * produce a format that recurses on itself, which will cause an infinite loop.
  190. * <p>
  191. * <strong>Note:</strong> formats are numbered by order of
  192. * variable in the string.
  193. * This is <strong>not</strong> the same as the argument numbering!
  194. * For example: with "abc{2}def{3}ghi{0}...",
  195. * <ul>
  196. * <li>format0 affects the first variable {2}
  197. * <li>format1 affects the second variable {3}
  198. * <li>format2 affects the second variable {0}
  199. * <li>and so on.
  200. * </ul>
  201. * <p>
  202. * When a single argument is parsed more than once in the string, the last match
  203. * will be the final result of the parsing. For example,
  204. * <pre>
  205. * MessageFormat mf = new MessageFormat("{0,number,#.##}, {0,number,#.#}");
  206. * Object[] objs = {new Double(3.1415)};
  207. * String result = mf.format( objs );
  208. * // result now equals "3.14, 3.1"
  209. * objs = null;
  210. * objs = mf.parse(result, new ParsePosition(0));
  211. * // objs now equals {new Double(3.1)}
  212. * </pre>
  213. * <p>
  214. * Likewise, parsing with a MessageFormat object using patterns containing
  215. * multiple occurances of the same argument would return the last match. For
  216. * example,
  217. * <pre>
  218. * MessageFormat mf = new MessageFormat("{0}, {0}, {0}");
  219. * String forParsing = "x, y, z";
  220. * Object[] objs = mf.parse(forParsing, new ParsePosition(0));
  221. * // result now equals {new String("z")}
  222. * </pre>
  223. * <p>
  224. * You can use <code>setLocale</code> followed by <code>applyPattern</code>
  225. * (and then possibly <code>setFormat</code>) to re-initialize a
  226. * <code>MessageFormat</code> with a different locale.
  227. *
  228. * @see java.util.Locale
  229. * @see Format
  230. * @see NumberFormat
  231. * @see DecimalFormat
  232. * @see ChoiceFormat
  233. * @version 1.38, 01/19/00
  234. * @author Mark Davis
  235. */
  236. public class MessageFormat extends Format {
  237. /**
  238. * Constructs with the specified pattern.
  239. * @see MessageFormat#applyPattern
  240. */
  241. public MessageFormat(String pattern) {
  242. applyPattern(pattern);
  243. }
  244. /**
  245. * Constructs with the specified pattern and formats for the
  246. * arguments in that pattern.
  247. */
  248. public void setLocale(Locale theLocale) {
  249. locale = theLocale;
  250. }
  251. /**
  252. * Gets the locale. This locale is used for fetching default number or date
  253. * format information.
  254. */
  255. public Locale getLocale() {
  256. return locale;
  257. }
  258. /**
  259. * Sets the pattern. See the class description.
  260. */
  261. public void applyPattern(String newPattern) {
  262. StringBuffer[] segments = new StringBuffer[4];
  263. for (int i = 0; i < segments.length; ++i) {
  264. segments[i] = new StringBuffer();
  265. }
  266. int part = 0;
  267. int formatNumber = 0;
  268. boolean inQuote = false;
  269. int braceStack = 0;
  270. maxOffset = -1;
  271. for (int i = 0; i < newPattern.length(); ++i) {
  272. char ch = newPattern.charAt(i);
  273. if (part == 0) {
  274. if (ch == '\'') {
  275. if (i + 1 < newPattern.length()
  276. && newPattern.charAt(i+1) == '\'') {
  277. segments[part].append(ch); // handle doubles
  278. ++i;
  279. } else {
  280. inQuote = !inQuote;
  281. }
  282. } else if (ch == '{' && !inQuote) {
  283. part = 1;
  284. } else {
  285. segments[part].append(ch);
  286. }
  287. } else if (inQuote) { // just copy quotes in parts
  288. segments[part].append(ch);
  289. if (ch == '\'') {
  290. inQuote = false;
  291. }
  292. } else {
  293. switch (ch) {
  294. case ',':
  295. if (part < 3)
  296. part += 1;
  297. else
  298. segments[part].append(ch);
  299. break;
  300. case '{':
  301. ++braceStack;
  302. segments[part].append(ch);
  303. break;
  304. case '}':
  305. if (braceStack == 0) {
  306. part = 0;
  307. makeFormat(i, formatNumber, segments);
  308. formatNumber++;
  309. } else {
  310. --braceStack;
  311. segments[part].append(ch);
  312. }
  313. break;
  314. case '\'':
  315. inQuote = true;
  316. // fall through, so we keep quotes in other parts
  317. default:
  318. segments[part].append(ch);
  319. break;
  320. }
  321. }
  322. }
  323. if (braceStack == 0 && part != 0) {
  324. maxOffset = -1;
  325. throw new IllegalArgumentException("Unmatched braces in the pattern.");
  326. }
  327. pattern = segments[0].toString();
  328. }
  329. /**
  330. * Gets the pattern. See the class description.
  331. */
  332. public String toPattern() {
  333. // later, make this more extensible
  334. int lastOffset = 0;
  335. StringBuffer result = new StringBuffer();
  336. for (int i = 0; i <= maxOffset; ++i) {
  337. copyAndFixQuotes(pattern, lastOffset, offsets[i],result);
  338. lastOffset = offsets[i];
  339. result.append('{');
  340. result.append(argumentNumbers[i]);
  341. if (formats[i] == null) {
  342. // do nothing, string format
  343. } else if (formats[i] instanceof DecimalFormat) {
  344. if (formats[i].equals(NumberFormat.getInstance(locale))) {
  345. result.append(",number");
  346. } else if (formats[i].equals(
  347. NumberFormat.getCurrencyInstance(locale))) {
  348. result.append(",number,currency");
  349. } else if (formats[i].equals(
  350. NumberFormat.getPercentInstance(locale))) {
  351. result.append(",number,percent");
  352. } else if (formats[i].equals(getIntegerFormat(locale))) {
  353. result.append(",number,integer");
  354. } else {
  355. result.append(",number," +
  356. ((DecimalFormat)formats[i]).toPattern());
  357. }
  358. } else if (formats[i] instanceof SimpleDateFormat) {
  359. if (formats[i].equals(DateFormat.getDateInstance(
  360. DateFormat.DEFAULT,locale))) {
  361. result.append(",date");
  362. } else if (formats[i].equals(DateFormat.getDateInstance(
  363. DateFormat.SHORT,locale))) {
  364. result.append(",date,short");
  365. } else if (formats[i].equals(DateFormat.getDateInstance(
  366. DateFormat.DEFAULT,locale))) {
  367. result.append(",date,medium");
  368. } else if (formats[i].equals(DateFormat.getDateInstance(
  369. DateFormat.LONG,locale))) {
  370. result.append(",date,long");
  371. } else if (formats[i].equals(DateFormat.getDateInstance(
  372. DateFormat.FULL,locale))) {
  373. result.append(",date,full");
  374. } else if (formats[i].equals(DateFormat.getTimeInstance(
  375. DateFormat.DEFAULT,locale))) {
  376. result.append(",time");
  377. } else if (formats[i].equals(DateFormat.getTimeInstance(
  378. DateFormat.SHORT,locale))) {
  379. result.append(",time,short");
  380. } else if (formats[i].equals(DateFormat.getTimeInstance(
  381. DateFormat.DEFAULT,locale))) {
  382. result.append(",time,medium");
  383. } else if (formats[i].equals(DateFormat.getTimeInstance(
  384. DateFormat.LONG,locale))) {
  385. result.append(",time,long");
  386. } else if (formats[i].equals(DateFormat.getTimeInstance(
  387. DateFormat.FULL,locale))) {
  388. result.append(",time,full");
  389. } else {
  390. result.append(",date,"
  391. + ((SimpleDateFormat)formats[i]).toPattern());
  392. }
  393. } else if (formats[i] instanceof ChoiceFormat) {
  394. result.append(",choice,"
  395. + ((ChoiceFormat)formats[i]).toPattern());
  396. } else {
  397. //result.append(", unknown");
  398. }
  399. result.append('}');
  400. }
  401. copyAndFixQuotes(pattern, lastOffset, pattern.length(), result);
  402. return result.toString();
  403. }
  404. /**
  405. * Sets formats to use on parameters.
  406. * See the class description about format numbering.
  407. */
  408. public void setFormats(Format[] newFormats) {
  409. try {
  410. formats = (Format[]) newFormats.clone();
  411. } catch (Exception e) {
  412. return; // should never occur!
  413. }
  414. }
  415. /**
  416. * Set a format to be used on a variable in the pattern.
  417. * @param variable the zero-based number of the variable in the format.
  418. * This is <em>not</em> the argument number. If <code>variable</code>
  419. * is out of range, an <code>ArrayIndexOutOfBoundsException</code> is
  420. * thrown.
  421. * @param newFormat the format to use for the specified variable
  422. */
  423. public void setFormat(int variable, Format newFormat) {
  424. formats[variable] = newFormat;
  425. }
  426. /**
  427. * Gets formats that were set with setFormats.
  428. * See the class description about format numbering.
  429. */
  430. public Format[] getFormats() {
  431. try {
  432. return (Format[]) formats.clone();
  433. } catch (Exception e) {
  434. return formats; // should never occur!
  435. }
  436. }
  437. /**
  438. * Returns pattern with formatted objects. If source is null, the
  439. * original pattern is returned, if source contains null objects, the
  440. * formatted result will substitute each argument with the string "null".
  441. * @param source an array of objects to be formatted & substituted.
  442. * @param result where text is appended.
  443. * @param ignore no useful status is returned.
  444. */
  445. public final StringBuffer format(Object[] source, StringBuffer result,
  446. FieldPosition ignore)
  447. {
  448. return format(source,result,ignore, 0);
  449. }
  450. /**
  451. * Convenience routine.
  452. * Avoids explicit creation of MessageFormat,
  453. * but doesn't allow future optimizations.
  454. */
  455. public static String format(String pattern, Object[] arguments) {
  456. MessageFormat temp = new MessageFormat(pattern);
  457. return temp.format(arguments);
  458. }
  459. // Overrides
  460. /**
  461. * Returns pattern with formatted objects. If source is null, the
  462. * original pattern is returned, if source contains null objects, the
  463. * formatted result will substitute each argument with the string "null".
  464. * @param source an array of objects to be formatted & substituted.
  465. * @param result where text is appended.
  466. * @param ignore no useful status is returned.
  467. */
  468. public final StringBuffer format(Object source, StringBuffer result,
  469. FieldPosition ignore)
  470. {
  471. return format((Object[])source, result,ignore, 0);
  472. }
  473. /**
  474. * Parses the string.
  475. *
  476. * <p>Caveats: The parse may fail in a number of circumstances.
  477. * For example:
  478. * <ul>
  479. * <li>If one of the arguments does not occur in the pattern.
  480. * <li>If the format of an argument loses information, such as
  481. * with a choice format where a large number formats to "many".
  482. * <li>Does not yet handle recursion (where
  483. * the substituted strings contain {n} references.)
  484. * <li>Will not always find a match (or the correct match)
  485. * if some part of the parse is ambiguous.
  486. * For example, if the pattern "{1},{2}" is used with the
  487. * string arguments {"a,b", "c"}, it will format as "a,b,c".
  488. * When the result is parsed, it will return {"a", "b,c"}.
  489. * <li>If a single argument is parsed more than once in the string,
  490. * then the later parse wins.
  491. * </ul>
  492. * When the parse fails, use ParsePosition.getErrorIndex() to find out
  493. * where in the string did the parsing failed. The returned error
  494. * index is the starting offset of the sub-patterns that the string
  495. * is comparing with. For example, if the parsing string "AAA {0} BBB"
  496. * is comparing against the pattern "AAD {0} BBB", the error index is
  497. * 0. When an error occurs, the call to this method will return null.
  498. * If the soruce is null, return an empty array.
  499. */
  500. public Object[] parse(String source, ParsePosition status) {
  501. Object[] empty = {};
  502. if (source == null) return empty;
  503. Object[] resultArray = new Object[10];
  504. int patternOffset = 0;
  505. int sourceOffset = status.index;
  506. ParsePosition tempStatus = new ParsePosition(0);
  507. for (int i = 0; i <= maxOffset; ++i) {
  508. // match up to format
  509. int len = offsets[i] - patternOffset;
  510. if (len == 0 || pattern.regionMatches(patternOffset,
  511. source, sourceOffset, len)) {
  512. sourceOffset += len;
  513. patternOffset += len;
  514. } else {
  515. status.errorIndex = sourceOffset;
  516. return null; // leave index as is to signal error
  517. }
  518. // now use format
  519. if (formats[i] == null) { // string format
  520. // if at end, use longest possible match
  521. // otherwise uses first match to intervening string
  522. // does NOT recursively try all possibilities
  523. int tempLength = (i != maxOffset) ? offsets[i+1] : pattern.length();
  524. int next;
  525. if (patternOffset >= tempLength) {
  526. next = source.length();
  527. }else{
  528. next = source.indexOf( pattern.substring(patternOffset,tempLength), sourceOffset);
  529. }
  530. if (next < 0) {
  531. status.errorIndex = sourceOffset;
  532. return null; // leave index as is to signal error
  533. } else {
  534. String strValue= source.substring(sourceOffset,next);
  535. if (!strValue.equals("{"+argumentNumbers[i]+"}"))
  536. resultArray[argumentNumbers[i]]
  537. = source.substring(sourceOffset,next);
  538. sourceOffset = next;
  539. }
  540. } else {
  541. tempStatus.index = sourceOffset;
  542. resultArray[argumentNumbers[i]]
  543. = formats[i].parseObject(source,tempStatus);
  544. if (tempStatus.index == sourceOffset) {
  545. status.errorIndex = sourceOffset;
  546. return null; // leave index as is to signal error
  547. }
  548. sourceOffset = tempStatus.index; // update
  549. }
  550. }
  551. int len = pattern.length() - patternOffset;
  552. if (len == 0 || pattern.regionMatches(patternOffset,
  553. source, sourceOffset, len)) {
  554. status.index = sourceOffset + len;
  555. } else {
  556. status.errorIndex = sourceOffset;
  557. return null; // leave index as is to signal error
  558. }
  559. return resultArray;
  560. }
  561. /**
  562. * Parses the string. Does not yet handle recursion (where
  563. * the substituted strings contain {n} references.)
  564. * @exception ParseException if the string can't be parsed.
  565. */
  566. public Object[] parse(String source) throws ParseException {
  567. ParsePosition status = new ParsePosition(0);
  568. Object[] result = parse(source, status);
  569. if (status.index == 0) // unchanged, returned object is null
  570. throw new ParseException("MessageFormat parse error!", status.errorIndex);
  571. return result;
  572. }
  573. /**
  574. * Parses the string. Does not yet handle recursion (where
  575. * the substituted strings contain %n references.)
  576. */
  577. public Object parseObject (String text, ParsePosition status) {
  578. return parse(text, status);
  579. }
  580. /**
  581. * Overrides Cloneable
  582. */
  583. public Object clone()
  584. {
  585. MessageFormat other = (MessageFormat) super.clone();
  586. // clone arrays. Can't do with utility because of bug in Cloneable
  587. other.formats = (Format[]) formats.clone(); // shallow clone
  588. for (int i = 0; i < formats.length; ++i) {
  589. if (formats[i] != null)
  590. other.formats[i] = (Format)formats[i].clone();
  591. }
  592. // for primitives or immutables, shallow clone is enough
  593. other.offsets = (int[]) offsets.clone();
  594. other.argumentNumbers = (int[]) argumentNumbers.clone();
  595. return other;
  596. }
  597. /**
  598. * Equality comparision between two message format objects
  599. */
  600. public boolean equals(Object obj) {
  601. if (this == obj) // quick check
  602. return true;
  603. if (obj == null || getClass() != obj.getClass())
  604. return false;
  605. MessageFormat other = (MessageFormat) obj;
  606. return (maxOffset == other.maxOffset
  607. && pattern.equals(other.pattern)
  608. && Utility.objectEquals(locale, other.locale) // does null check
  609. && Utility.arrayEquals(offsets,other.offsets)
  610. && Utility.arrayEquals(argumentNumbers,other.argumentNumbers)
  611. && Utility.arrayEquals(formats,other.formats));
  612. }
  613. /**
  614. * Generates a hash code for the message format object.
  615. */
  616. public int hashCode() {
  617. return pattern.hashCode(); // enough for reasonable distribution
  618. }
  619. // ===========================privates============================
  620. /**
  621. * The locale to use for formatting numbers and dates.
  622. * @serial
  623. */
  624. private Locale locale = Locale.getDefault();
  625. /**
  626. * The string that the formatted values are to be plugged into. In other words, this
  627. * is the pattern supplied on construction with all of the {} expressions taken out.
  628. * @serial
  629. */
  630. private String pattern = "";
  631. /** The maximum number of arguments in the format */
  632. private static final int MAX_ARGUMENTS = 10;
  633. /**
  634. * An array of ten formatters, which are used to format the first ten arguments.
  635. * @serial
  636. */
  637. // later, allow more than ten items
  638. private Format[] formats = new Format[MAX_ARGUMENTS];
  639. /**
  640. * The positions where the results of formatting each argument are to be inserted
  641. * into the pattern.
  642. * @serial
  643. */
  644. private int[] offsets = new int[MAX_ARGUMENTS];
  645. /**
  646. * The argument numbers corresponding to each formatter. (The formatters are stored
  647. * in the order they occur in the pattern, not in the order in which the arguments
  648. * are specified.)
  649. * @serial
  650. */
  651. private int[] argumentNumbers = new int[MAX_ARGUMENTS];
  652. /**
  653. * One less than the number of entries in <code>offsets</code>. Can also be thought of
  654. * as the index of the highest-numbered element in <code>offsets</code> that is being used.
  655. * All of these arrays should have the same number of elements being used as <code>offsets</code>
  656. * does, and so this variable suffices to tell us how many entries are in all of them.
  657. * @serial
  658. */
  659. private int maxOffset = -1;
  660. /**
  661. * Constructs with the specified pattern.
  662. * @see MessageFormat#applyPattern
  663. */
  664. private MessageFormat(String pattern, Locale loc) {
  665. locale = (Locale)loc.clone();
  666. applyPattern(pattern);
  667. }
  668. /**
  669. * Internal routine used by format.
  670. * @param recursionProtection Initially zero. Bits 0..9 are used to indicate
  671. * that a parameter has already been seen, to avoid recursion. Currently
  672. * unused.
  673. */
  674. private StringBuffer format(Object[] arguments, StringBuffer result,
  675. FieldPosition status, int recursionProtection) {
  676. // note: this implementation assumes a fast substring & index.
  677. // if this is not true, would be better to append chars one by one.
  678. int lastOffset = 0;
  679. for (int i = 0; i <= maxOffset; ++i) {
  680. result.append(pattern.substring(lastOffset, offsets[i]));
  681. lastOffset = offsets[i];
  682. int argumentNumber = argumentNumbers[i];
  683. if (arguments == null || argumentNumber >= arguments.length) {
  684. result.append("{" + argumentNumber + "}");
  685. continue;
  686. }
  687. // int argRecursion = ((recursionProtection >> (argumentNumber*2)) & 0x3);
  688. if (false) { // if (argRecursion == 3){
  689. // prevent loop!!!
  690. result.append('\uFFFD');
  691. } else {
  692. Object obj = arguments[argumentNumber];
  693. String arg;
  694. boolean tryRecursion = false;
  695. if (obj == null) {
  696. arg = "null";
  697. } else if (formats[i] != null) {
  698. arg = formats[i].format(obj);
  699. tryRecursion = formats[i] instanceof ChoiceFormat;
  700. } else if (obj instanceof Number) {
  701. // format number if can
  702. arg = NumberFormat.getInstance(locale).format(obj); // fix
  703. } else if (obj instanceof Date) {
  704. // format a Date if can
  705. arg = DateFormat.getDateTimeInstance(DateFormat.SHORT,
  706. DateFormat.SHORT,
  707. locale).format(obj);//fix
  708. } else if (obj instanceof String) {
  709. arg = (String) obj;
  710. } else {
  711. arg = obj.toString();
  712. if (arg == null) arg = "null";
  713. }
  714. // recurse if necessary
  715. if (tryRecursion && arg.indexOf('{') >= 0) {
  716. MessageFormat temp = new MessageFormat(arg, locale);
  717. temp.format(arguments,result,status,recursionProtection);
  718. } else {
  719. result.append(arg);
  720. }
  721. }
  722. }
  723. result.append(pattern.substring(lastOffset, pattern.length()));
  724. return result;
  725. }
  726. private static final String[] typeList =
  727. {"", "", "number", "", "date", "", "time", "", "choice"};
  728. private static final String[] modifierList =
  729. {"", "", "currency", "", "percent", "", "integer"};
  730. private static final String[] dateModifierList =
  731. {"", "", "short", "", "medium", "", "long", "", "full"};
  732. private void makeFormat(int position, int offsetNumber,
  733. StringBuffer[] segments)
  734. {
  735. // get the number
  736. int argumentNumber;
  737. int oldMaxOffset = maxOffset;
  738. try {
  739. argumentNumber = Integer.parseInt(segments[1].toString()); // always unlocalized!
  740. if (argumentNumber < 0 || argumentNumber > 9) {
  741. throw new NumberFormatException();
  742. }
  743. maxOffset = offsetNumber;
  744. offsets[offsetNumber] = segments[0].length();
  745. argumentNumbers[offsetNumber] = argumentNumber;
  746. } catch (Exception e) {
  747. throw new IllegalArgumentException("argument number too large at ");
  748. }
  749. // now get the format
  750. Format newFormat = null;
  751. switch (findKeyword(segments[2].toString(), typeList)) {
  752. case 0:
  753. break;
  754. case 1: case 2:// number
  755. switch (findKeyword(segments[3].toString(), modifierList)) {
  756. case 0: // default;
  757. newFormat = NumberFormat.getInstance(locale);
  758. break;
  759. case 1: case 2:// currency
  760. newFormat = NumberFormat.getCurrencyInstance(locale);
  761. break;
  762. case 3: case 4:// percent
  763. newFormat = NumberFormat.getPercentInstance(locale);
  764. break;
  765. case 5: case 6:// integer
  766. newFormat = getIntegerFormat(locale);
  767. break;
  768. default: // pattern
  769. newFormat = NumberFormat.getInstance(locale);
  770. try {
  771. ((DecimalFormat)newFormat).applyPattern(segments[3].toString());
  772. } catch (Exception e) {
  773. maxOffset = oldMaxOffset;
  774. throw new IllegalArgumentException(
  775. "Pattern incorrect or locale does not support formats, error at ");
  776. }
  777. break;
  778. }
  779. break;
  780. case 3: case 4: // date
  781. switch (findKeyword(segments[3].toString(), dateModifierList)) {
  782. case 0: // default
  783. newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, locale);
  784. break;
  785. case 1: case 2: // short
  786. newFormat = DateFormat.getDateInstance(DateFormat.SHORT, locale);
  787. break;
  788. case 3: case 4: // medium
  789. newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, locale);
  790. break;
  791. case 5: case 6: // long
  792. newFormat = DateFormat.getDateInstance(DateFormat.LONG, locale);
  793. break;
  794. case 7: case 8: // full
  795. newFormat = DateFormat.getDateInstance(DateFormat.FULL, locale);
  796. break;
  797. default:
  798. newFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, locale);
  799. try {
  800. ((SimpleDateFormat)newFormat).applyPattern(segments[3].toString());
  801. } catch (Exception e) {
  802. maxOffset = oldMaxOffset;
  803. throw new IllegalArgumentException(
  804. "Pattern incorrect or locale does not support formats, error at ");
  805. }
  806. break;
  807. }
  808. break;
  809. case 5: case 6:// time
  810. switch (findKeyword(segments[3].toString(), dateModifierList)) {
  811. case 0: // default
  812. newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale);
  813. break;
  814. case 1: case 2: // short
  815. newFormat = DateFormat.getTimeInstance(DateFormat.SHORT, locale);
  816. break;
  817. case 3: case 4: // medium
  818. newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale);
  819. break;
  820. case 5: case 6: // long
  821. newFormat = DateFormat.getTimeInstance(DateFormat.LONG, locale);
  822. break;
  823. case 7: case 8: // full
  824. newFormat = DateFormat.getTimeInstance(DateFormat.FULL, locale);
  825. break;
  826. default:
  827. newFormat = DateFormat.getTimeInstance(DateFormat.DEFAULT, locale);
  828. try {
  829. ((SimpleDateFormat)newFormat).applyPattern(segments[3].toString());
  830. } catch (Exception e) {
  831. maxOffset = oldMaxOffset;
  832. throw new IllegalArgumentException(
  833. "Pattern incorrect or locale does not support formats, error at ");
  834. }
  835. break;
  836. }
  837. break;
  838. case 7: case 8:// choice
  839. try {
  840. newFormat = new ChoiceFormat(segments[3].toString());
  841. } catch (Exception e) {
  842. maxOffset = oldMaxOffset;
  843. throw new IllegalArgumentException(
  844. "Choice Pattern incorrect, error at ");
  845. }
  846. break;
  847. default:
  848. maxOffset = oldMaxOffset;
  849. throw new IllegalArgumentException("unknown format type at ");
  850. }
  851. formats[offsetNumber] = newFormat;
  852. segments[1].setLength(0); // throw away other segments
  853. segments[2].setLength(0);
  854. segments[3].setLength(0);
  855. }
  856. private static final int findKeyword(String s, String[] list) {
  857. s = s.trim().toLowerCase();
  858. for (int i = 0; i < list.length; ++i) {
  859. if (s.equals(list[i]))
  860. return i;
  861. }
  862. return -1;
  863. }
  864. /**
  865. * Convenience method that ought to be in NumberFormat
  866. */
  867. NumberFormat getIntegerFormat(Locale locale) {
  868. NumberFormat temp = NumberFormat.getInstance(locale);
  869. if (temp instanceof DecimalFormat) {
  870. DecimalFormat temp2 = (DecimalFormat) temp;
  871. temp2.setMaximumFractionDigits(0);
  872. temp2.setDecimalSeparatorAlwaysShown(false);
  873. temp2.setParseIntegerOnly(true);
  874. }
  875. return temp;
  876. }
  877. private static final void copyAndFixQuotes(
  878. String source, int start, int end, StringBuffer target) {
  879. for (int i = start; i < end; ++i) {
  880. char ch = source.charAt(i);
  881. if (ch == '{') {
  882. target.append("'{'");
  883. } else if (ch == '}') {
  884. target.append("'}'");
  885. } else if (ch == '\'') {
  886. target.append("''");
  887. } else {
  888. target.append(ch);
  889. }
  890. }
  891. }
  892. /**
  893. * After reading an object from the input stream, do a simple verification
  894. * to maintain class invariants.
  895. * @throws InvalidObjectException if the objects read from the stream is invalid.
  896. */
  897. private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
  898. in.defaultReadObject();
  899. boolean isValid = maxOffset >= -1
  900. && maxOffset < MAX_ARGUMENTS
  901. && formats.length == MAX_ARGUMENTS
  902. && offsets.length == MAX_ARGUMENTS
  903. && argumentNumbers.length == MAX_ARGUMENTS;
  904. if (isValid) {
  905. int lastOffset = pattern.length() + 1;
  906. for (int i = maxOffset; i >= 0; --i) {
  907. if ((offsets[i] < 0) || (offsets[i] > lastOffset)) {
  908. isValid = false;
  909. break;
  910. } else {
  911. lastOffset = offsets[i];
  912. }
  913. }
  914. }
  915. if (!isValid) {
  916. throw new InvalidObjectException("Could not reconstruct MessageFormat from corrupt stream.");
  917. }
  918. }
  919. }