- /*
- * @(#)ChoiceFormat.java 1.26 00/01/19
- *
- * Copyright 1996-2000 Sun Microsystems, Inc. All Rights Reserved.
- *
- * This software is the proprietary information of Sun Microsystems, Inc.
- * Use is subject to license terms.
- *
- */
-
- /*
- * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
- * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved
- *
- * The original version of this source code and documentation is copyrighted
- * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
- * materials are provided under terms of a License Agreement between Taligent
- * and Sun. This technology is protected by multiple US and International
- * patents. This notice and attribution to Taligent may not be removed.
- * Taligent is a registered trademark of Taligent, Inc.
- *
- */
-
- package java.text;
- import java.text.Utility;
- import java.io.ObjectInputStream;
- import java.io.InvalidObjectException;
- import java.io.IOException;
-
- /**
- * A <code>ChoiceFormat</code> allows you to attach a format to a range of numbers.
- * It is generally used in a <code>MessageFormat</code> for handling plurals.
- * The choice is specified with an ascending list of doubles, where each item
- * specifies a half-open interval up to the next item:
- * <blockquote>
- * <pre>
- * X matches j if and only if limit[j] <= X < limit[j+1]
- * </pre>
- * </blockquote>
- * If there is no match, then either the first or last index is used, depending
- * on whether the number (X) is too low or too high. If the limit array is not
- * in ascending order, the results of formatting will be incorrect. ChoiceFormat
- * also accepts <code>\\u221E</code> as equivalent to infinity(INF).
- *
- * <p>
- * <strong>Note:</strong>
- * <code>ChoiceFormat</code> differs from the other <code>Format</code>
- * classes in that you create a <code>ChoiceFormat</code> object with a
- * constructor (not with a <code>getInstance</code> style factory
- * method). The factory methods aren't necessary because <code>ChoiceFormat</code>
- * doesn't require any complex setup for a given locale. In fact,
- * <code>ChoiceFormat</code> doesn't implement any locale specific behavior.
- *
- * <p>
- * When creating a <code>ChoiceFormat</code>, you must specify an array of formats
- * and an array of limits. The length of these arrays must be the same.
- * For example,
- * <ul>
- * <li>
- * <em>limits</em> = {1,2,3,4,5,6,7}<br>
- * <em>formats</em> = {"Sun","Mon","Tue","Wed","Thur","Fri","Sat"}
- * <li>
- * <em>limits</em> = {0, 1, ChoiceFormat.nextDouble(1)}<br>
- * <em>formats</em> = {"no files", "one file", "many files"}<br>
- * (<code>nextDouble</code> can be used to get the next higher double, to
- * make the half-open interval.)
- * </ul>
- *
- * <p>
- * Here is a simple example that shows formatting and parsing:
- * <blockquote>
- * <pre>
- * double[] limits = {1,2,3,4,5,6,7};
- * String[] monthNames = {"Sun","Mon","Tue","Wed","Thur","Fri","Sat"};
- * ChoiceFormat form = new ChoiceFormat(limits, monthNames);
- * ParsePosition status = new ParsePosition(0);
- * for (double i = 0.0; i <= 8.0; ++i) {
- * status.setIndex(0);
- * System.out.println(i + " -> " + form.format(i) + " -> "
- * + form.parse(form.format(i),status));
- * }
- * </pre>
- * </blockquote>
- * Here is a more complex example, with a pattern format:
- * <blockquote>
- * <pre>
- * double[] filelimits = {0,1,2};
- * String[] filepart = {"are no files","is one file","are {2} files"};
- * ChoiceFormat fileform = new ChoiceFormat(filelimits, filepart);
- * Format[] testFormats = {fileform, null, NumberFormat.getInstance()};
- * MessageFormat pattform = new MessageFormat("There {0} on {1}");
- * pattform.setFormats(testFormats);
- * Object[] testArgs = {null, "ADisk", null};
- * for (int i = 0; i < 4; ++i) {
- * testArgs[0] = new Integer(i);
- * testArgs[2] = testArgs[0];
- * System.out.println(pattform.format(testArgs));
- * }
- * </pre>
- * </blockquote>
- * <p>
- * Specifying a pattern for ChoiceFormat objects is fairly straightforward.
- * For example:
- * <blockquote>
- * <pre>
- * ChoiceFormat fmt = new ChoiceFormat(
- * "-1#is negative| 0#is zero or fraction | 1#is one |1.0<is 1+ |2#is two |2<is more than 2.");
- * System.out.println("Formatter Pattern : " + fmt.toPattern());
- *
- * System.out.println("Format with -INF : " + fmt.format(Double.NEGATIVE_INFINITY));
- * System.out.println("Format with -1.0 : " + fmt.format(-1.0));
- * System.out.println("Format with 0 : " + fmt.format(0));
- * System.out.println("Format with 0.9 : " + fmt.format(0.9));
- * System.out.println("Format with 1.0 : " + fmt.format(1));
- * System.out.println("Format with 1.5 : " + fmt.format(1.5));
- * System.out.println("Format with 2 : " + fmt.format(2));
- * System.out.println("Format with 2.1 : " + fmt.format(2.1));
- * System.out.println("Format with NaN : " + fmt.format(Double.NaN));
- * System.out.println("Format with +INF : " + fmt.format(Double.POSITIVE_INFINITY));
- * </pre>
- * </blockquote>
- * And the output result would be like the following:
- * <pre>
- * <blockquote>
- * Format with -INF : is negative
- * Format with -1.0 : is negative
- * Format with 0 : is zero or fraction
- * Format with 0.9 : is zero or fraction
- * Format with 1.0 : is one
- * Format with 1.5 : is 1+
- * Format with 2 : is two
- * Format with 2.1 : is more than 2.
- * Format with NaN : is negative
- * Format with +INF : is more than 2.
- * </pre>
- * </blockquote>
- * @see DecimalFormat
- * @see MessageFormat
- * @version 1.22 09/21/98
- * @author Mark Davis
- */
- public class ChoiceFormat extends NumberFormat {
- /**
- * Sets the pattern.
- * @param newPattern See the class description.
- */
- public void applyPattern(String newPattern) {
- StringBuffer[] segments = new StringBuffer[2];
- for (int i = 0; i < segments.length; ++i) {
- segments[i] = new StringBuffer(); // later, use single
- }
- double[] newChoiceLimits = new double[30]; // current limit
- String[] newChoiceFormats = new String[30]; // later, use Vectors
- int count = 0;
- int part = 0;
- double startValue = 0;
- double oldStartValue = Double.NaN;
- boolean inQuote = false;
- for (int i = 0; i < newPattern.length(); ++i) {
- char ch = newPattern.charAt(i);
- if (ch=='\'') {
- // Check for "''" indicating a literal quote
- if ((i+1)<newPattern.length() && newPattern.charAt(i+1)==ch) {
- segments[part].append(ch);
- ++i;
- }
- else inQuote = !inQuote;
- }
- else if (inQuote) {
- segments[part].append(ch);
- }
- else if (ch == '<' || ch == '#' || ch == '\u2264') {
- if (segments[0].equals("")) {
- throw new IllegalArgumentException();
- }
- try {
- String tempBuffer = segments[0].toString();
- if (tempBuffer.equals("\u221E")) {
- startValue = Double.POSITIVE_INFINITY;
- } else if (tempBuffer.equals("-\u221E")) {
- startValue = Double.NEGATIVE_INFINITY;
- } else {
- startValue = Double.valueOf(segments[0].toString()).doubleValue();
- }
- } catch (Exception e) {
- throw new IllegalArgumentException();
- }
- if (ch == '<' && startValue != Double.POSITIVE_INFINITY &&
- startValue != Double.NEGATIVE_INFINITY) {
- startValue = nextDouble(startValue);
- }
- if (startValue <= oldStartValue) {
- throw new IllegalArgumentException();
- }
- segments[0].setLength(0);
- part = 1;
- } else if (ch == '|') {
- newChoiceLimits[count] = startValue;
- newChoiceFormats[count] = segments[1].toString();
- ++count;
- oldStartValue = startValue;
- segments[1].setLength(0);
- part = 0;
- } else {
- segments[part].append(ch);
- }
- }
- // clean up last one
- if (part == 1) {
- newChoiceLimits[count] = startValue;
- newChoiceFormats[count] = segments[1].toString();
- ++count;
- }
- choiceLimits = new double[count];
- System.arraycopy(newChoiceLimits, 0, choiceLimits, 0, count);
- choiceFormats = new String[count];
- System.arraycopy(newChoiceFormats, 0, choiceFormats, 0, count);
- }
- /**
- * Gets the pattern.
- */
-
- public String toPattern() {
- StringBuffer result = new StringBuffer();
- for (int i = 0; i < choiceLimits.length; ++i) {
- if (i != 0) {
- result.append('|');
- }
- // choose based upon which has less precision
- // approximate that by choosing the closest one to an integer.
- // could do better, but it's not worth it.
- double less = previousDouble(choiceLimits[i]);
- double tryLessOrEqual = Math.abs(Math.IEEEremainder(choiceLimits[i], 1.0d));
- double tryLess = Math.abs(Math.IEEEremainder(less, 1.0d));
-
- if (tryLessOrEqual < tryLess) {
- result.append(""+choiceLimits[i]);
- result.append('#');
- } else {
- if (choiceLimits[i] == Double.POSITIVE_INFINITY) {
- result.append("\u221E");
- } else if (choiceLimits[i] == Double.NEGATIVE_INFINITY) {
- result.append("-\u221E");
- } else {
- result.append(""+less);
- }
- result.append('<');
- }
- // Append choiceFormats[i], using quotes if there are special characters.
- // Single quotes themselves must be escaped in either case.
- String text = choiceFormats[i];
- boolean needQuote = text.indexOf('<') >= 0
- || text.indexOf('#') >= 0
- || text.indexOf('\u2264') >= 0
- || text.indexOf('|') >= 0;
- if (needQuote) result.append('\'');
- if (text.indexOf('\'') < 0) result.append(text);
- else {
- for (int j=0; j<text.length(); ++j) {
- char c = text.charAt(j);
- result.append(c);
- if (c == '\'') result.append(c);
- }
- }
- if (needQuote) result.append('\'');
- }
- return result.toString();
- }
-
- /**
- * Constructs with limits and corresponding formats based on the pattern.
- */
- public ChoiceFormat(String newPattern) {
- applyPattern(newPattern);
- }
-
- /**
- * Constructs with the limits and the corresponding formats.
- * @see #setChoices
- */
- public ChoiceFormat(double[] limits, String[] formats) {
- setChoices(limits, formats);
- }
-
- /**
- * Set the choices to be used in formatting.
- * @param limits contains the top value that you want
- * parsed with that format,and should be in ascending sorted order. When
- * formatting X, the choice will be the i, where
- * limit[i] <= X < limit[i+1].
- * If the limit array is not in ascending order, the results of formatting
- * will be incorrect.
- * @param formats are the formats you want to use for each limit.
- * They can be either Format objects or Strings.
- * When formatting with object Y,
- * if the object is a NumberFormat, then ((NumberFormat) Y).format(X)
- * is called. Otherwise Y.toString() is called.
- */
- public void setChoices(double[] limits, String formats[]) {
- if (limits.length != formats.length) {
- throw new IllegalArgumentException(
- "Array and limit arrays must be of the same length.");
- }
- choiceLimits = limits;
- choiceFormats = formats;
- }
-
- /**
- * Get the limits passed in the constructor.
- * @return the limits.
- */
- public double[] getLimits() {
- return choiceLimits;
- }
-
- /**
- * Get the formats passed in the constructor.
- * @return the formats.
- */
- public Object[] getFormats() {
- return choiceFormats;
- }
-
- // Overrides
-
- /**
- * Specialization of format. This method really calls
- * <code>format(double, StringBuffer, FieldPosition)</code>
- * thus the range of longs that are supported is only equal to
- * the range that can be stored by double. This will never be
- * a practical limitation.
- */
- public StringBuffer format(long number, StringBuffer toAppendTo,
- FieldPosition status) {
- return format((double)number, toAppendTo, status);
- }
-
- /**
- * Returns pattern with formatted double.
- * @param number number to be formatted & substituted.
- * @param toAppendTo where text is appended.
- * @param status ignore no useful status is returned.
- */
- public StringBuffer format(double number, StringBuffer toAppendTo,
- FieldPosition status) {
- // find the number
- int i;
- for (i = 0; i < choiceLimits.length; ++i) {
- if (!(number >= choiceLimits[i])) {
- // same as number < choiceLimits, except catchs NaN
- break;
- }
- }
- --i;
- if (i < 0) i = 0;
- // return either a formatted number, or a string
- return toAppendTo.append(choiceFormats[i]);
- }
-
- /**
- * Parses a Number from the input text.
- * @param text the source text.
- * @param status an input-output parameter. On input, the
- * status.index field indicates the first character of the
- * source text that should be parsed. On exit, if no error
- * occured, status.index is set to the first unparsed character
- * in the source text. On exit, if an error did occur,
- * status.index is unchanged and status.errorIndex is set to the
- * first index of the character that caused the parse to fail.
- * @return A Number representing the value of the number parsed.
- */
- public Number parse(String text, ParsePosition status) {
- // find the best number (defined as the one with the longest parse)
- int start = status.index;
- int furthest = start;
- double bestNumber = Double.NaN;
- double tempNumber = 0.0;
- for (int i = 0; i < choiceFormats.length; ++i) {
- String tempString = choiceFormats[i];
- if (text.regionMatches(start, tempString, 0, tempString.length())) {
- status.index = start + tempString.length();
- tempNumber = choiceLimits[i];
- if (status.index > furthest) {
- furthest = status.index;
- bestNumber = tempNumber;
- if (furthest == text.length()) break;
- }
- }
- }
- status.index = furthest;
- if (status.index == start) {
- status.errorIndex = furthest;
- }
- return new Double(bestNumber);
- }
-
- /**
- * Finds the least double greater than d.
- * If NaN, returns same value.
- * <p>Used to make half-open intervals.
- * @see #previousDouble
- */
- public static final double nextDouble (double d) {
- return nextDouble(d,true);
- }
-
- /**
- * Finds the greatest double less than d.
- * If NaN, returns same value.
- * @see #nextDouble
- */
- public static final double previousDouble (double d) {
- return nextDouble(d,false);
- }
-
- /**
- * Overrides Cloneable
- */
- public Object clone()
- {
- ChoiceFormat other = (ChoiceFormat) super.clone();
- // for primitives or immutables, shallow clone is enough
- other.choiceLimits = (double[]) choiceLimits.clone();
- other.choiceFormats = (String[]) choiceFormats.clone();
- return other;
- }
-
- /**
- * Generates a hash code for the message format object.
- */
- public int hashCode() {
- int result = choiceLimits.length;
- if (choiceFormats.length > 0) {
- // enough for reasonable distribution
- result ^= choiceFormats[choiceFormats.length-1].hashCode();
- }
- return result;
- }
-
- /**
- * Equality comparision between two
- */
- public boolean equals(Object obj) {
- if (obj == null) return false;
- if (this == obj) // quick check
- return true;
- if (getClass() != obj.getClass())
- return false;
- ChoiceFormat other = (ChoiceFormat) obj;
- return (Utility.arrayEquals(choiceLimits,other.choiceLimits)
- && Utility.arrayEquals(choiceFormats,other.choiceFormats));
- }
-
- /**
- * After reading an object from the input stream, do a simple verification
- * to maintain class invariants.
- * @throws InvalidObjectException if the objects read from the stream is invalid.
- */
- private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
- in.defaultReadObject();
- if (choiceLimits.length != choiceFormats.length) {
- throw new InvalidObjectException(
- "limits and format arrays of different length.");
- }
- }
-
- // ===============privates===========================
-
- /**
- * A list of lower bounds for the choices. The formatter will return
- * <code>choiceFormats[i]</code> if the number being formatted is greater than or equal to
- * <code>choiceLimits[i]</code> and less than <code>choiceLimits[i+1]</code>.
- * @serial
- */
- private double[] choiceLimits;
-
- /**
- * A list of choice strings. The formatter will return
- * <code>choiceFormats[i]</code> if the number being formatted is greater than or equal to
- * <code>choiceLimits[i]</code> and less than <code>choiceLimits[i+1]</code>.
- * @serial
- */
- private String[] choiceFormats;
-
- /*
- static final long SIGN = 0x8000000000000000L;
- static final long EXPONENT = 0x7FF0000000000000L;
- static final long SIGNIFICAND = 0x000FFFFFFFFFFFFFL;
-
- private static double nextDouble (double d, boolean positive) {
- if (Double.isNaN(d) || Double.isInfinite(d)) {
- return d;
- }
- long bits = Double.doubleToLongBits(d);
- long significand = bits & SIGNIFICAND;
- if (bits < 0) {
- significand |= (SIGN | EXPONENT);
- }
- long exponent = bits & EXPONENT;
- if (positive) {
- significand += 1;
- // FIXME fix overflow & underflow
- } else {
- significand -= 1;
- // FIXME fix overflow & underflow
- }
- bits = exponent | (significand & ~EXPONENT);
- return Double.longBitsToDouble(bits);
- }
- */
-
- static final long SIGN = 0x8000000000000000L;
- static final long EXPONENT = 0x7FF0000000000000L;
- static final long POSITIVEINFINITY = 0x7FF0000000000000L;
-
- /**
- * Finds the least double greater than d (if positive == true),
- * or the greatest double less than d (if positive == false).
- * If NaN, returns same value.
- *
- * Does not affect floating-point flags,
- * provided these member functions do not:
- * Double.longBitsToDouble(long)
- * Double.doubleToLongBits(double)
- * Double.isNaN(double)
- */
- public static double nextDouble (double d, boolean positive) {
-
- /* filter out NaN's */
- if (Double.isNaN(d)) {
- return d;
- }
-
- /* zero's are also a special case */
- if (d == 0.0) {
- double smallestPositiveDouble = Double.longBitsToDouble(1L);
- if (positive) {
- return smallestPositiveDouble;
- } else {
- return -smallestPositiveDouble;
- }
- }
-
- /* if entering here, d is a nonzero value */
-
- /* hold all bits in a long for later use */
- long bits = Double.doubleToLongBits(d);
-
- /* strip off the sign bit */
- long magnitude = bits & ~SIGN;
-
- /* if next double away from zero, increase magnitude */
- if ((bits > 0) == positive) {
- if (magnitude != POSITIVEINFINITY) {
- magnitude += 1;
- }
- }
- /* else decrease magnitude */
- else {
- magnitude -= 1;
- }
-
- /* restore sign bit and return */
- long signbit = bits & SIGN;
- return Double.longBitsToDouble (magnitude | signbit);
- }
-
- }
-