1. /*
  2. * @(#)ChoiceFormat.java 1.32 03/01/23
  3. *
  4. * Copyright 2003 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. /**
  148. * Sets the pattern.
  149. * @param newPattern See the class description.
  150. */
  151. public void applyPattern(String newPattern) {
  152. StringBuffer[] segments = new StringBuffer[2];
  153. for (int i = 0; i < segments.length; ++i) {
  154. segments[i] = new StringBuffer();
  155. }
  156. double[] newChoiceLimits = new double[30];
  157. String[] newChoiceFormats = new String[30];
  158. int count = 0;
  159. int part = 0;
  160. double startValue = 0;
  161. double oldStartValue = Double.NaN;
  162. boolean inQuote = false;
  163. for (int i = 0; i < newPattern.length(); ++i) {
  164. char ch = newPattern.charAt(i);
  165. if (ch=='\'') {
  166. // Check for "''" indicating a literal quote
  167. if ((i+1)<newPattern.length() && newPattern.charAt(i+1)==ch) {
  168. segments[part].append(ch);
  169. ++i;
  170. } else {
  171. inQuote = !inQuote;
  172. }
  173. } else if (inQuote) {
  174. segments[part].append(ch);
  175. } else if (ch == '<' || ch == '#' || ch == '\u2264') {
  176. if (segments[0].equals("")) {
  177. throw new IllegalArgumentException();
  178. }
  179. try {
  180. String tempBuffer = segments[0].toString();
  181. if (tempBuffer.equals("\u221E")) {
  182. startValue = Double.POSITIVE_INFINITY;
  183. } else if (tempBuffer.equals("-\u221E")) {
  184. startValue = Double.NEGATIVE_INFINITY;
  185. } else {
  186. startValue = Double.valueOf(segments[0].toString()).doubleValue();
  187. }
  188. } catch (Exception e) {
  189. throw new IllegalArgumentException();
  190. }
  191. if (ch == '<' && startValue != Double.POSITIVE_INFINITY &&
  192. startValue != Double.NEGATIVE_INFINITY) {
  193. startValue = nextDouble(startValue);
  194. }
  195. if (startValue <= oldStartValue) {
  196. throw new IllegalArgumentException();
  197. }
  198. segments[0].setLength(0);
  199. part = 1;
  200. } else if (ch == '|') {
  201. if (count == newChoiceLimits.length) {
  202. newChoiceLimits = doubleArraySize(newChoiceLimits);
  203. newChoiceFormats = doubleArraySize(newChoiceFormats);
  204. }
  205. newChoiceLimits[count] = startValue;
  206. newChoiceFormats[count] = segments[1].toString();
  207. ++count;
  208. oldStartValue = startValue;
  209. segments[1].setLength(0);
  210. part = 0;
  211. } else {
  212. segments[part].append(ch);
  213. }
  214. }
  215. // clean up last one
  216. if (part == 1) {
  217. if (count == newChoiceLimits.length) {
  218. newChoiceLimits = doubleArraySize(newChoiceLimits);
  219. newChoiceFormats = doubleArraySize(newChoiceFormats);
  220. }
  221. newChoiceLimits[count] = startValue;
  222. newChoiceFormats[count] = segments[1].toString();
  223. ++count;
  224. }
  225. choiceLimits = new double[count];
  226. System.arraycopy(newChoiceLimits, 0, choiceLimits, 0, count);
  227. choiceFormats = new String[count];
  228. System.arraycopy(newChoiceFormats, 0, choiceFormats, 0, count);
  229. }
  230. /**
  231. * Gets the pattern.
  232. */
  233. public String toPattern() {
  234. StringBuffer result = new StringBuffer();
  235. for (int i = 0; i < choiceLimits.length; ++i) {
  236. if (i != 0) {
  237. result.append('|');
  238. }
  239. // choose based upon which has less precision
  240. // approximate that by choosing the closest one to an integer.
  241. // could do better, but it's not worth it.
  242. double less = previousDouble(choiceLimits[i]);
  243. double tryLessOrEqual = Math.abs(Math.IEEEremainder(choiceLimits[i], 1.0d));
  244. double tryLess = Math.abs(Math.IEEEremainder(less, 1.0d));
  245. if (tryLessOrEqual < tryLess) {
  246. result.append(""+choiceLimits[i]);
  247. result.append('#');
  248. } else {
  249. if (choiceLimits[i] == Double.POSITIVE_INFINITY) {
  250. result.append("\u221E");
  251. } else if (choiceLimits[i] == Double.NEGATIVE_INFINITY) {
  252. result.append("-\u221E");
  253. } else {
  254. result.append(""+less);
  255. }
  256. result.append('<');
  257. }
  258. // Append choiceFormats[i], using quotes if there are special characters.
  259. // Single quotes themselves must be escaped in either case.
  260. String text = choiceFormats[i];
  261. boolean needQuote = text.indexOf('<') >= 0
  262. || text.indexOf('#') >= 0
  263. || text.indexOf('\u2264') >= 0
  264. || text.indexOf('|') >= 0;
  265. if (needQuote) result.append('\'');
  266. if (text.indexOf('\'') < 0) result.append(text);
  267. else {
  268. for (int j=0; j<text.length(); ++j) {
  269. char c = text.charAt(j);
  270. result.append(c);
  271. if (c == '\'') result.append(c);
  272. }
  273. }
  274. if (needQuote) result.append('\'');
  275. }
  276. return result.toString();
  277. }
  278. /**
  279. * Constructs with limits and corresponding formats based on the pattern.
  280. * @see #applyPattern
  281. */
  282. public ChoiceFormat(String newPattern) {
  283. applyPattern(newPattern);
  284. }
  285. /**
  286. * Constructs with the limits and the corresponding formats.
  287. * @see #setChoices
  288. */
  289. public ChoiceFormat(double[] limits, String[] formats) {
  290. setChoices(limits, formats);
  291. }
  292. /**
  293. * Set the choices to be used in formatting.
  294. * @param limits contains the top value that you want
  295. * parsed with that format,and should be in ascending sorted order. When
  296. * formatting X, the choice will be the i, where
  297. * limit[i] <= X < limit[i+1].
  298. * If the limit array is not in ascending order, the results of formatting
  299. * will be incorrect.
  300. * @param formats are the formats you want to use for each limit.
  301. * They can be either Format objects or Strings.
  302. * When formatting with object Y,
  303. * if the object is a NumberFormat, then ((NumberFormat) Y).format(X)
  304. * is called. Otherwise Y.toString() is called.
  305. */
  306. public void setChoices(double[] limits, String formats[]) {
  307. if (limits.length != formats.length) {
  308. throw new IllegalArgumentException(
  309. "Array and limit arrays must be of the same length.");
  310. }
  311. choiceLimits = limits;
  312. choiceFormats = formats;
  313. }
  314. /**
  315. * Get the limits passed in the constructor.
  316. * @return the limits.
  317. */
  318. public double[] getLimits() {
  319. return choiceLimits;
  320. }
  321. /**
  322. * Get the formats passed in the constructor.
  323. * @return the formats.
  324. */
  325. public Object[] getFormats() {
  326. return choiceFormats;
  327. }
  328. // Overrides
  329. /**
  330. * Specialization of format. This method really calls
  331. * <code>format(double, StringBuffer, FieldPosition)</code>
  332. * thus the range of longs that are supported is only equal to
  333. * the range that can be stored by double. This will never be
  334. * a practical limitation.
  335. */
  336. public StringBuffer format(long number, StringBuffer toAppendTo,
  337. FieldPosition status) {
  338. return format((double)number, toAppendTo, status);
  339. }
  340. /**
  341. * Returns pattern with formatted double.
  342. * @param number number to be formatted & substituted.
  343. * @param toAppendTo where text is appended.
  344. * @param status ignore no useful status is returned.
  345. */
  346. public StringBuffer format(double number, StringBuffer toAppendTo,
  347. FieldPosition status) {
  348. // find the number
  349. int i;
  350. for (i = 0; i < choiceLimits.length; ++i) {
  351. if (!(number >= choiceLimits[i])) {
  352. // same as number < choiceLimits, except catchs NaN
  353. break;
  354. }
  355. }
  356. --i;
  357. if (i < 0) i = 0;
  358. // return either a formatted number, or a string
  359. return toAppendTo.append(choiceFormats[i]);
  360. }
  361. /**
  362. * Parses a Number from the input text.
  363. * @param text the source text.
  364. * @param status an input-output parameter. On input, the
  365. * status.index field indicates the first character of the
  366. * source text that should be parsed. On exit, if no error
  367. * occured, status.index is set to the first unparsed character
  368. * in the source text. On exit, if an error did occur,
  369. * status.index is unchanged and status.errorIndex is set to the
  370. * first index of the character that caused the parse to fail.
  371. * @return A Number representing the value of the number parsed.
  372. */
  373. public Number parse(String text, ParsePosition status) {
  374. // find the best number (defined as the one with the longest parse)
  375. int start = status.index;
  376. int furthest = start;
  377. double bestNumber = Double.NaN;
  378. double tempNumber = 0.0;
  379. for (int i = 0; i < choiceFormats.length; ++i) {
  380. String tempString = choiceFormats[i];
  381. if (text.regionMatches(start, tempString, 0, tempString.length())) {
  382. status.index = start + tempString.length();
  383. tempNumber = choiceLimits[i];
  384. if (status.index > furthest) {
  385. furthest = status.index;
  386. bestNumber = tempNumber;
  387. if (furthest == text.length()) break;
  388. }
  389. }
  390. }
  391. status.index = furthest;
  392. if (status.index == start) {
  393. status.errorIndex = furthest;
  394. }
  395. return new Double(bestNumber);
  396. }
  397. /**
  398. * Finds the least double greater than d.
  399. * If NaN, returns same value.
  400. * <p>Used to make half-open intervals.
  401. * @see #previousDouble
  402. */
  403. public static final double nextDouble (double d) {
  404. return nextDouble(d,true);
  405. }
  406. /**
  407. * Finds the greatest double less than d.
  408. * If NaN, returns same value.
  409. * @see #nextDouble
  410. */
  411. public static final double previousDouble (double d) {
  412. return nextDouble(d,false);
  413. }
  414. /**
  415. * Overrides Cloneable
  416. */
  417. public Object clone()
  418. {
  419. ChoiceFormat other = (ChoiceFormat) super.clone();
  420. // for primitives or immutables, shallow clone is enough
  421. other.choiceLimits = (double[]) choiceLimits.clone();
  422. other.choiceFormats = (String[]) choiceFormats.clone();
  423. return other;
  424. }
  425. /**
  426. * Generates a hash code for the message format object.
  427. */
  428. public int hashCode() {
  429. int result = choiceLimits.length;
  430. if (choiceFormats.length > 0) {
  431. // enough for reasonable distribution
  432. result ^= choiceFormats[choiceFormats.length-1].hashCode();
  433. }
  434. return result;
  435. }
  436. /**
  437. * Equality comparision between two
  438. */
  439. public boolean equals(Object obj) {
  440. if (obj == null) return false;
  441. if (this == obj) // quick check
  442. return true;
  443. if (getClass() != obj.getClass())
  444. return false;
  445. ChoiceFormat other = (ChoiceFormat) obj;
  446. return (Utility.arrayEquals(choiceLimits,other.choiceLimits)
  447. && Utility.arrayEquals(choiceFormats,other.choiceFormats));
  448. }
  449. /**
  450. * After reading an object from the input stream, do a simple verification
  451. * to maintain class invariants.
  452. * @throws InvalidObjectException if the objects read from the stream is invalid.
  453. */
  454. private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
  455. in.defaultReadObject();
  456. if (choiceLimits.length != choiceFormats.length) {
  457. throw new InvalidObjectException(
  458. "limits and format arrays of different length.");
  459. }
  460. }
  461. // ===============privates===========================
  462. /**
  463. * A list of lower bounds for the choices. The formatter will return
  464. * <code>choiceFormats[i]</code> if the number being formatted is greater than or equal to
  465. * <code>choiceLimits[i]</code> and less than <code>choiceLimits[i+1]</code>.
  466. * @serial
  467. */
  468. private double[] choiceLimits;
  469. /**
  470. * A list of choice strings. The formatter will return
  471. * <code>choiceFormats[i]</code> if the number being formatted is greater than or equal to
  472. * <code>choiceLimits[i]</code> and less than <code>choiceLimits[i+1]</code>.
  473. * @serial
  474. */
  475. private String[] choiceFormats;
  476. /*
  477. static final long SIGN = 0x8000000000000000L;
  478. static final long EXPONENT = 0x7FF0000000000000L;
  479. static final long SIGNIFICAND = 0x000FFFFFFFFFFFFFL;
  480. private static double nextDouble (double d, boolean positive) {
  481. if (Double.isNaN(d) || Double.isInfinite(d)) {
  482. return d;
  483. }
  484. long bits = Double.doubleToLongBits(d);
  485. long significand = bits & SIGNIFICAND;
  486. if (bits < 0) {
  487. significand |= (SIGN | EXPONENT);
  488. }
  489. long exponent = bits & EXPONENT;
  490. if (positive) {
  491. significand += 1;
  492. // FIXME fix overflow & underflow
  493. } else {
  494. significand -= 1;
  495. // FIXME fix overflow & underflow
  496. }
  497. bits = exponent | (significand & ~EXPONENT);
  498. return Double.longBitsToDouble(bits);
  499. }
  500. */
  501. static final long SIGN = 0x8000000000000000L;
  502. static final long EXPONENT = 0x7FF0000000000000L;
  503. static final long POSITIVEINFINITY = 0x7FF0000000000000L;
  504. /**
  505. * Finds the least double greater than d (if positive == true),
  506. * or the greatest double less than d (if positive == false).
  507. * If NaN, returns same value.
  508. *
  509. * Does not affect floating-point flags,
  510. * provided these member functions do not:
  511. * Double.longBitsToDouble(long)
  512. * Double.doubleToLongBits(double)
  513. * Double.isNaN(double)
  514. */
  515. public static double nextDouble (double d, boolean positive) {
  516. /* filter out NaN's */
  517. if (Double.isNaN(d)) {
  518. return d;
  519. }
  520. /* zero's are also a special case */
  521. if (d == 0.0) {
  522. double smallestPositiveDouble = Double.longBitsToDouble(1L);
  523. if (positive) {
  524. return smallestPositiveDouble;
  525. } else {
  526. return -smallestPositiveDouble;
  527. }
  528. }
  529. /* if entering here, d is a nonzero value */
  530. /* hold all bits in a long for later use */
  531. long bits = Double.doubleToLongBits(d);
  532. /* strip off the sign bit */
  533. long magnitude = bits & ~SIGN;
  534. /* if next double away from zero, increase magnitude */
  535. if ((bits > 0) == positive) {
  536. if (magnitude != POSITIVEINFINITY) {
  537. magnitude += 1;
  538. }
  539. }
  540. /* else decrease magnitude */
  541. else {
  542. magnitude -= 1;
  543. }
  544. /* restore sign bit and return */
  545. long signbit = bits & SIGN;
  546. return Double.longBitsToDouble (magnitude | signbit);
  547. }
  548. private static double[] doubleArraySize(double[] array) {
  549. int oldSize = array.length;
  550. double[] newArray = new double[oldSize * 2];
  551. System.arraycopy(array, 0, newArray, 0, oldSize);
  552. return newArray;
  553. }
  554. private String[] doubleArraySize(String[] array) {
  555. int oldSize = array.length;
  556. String[] newArray = new String[oldSize * 2];
  557. System.arraycopy(array, 0, newArray, 0, oldSize);
  558. return newArray;
  559. }
  560. }