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