1. /*
  2. * @(#)ChoiceFormat.java 1.34 03/12/19
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. /*
  8. * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
  9. * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved
  10. *
  11. * The original version of this source code and documentation is copyrighted
  12. * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
  13. * materials are provided under terms of a License Agreement between Taligent
  14. * and Sun. This technology is protected by multiple US and International
  15. * patents. This notice and attribution to Taligent may not be removed.
  16. * Taligent is a registered trademark of Taligent, Inc.
  17. *
  18. */
  19. package java.text;
  20. import java.io.InvalidObjectException;
  21. import java.io.IOException;
  22. import java.io.ObjectInputStream;
  23. import sun.text.Utility;
  24. /**
  25. * A <code>ChoiceFormat</code> allows you to attach a format to a range of numbers.
  26. * It is generally used in a <code>MessageFormat</code> for handling plurals.
  27. * The choice is specified with an ascending list of doubles, where each item
  28. * specifies a half-open interval up to the next item:
  29. * <blockquote>
  30. * <pre>
  31. * X matches j if and only if limit[j] <= X < limit[j+1]
  32. * </pre>
  33. * </blockquote>
  34. * If there is no match, then either the first or last index is used, depending
  35. * on whether the number (X) is too low or too high. If the limit array is not
  36. * in ascending order, the results of formatting will be incorrect. ChoiceFormat
  37. * also accepts <code>\u221E</code> as equivalent to infinity(INF).
  38. *
  39. * <p>
  40. * <strong>Note:</strong>
  41. * <code>ChoiceFormat</code> differs from the other <code>Format</code>
  42. * classes in that you create a <code>ChoiceFormat</code> object with a
  43. * constructor (not with a <code>getInstance</code> style factory
  44. * method). The factory methods aren't necessary because <code>ChoiceFormat</code>
  45. * doesn't require any complex setup for a given locale. In fact,
  46. * <code>ChoiceFormat</code> doesn't implement any locale specific behavior.
  47. *
  48. * <p>
  49. * When creating a <code>ChoiceFormat</code>, you must specify an array of formats
  50. * and an array of limits. The length of these arrays must be the same.
  51. * For example,
  52. * <ul>
  53. * <li>
  54. * <em>limits</em> = {1,2,3,4,5,6,7}<br>
  55. * <em>formats</em> = {"Sun","Mon","Tue","Wed","Thur","Fri","Sat"}
  56. * <li>
  57. * <em>limits</em> = {0, 1, ChoiceFormat.nextDouble(1)}<br>
  58. * <em>formats</em> = {"no files", "one file", "many files"}<br>
  59. * (<code>nextDouble</code> can be used to get the next higher double, to
  60. * make the half-open interval.)
  61. * </ul>
  62. *
  63. * <p>
  64. * Here is a simple example that shows formatting and parsing:
  65. * <blockquote>
  66. * <pre>
  67. * double[] limits = {1,2,3,4,5,6,7};
  68. * String[] monthNames = {"Sun","Mon","Tue","Wed","Thur","Fri","Sat"};
  69. * ChoiceFormat form = new ChoiceFormat(limits, monthNames);
  70. * ParsePosition status = new ParsePosition(0);
  71. * for (double i = 0.0; i <= 8.0; ++i) {
  72. * status.setIndex(0);
  73. * System.out.println(i + " -> " + form.format(i) + " -> "
  74. * + form.parse(form.format(i),status));
  75. * }
  76. * </pre>
  77. * </blockquote>
  78. * Here is a more complex example, with a pattern format:
  79. * <blockquote>
  80. * <pre>
  81. * double[] filelimits = {0,1,2};
  82. * String[] filepart = {"are no files","is one file","are {2} files"};
  83. * ChoiceFormat fileform = new ChoiceFormat(filelimits, filepart);
  84. * Format[] testFormats = {fileform, null, NumberFormat.getInstance()};
  85. * MessageFormat pattform = new MessageFormat("There {0} on {1}");
  86. * pattform.setFormats(testFormats);
  87. * Object[] testArgs = {null, "ADisk", null};
  88. * for (int i = 0; i < 4; ++i) {
  89. * testArgs[0] = new Integer(i);
  90. * testArgs[2] = testArgs[0];
  91. * System.out.println(pattform.format(testArgs));
  92. * }
  93. * </pre>
  94. * </blockquote>
  95. * <p>
  96. * Specifying a pattern for ChoiceFormat objects is fairly straightforward.
  97. * For example:
  98. * <blockquote>
  99. * <pre>
  100. * ChoiceFormat fmt = new ChoiceFormat(
  101. * "-1#is negative| 0#is zero or fraction | 1#is one |1.0<is 1+ |2#is two |2<is more than 2.");
  102. * System.out.println("Formatter Pattern : " + fmt.toPattern());
  103. *
  104. * System.out.println("Format with -INF : " + fmt.format(Double.NEGATIVE_INFINITY));
  105. * System.out.println("Format with -1.0 : " + fmt.format(-1.0));
  106. * System.out.println("Format with 0 : " + fmt.format(0));
  107. * System.out.println("Format with 0.9 : " + fmt.format(0.9));
  108. * System.out.println("Format with 1.0 : " + fmt.format(1));
  109. * System.out.println("Format with 1.5 : " + fmt.format(1.5));
  110. * System.out.println("Format with 2 : " + fmt.format(2));
  111. * System.out.println("Format with 2.1 : " + fmt.format(2.1));
  112. * System.out.println("Format with NaN : " + fmt.format(Double.NaN));
  113. * System.out.println("Format with +INF : " + fmt.format(Double.POSITIVE_INFINITY));
  114. * </pre>
  115. * </blockquote>
  116. * And the output result would be like the following:
  117. * <pre>
  118. * <blockquote>
  119. * Format with -INF : is negative
  120. * Format with -1.0 : is negative
  121. * Format with 0 : is zero or fraction
  122. * Format with 0.9 : is zero or fraction
  123. * Format with 1.0 : is one
  124. * Format with 1.5 : is 1+
  125. * Format with 2 : is two
  126. * Format with 2.1 : is more than 2.
  127. * Format with NaN : is negative
  128. * Format with +INF : is more than 2.
  129. * </pre>
  130. * </blockquote>
  131. *
  132. * <h4><a name="synchronization">Synchronization</a></h4>
  133. *
  134. * <p>
  135. * Choice formats are not synchronized.
  136. * It is recommended to create separate format instances for each thread.
  137. * If multiple threads access a format concurrently, it must be synchronized
  138. * externally.
  139. *
  140. *
  141. * @see DecimalFormat
  142. * @see MessageFormat
  143. * @version 1.22 09/21/98
  144. * @author Mark Davis
  145. */
  146. public class ChoiceFormat extends NumberFormat {
  147. // Proclaim serial compatibility with 1.1 FCS
  148. private static final long serialVersionUID = 1795184449645032964L;
  149. /**
  150. * Sets the pattern.
  151. * @param newPattern See the class description.
  152. */
  153. public void applyPattern(String newPattern) {
  154. StringBuffer[] segments = new StringBuffer[2];
  155. for (int i = 0; i < segments.length; ++i) {
  156. segments[i] = new StringBuffer();
  157. }
  158. double[] newChoiceLimits = new double[30];
  159. String[] newChoiceFormats = new String[30];
  160. int count = 0;
  161. int part = 0;
  162. double startValue = 0;
  163. double oldStartValue = Double.NaN;
  164. boolean inQuote = false;
  165. for (int i = 0; i < newPattern.length(); ++i) {
  166. char ch = newPattern.charAt(i);
  167. if (ch=='\'') {
  168. // Check for "''" indicating a literal quote
  169. if ((i+1)<newPattern.length() && newPattern.charAt(i+1)==ch) {
  170. segments[part].append(ch);
  171. ++i;
  172. } else {
  173. inQuote = !inQuote;
  174. }
  175. } else if (inQuote) {
  176. segments[part].append(ch);
  177. } else if (ch == '<' || ch == '#' || ch == '\u2264') {
  178. if (segments[0].equals("")) {
  179. throw new IllegalArgumentException();
  180. }
  181. try {
  182. String tempBuffer = segments[0].toString();
  183. if (tempBuffer.equals("\u221E")) {
  184. startValue = Double.POSITIVE_INFINITY;
  185. } else if (tempBuffer.equals("-\u221E")) {
  186. startValue = Double.NEGATIVE_INFINITY;
  187. } else {
  188. startValue = Double.valueOf(segments[0].toString()).doubleValue();
  189. }
  190. } catch (Exception e) {
  191. throw new IllegalArgumentException();
  192. }
  193. if (ch == '<' && startValue != Double.POSITIVE_INFINITY &&
  194. startValue != Double.NEGATIVE_INFINITY) {
  195. startValue = nextDouble(startValue);
  196. }
  197. if (startValue <= oldStartValue) {
  198. throw new IllegalArgumentException();
  199. }
  200. segments[0].setLength(0);
  201. part = 1;
  202. } else if (ch == '|') {
  203. if (count == newChoiceLimits.length) {
  204. newChoiceLimits = doubleArraySize(newChoiceLimits);
  205. newChoiceFormats = doubleArraySize(newChoiceFormats);
  206. }
  207. newChoiceLimits[count] = startValue;
  208. newChoiceFormats[count] = segments[1].toString();
  209. ++count;
  210. oldStartValue = startValue;
  211. segments[1].setLength(0);
  212. part = 0;
  213. } else {
  214. segments[part].append(ch);
  215. }
  216. }
  217. // clean up last one
  218. if (part == 1) {
  219. if (count == newChoiceLimits.length) {
  220. newChoiceLimits = doubleArraySize(newChoiceLimits);
  221. newChoiceFormats = doubleArraySize(newChoiceFormats);
  222. }
  223. newChoiceLimits[count] = startValue;
  224. newChoiceFormats[count] = segments[1].toString();
  225. ++count;
  226. }
  227. choiceLimits = new double[count];
  228. System.arraycopy(newChoiceLimits, 0, choiceLimits, 0, count);
  229. choiceFormats = new String[count];
  230. System.arraycopy(newChoiceFormats, 0, choiceFormats, 0, count);
  231. }
  232. /**
  233. * Gets the pattern.
  234. */
  235. public String toPattern() {
  236. StringBuffer result = new StringBuffer();
  237. for (int i = 0; i < choiceLimits.length; ++i) {
  238. if (i != 0) {
  239. result.append('|');
  240. }
  241. // choose based upon which has less precision
  242. // approximate that by choosing the closest one to an integer.
  243. // could do better, but it's not worth it.
  244. double less = previousDouble(choiceLimits[i]);
  245. double tryLessOrEqual = Math.abs(Math.IEEEremainder(choiceLimits[i], 1.0d));
  246. double tryLess = Math.abs(Math.IEEEremainder(less, 1.0d));
  247. if (tryLessOrEqual < tryLess) {
  248. result.append(""+choiceLimits[i]);
  249. result.append('#');
  250. } else {
  251. if (choiceLimits[i] == Double.POSITIVE_INFINITY) {
  252. result.append("\u221E");
  253. } else if (choiceLimits[i] == Double.NEGATIVE_INFINITY) {
  254. result.append("-\u221E");
  255. } else {
  256. result.append(""+less);
  257. }
  258. result.append('<');
  259. }
  260. // Append choiceFormats[i], using quotes if there are special characters.
  261. // Single quotes themselves must be escaped in either case.
  262. String text = choiceFormats[i];
  263. boolean needQuote = text.indexOf('<') >= 0
  264. || text.indexOf('#') >= 0
  265. || text.indexOf('\u2264') >= 0
  266. || text.indexOf('|') >= 0;
  267. if (needQuote) result.append('\'');
  268. if (text.indexOf('\'') < 0) result.append(text);
  269. else {
  270. for (int j=0; j<text.length(); ++j) {
  271. char c = text.charAt(j);
  272. result.append(c);
  273. if (c == '\'') result.append(c);
  274. }
  275. }
  276. if (needQuote) result.append('\'');
  277. }
  278. return result.toString();
  279. }
  280. /**
  281. * Constructs with limits and corresponding formats based on the pattern.
  282. * @see #applyPattern
  283. */
  284. public ChoiceFormat(String newPattern) {
  285. applyPattern(newPattern);
  286. }
  287. /**
  288. * Constructs with the limits and the corresponding formats.
  289. * @see #setChoices
  290. */
  291. public ChoiceFormat(double[] limits, String[] formats) {
  292. setChoices(limits, formats);
  293. }
  294. /**
  295. * Set the choices to be used in formatting.
  296. * @param limits contains the top value that you want
  297. * parsed with that format,and should be in ascending sorted order. When
  298. * formatting X, the choice will be the i, where
  299. * limit[i] <= X < limit[i+1].
  300. * If the limit array is not in ascending order, the results of formatting
  301. * will be incorrect.
  302. * @param formats are the formats you want to use for each limit.
  303. * They can be either Format objects or Strings.
  304. * When formatting with object Y,
  305. * if the object is a NumberFormat, then ((NumberFormat) Y).format(X)
  306. * is called. Otherwise Y.toString() is called.
  307. */
  308. public void setChoices(double[] limits, String formats[]) {
  309. if (limits.length != formats.length) {
  310. throw new IllegalArgumentException(
  311. "Array and limit arrays must be of the same length.");
  312. }
  313. choiceLimits = limits;
  314. choiceFormats = formats;
  315. }
  316. /**
  317. * Get the limits passed in the constructor.
  318. * @return the limits.
  319. */
  320. public double[] getLimits() {
  321. return choiceLimits;
  322. }
  323. /**
  324. * Get the formats passed in the constructor.
  325. * @return the formats.
  326. */
  327. public Object[] getFormats() {
  328. return choiceFormats;
  329. }
  330. // Overrides
  331. /**
  332. * Specialization of format. This method really calls
  333. * <code>format(double, StringBuffer, FieldPosition)</code>
  334. * thus the range of longs that are supported is only equal to
  335. * the range that can be stored by double. This will never be
  336. * a practical limitation.
  337. */
  338. public StringBuffer format(long number, StringBuffer toAppendTo,
  339. FieldPosition status) {
  340. return format((double)number, toAppendTo, status);
  341. }
  342. /**
  343. * Returns pattern with formatted double.
  344. * @param number number to be formatted & substituted.
  345. * @param toAppendTo where text is appended.
  346. * @param status ignore no useful status is returned.
  347. */
  348. public StringBuffer format(double number, StringBuffer toAppendTo,
  349. FieldPosition status) {
  350. // find the number
  351. int i;
  352. for (i = 0; i < choiceLimits.length; ++i) {
  353. if (!(number >= choiceLimits[i])) {
  354. // same as number < choiceLimits, except catchs NaN
  355. break;
  356. }
  357. }
  358. --i;
  359. if (i < 0) i = 0;
  360. // return either a formatted number, or a string
  361. return toAppendTo.append(choiceFormats[i]);
  362. }
  363. /**
  364. * Parses a Number from the input text.
  365. * @param text the source text.
  366. * @param status an input-output parameter. On input, the
  367. * status.index field indicates the first character of the
  368. * source text that should be parsed. On exit, if no error
  369. * occured, status.index is set to the first unparsed character
  370. * in the source text. On exit, if an error did occur,
  371. * status.index is unchanged and status.errorIndex is set to the
  372. * first index of the character that caused the parse to fail.
  373. * @return A Number representing the value of the number parsed.
  374. */
  375. public Number parse(String text, ParsePosition status) {
  376. // find the best number (defined as the one with the longest parse)
  377. int start = status.index;
  378. int furthest = start;
  379. double bestNumber = Double.NaN;
  380. double tempNumber = 0.0;
  381. for (int i = 0; i < choiceFormats.length; ++i) {
  382. String tempString = choiceFormats[i];
  383. if (text.regionMatches(start, tempString, 0, tempString.length())) {
  384. status.index = start + tempString.length();
  385. tempNumber = choiceLimits[i];
  386. if (status.index > furthest) {
  387. furthest = status.index;
  388. bestNumber = tempNumber;
  389. if (furthest == text.length()) break;
  390. }
  391. }
  392. }
  393. status.index = furthest;
  394. if (status.index == start) {
  395. status.errorIndex = furthest;
  396. }
  397. return new Double(bestNumber);
  398. }
  399. /**
  400. * Finds the least double greater than d.
  401. * If NaN, returns same value.
  402. * <p>Used to make half-open intervals.
  403. * @see #previousDouble
  404. */
  405. public static final double nextDouble (double d) {
  406. return nextDouble(d,true);
  407. }
  408. /**
  409. * Finds the greatest double less than d.
  410. * If NaN, returns same value.
  411. * @see #nextDouble
  412. */
  413. public static final double previousDouble (double d) {
  414. return nextDouble(d,false);
  415. }
  416. /**
  417. * Overrides Cloneable
  418. */
  419. public Object clone()
  420. {
  421. ChoiceFormat other = (ChoiceFormat) super.clone();
  422. // for primitives or immutables, shallow clone is enough
  423. other.choiceLimits = (double[]) choiceLimits.clone();
  424. other.choiceFormats = (String[]) choiceFormats.clone();
  425. return other;
  426. }
  427. /**
  428. * Generates a hash code for the message format object.
  429. */
  430. public int hashCode() {
  431. int result = choiceLimits.length;
  432. if (choiceFormats.length > 0) {
  433. // enough for reasonable distribution
  434. result ^= choiceFormats[choiceFormats.length-1].hashCode();
  435. }
  436. return result;
  437. }
  438. /**
  439. * Equality comparision between two
  440. */
  441. public boolean equals(Object obj) {
  442. if (obj == null) return false;
  443. if (this == obj) // quick check
  444. return true;
  445. if (getClass() != obj.getClass())
  446. return false;
  447. ChoiceFormat other = (ChoiceFormat) obj;
  448. return (Utility.arrayEquals(choiceLimits,other.choiceLimits)
  449. && Utility.arrayEquals(choiceFormats,other.choiceFormats));
  450. }
  451. /**
  452. * After reading an object from the input stream, do a simple verification
  453. * to maintain class invariants.
  454. * @throws InvalidObjectException if the objects read from the stream is invalid.
  455. */
  456. private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
  457. in.defaultReadObject();
  458. if (choiceLimits.length != choiceFormats.length) {
  459. throw new InvalidObjectException(
  460. "limits and format arrays of different length.");
  461. }
  462. }
  463. // ===============privates===========================
  464. /**
  465. * A list of lower bounds for the choices. The formatter will return
  466. * <code>choiceFormats[i]</code> if the number being formatted is greater than or equal to
  467. * <code>choiceLimits[i]</code> and less than <code>choiceLimits[i+1]</code>.
  468. * @serial
  469. */
  470. private double[] choiceLimits;
  471. /**
  472. * A list of choice strings. The formatter will return
  473. * <code>choiceFormats[i]</code> if the number being formatted is greater than or equal to
  474. * <code>choiceLimits[i]</code> and less than <code>choiceLimits[i+1]</code>.
  475. * @serial
  476. */
  477. private String[] choiceFormats;
  478. /*
  479. static final long SIGN = 0x8000000000000000L;
  480. static final long EXPONENT = 0x7FF0000000000000L;
  481. static final long SIGNIFICAND = 0x000FFFFFFFFFFFFFL;
  482. private static double nextDouble (double d, boolean positive) {
  483. if (Double.isNaN(d) || Double.isInfinite(d)) {
  484. return d;
  485. }
  486. long bits = Double.doubleToLongBits(d);
  487. long significand = bits & SIGNIFICAND;
  488. if (bits < 0) {
  489. significand |= (SIGN | EXPONENT);
  490. }
  491. long exponent = bits & EXPONENT;
  492. if (positive) {
  493. significand += 1;
  494. // FIXME fix overflow & underflow
  495. } else {
  496. significand -= 1;
  497. // FIXME fix overflow & underflow
  498. }
  499. bits = exponent | (significand & ~EXPONENT);
  500. return Double.longBitsToDouble(bits);
  501. }
  502. */
  503. static final long SIGN = 0x8000000000000000L;
  504. static final long EXPONENT = 0x7FF0000000000000L;
  505. static final long POSITIVEINFINITY = 0x7FF0000000000000L;
  506. /**
  507. * Finds the least double greater than d (if positive == true),
  508. * or the greatest double less than d (if positive == false).
  509. * If NaN, returns same value.
  510. *
  511. * Does not affect floating-point flags,
  512. * provided these member functions do not:
  513. * Double.longBitsToDouble(long)
  514. * Double.doubleToLongBits(double)
  515. * Double.isNaN(double)
  516. */
  517. public static double nextDouble (double d, boolean positive) {
  518. /* filter out NaN's */
  519. if (Double.isNaN(d)) {
  520. return d;
  521. }
  522. /* zero's are also a special case */
  523. if (d == 0.0) {
  524. double smallestPositiveDouble = Double.longBitsToDouble(1L);
  525. if (positive) {
  526. return smallestPositiveDouble;
  527. } else {
  528. return -smallestPositiveDouble;
  529. }
  530. }
  531. /* if entering here, d is a nonzero value */
  532. /* hold all bits in a long for later use */
  533. long bits = Double.doubleToLongBits(d);
  534. /* strip off the sign bit */
  535. long magnitude = bits & ~SIGN;
  536. /* if next double away from zero, increase magnitude */
  537. if ((bits > 0) == positive) {
  538. if (magnitude != POSITIVEINFINITY) {
  539. magnitude += 1;
  540. }
  541. }
  542. /* else decrease magnitude */
  543. else {
  544. magnitude -= 1;
  545. }
  546. /* restore sign bit and return */
  547. long signbit = bits & SIGN;
  548. return Double.longBitsToDouble (magnitude | signbit);
  549. }
  550. private static double[] doubleArraySize(double[] array) {
  551. int oldSize = array.length;
  552. double[] newArray = new double[oldSize * 2];
  553. System.arraycopy(array, 0, newArray, 0, oldSize);
  554. return newArray;
  555. }
  556. private String[] doubleArraySize(String[] array) {
  557. int oldSize = array.length;
  558. String[] newArray = new String[oldSize * 2];
  559. System.arraycopy(array, 0, newArray, 0, oldSize);
  560. return newArray;
  561. }
  562. }