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