ChoiceFormat.java 15.1 KB
Newer Older
1
/* ChoiceFormat.java -- Format over a range of numbers
2
   Copyright (C) 1998, 1999, 2000, 2001, 2002, 2004, 2005
3
   Free Software Foundation, Inc.
Tom Tromey committed
4

5
This file is part of GNU Classpath.
Tom Tromey committed
6

7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
GNU Classpath is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
 
GNU Classpath is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Classpath; see the file COPYING.  If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.

22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.

As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module.  An independent module is a module which is not derived from
or based on this library.  If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so.  If you do not wish to do so, delete this
exception statement from your version. */
Tom Tromey committed
38 39 40 41 42 43 44


package java.text;

import java.util.Vector;

/**
45 46 47 48
 * This class allows a format to be specified based on a range of numbers.
 * To use this class, first specify two lists of formats and range terminators.
 * These lists must be arrays of equal length.  The format of index 
 * <code>i</code> will be selected for value <code>X</code> if 
49
 * <code>terminator[i] &lt;= X &lt; limit[i + 1]</code>.  If the value X is not
50 51 52 53 54 55
 * included in any range, then either the first or last format will be 
 * used depending on whether the value X falls outside the range.
 * <p>
 * This sounds complicated, but that is because I did a poor job of
 * explaining it.  Consider the following example:
 * <p>
56 57 58 59
 *
<pre>terminators = { 1, ChoiceFormat.nextDouble(1) }
formats = { "file", "files" }</pre>
 *
60 61 62 63 64 65 66 67 68 69 70 71 72 73
 * <p>
 * In this case if the actual number tested is one or less, then the word
 * "file" is used as the format value.  If the number tested is greater than
 * one, then "files" is used.  This allows plurals to be handled
 * gracefully.  Note the use of the method <code>nextDouble</code>.  This
 * method selects the next highest double number than its argument.  This
 * effectively makes any double greater than 1.0 cause the "files" string
 * to be selected.  (Note that all terminator values are specified as
 * doubles.
 * <p>
 * Note that in order for this class to work properly, the range terminator
 * array must be sorted in ascending order and the format string array
 * must be the same length as the terminator array.
 *
74
 * @author Tom Tromey (tromey@cygnus.com)
75
 * @author Aaron M. Renn (arenn@urbanophile.com)
Tom Tromey committed
76 77 78 79 80 81 82 83
 * @date March 9, 1999
 */
/* Written using "Java Class Libraries", 2nd edition, plus online
 * API docs for JDK 1.2 from http://www.javasoft.com.
 * Status:  Believed complete and correct to 1.1.
 */
public class ChoiceFormat extends NumberFormat
{
84 85 86 87 88 89 90 91 92
  /**
   * This method sets new range terminators and format strings for this
   * object based on the specified pattern. This pattern is of the form 
   * "term#string|term#string...".  For example "1#Sunday|2#Monday|#Tuesday".
   *
   * @param pattern The pattern of terminators and format strings.
   *
   * @exception IllegalArgumentException If the pattern is not valid
   */
Tom Tromey committed
93
  public void applyPattern (String newPattern)
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
  {
    // Note: we assume the same kind of quoting rules apply here.
    // This isn't explicitly documented.  But for instance we accept
    // '#' as a literal hash in a format string.
    int index = 0, max = newPattern.length();
    Vector stringVec = new Vector ();
    Vector limitVec = new Vector ();
    StringBuffer buf = new StringBuffer ();
    
    while (true)
      {
	// Find end of double.
	int dstart = index;
	while (index < max)
	  {
	    char c = newPattern.charAt(index);
	    if (c == '#' || c == '\u2064' || c == '<')
	      break;
	    ++index;
	  }
	
	if (index == max)
	  throw new IllegalArgumentException ("unexpected end of text");
	Double d = new Double (newPattern.substring(dstart, index));

	if (newPattern.charAt(index) == '<')
	  d = new Double (nextDouble (d.doubleValue()));

	limitVec.addElement(d);

	// Scan text.
	++index;
	buf.setLength(0);
	while (index < max)
	  {
	    char c = newPattern.charAt(index);
	    if (c == '\'' && index < max + 1
		&& newPattern.charAt(index + 1) == '\'')
	      {
Tom Tromey committed
133
		buf.append(c);
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
		++index;
	      }
	    else if (c == '\'' && index < max + 2)
	      {
		buf.append(newPattern.charAt(index + 1));
		index += 2;
	      }
	    else if (c == '|')
	      break;
	    else
	      buf.append(c);
	    ++index;
	  }

	stringVec.addElement(buf.toString());
	if (index == max)
	  break;
	++index;
      }

    choiceFormats = new String[stringVec.size()];
    stringVec.copyInto(choiceFormats);

    choiceLimits = new double[limitVec.size()];
    for (int i = 0; i < choiceLimits.length; ++i)
      {
	Double d = (Double) limitVec.elementAt(i);
	choiceLimits[i] = d.doubleValue();
      }
  }

  /**
   * This method initializes a new instance of <code>ChoiceFormat</code> that
   * generates its range terminator and format string arrays from the
   * specified pattern.  This pattern is of the form 
   * "term#string|term#string...".  For example "1#Sunday|2#Monday|#Tuesday".
   * This is the same pattern type used by the <code>applyPattern</code>
   * method.
   *
   * @param pattern The pattern of terminators and format strings.
   *
   * @exception IllegalArgumentException If the pattern is not valid
   */
Tom Tromey committed
177
  public ChoiceFormat (String newPattern)
178 179 180 181 182 183 184 185 186 187 188 189
  {
    super ();
    applyPattern (newPattern);
  }

  /**
   * This method initializes a new instance of <code>ChoiceFormat</code> that
   * will use the specified range terminators and format strings.
   *
   * @param choiceLimits The array of range terminators
   * @param choiceFormats The array of format strings
   */
190
  public ChoiceFormat (double[] choiceLimits, String[] choiceFormats)
191 192 193 194 195 196 197 198 199
  {
    super ();
    setChoices (choiceLimits, choiceFormats);
  }

  /**
   * This method tests this object for equality with the specified 
   * object.  This will be true if and only if:
   * <ul>
200 201
   * <li>The specified object is not <code>null</code>.</li>
   * <li>The specified object is an instance of <code>ChoiceFormat</code>.</li>
202
   * <li>The termination ranges and format strings are identical to
203
   *     this object's. </li>
204 205 206 207 208 209 210
   * </ul>
   *
   * @param obj The object to test for equality against.
   *
   * @return <code>true</code> if the specified object is equal to
   * this one, <code>false</code> otherwise. 
   */
Tom Tromey committed
211
  public boolean equals (Object obj)
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
  {
    if (! (obj instanceof ChoiceFormat))
      return false;
    ChoiceFormat cf = (ChoiceFormat) obj;
    if (choiceLimits.length != cf.choiceLimits.length)
      return false;
    for (int i = choiceLimits.length - 1; i >= 0; --i)
      {
	if (choiceLimits[i] != cf.choiceLimits[i]
	    || !choiceFormats[i].equals(cf.choiceFormats[i]))
	  return false;
      }
    return true;
  }

  /**
   * This method appends the appropriate format string to the specified
   * <code>StringBuffer</code> based on the supplied <code>long</code>
   * argument.
   *
   * @param number The number used for determine (based on the range
   *               terminators) which format string to append. 
   * @param sb The <code>StringBuffer</code> to append the format string to.
   * @param status Unused.
   *
   * @return The <code>StringBuffer</code> with the format string appended.
   */
Tom Tromey committed
239 240
  public StringBuffer format (long num, StringBuffer appendBuf,
			      FieldPosition pos)
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
  {
    return format ((double) num, appendBuf, pos);
  }

  /**
   * This method appends the appropriate format string to the specified
   * <code>StringBuffer</code> based on the supplied <code>double</code>
   * argument.
   *
   * @param number The number used for determine (based on the range
   *               terminators) which format string to append. 
   * @param sb The <code>StringBuffer</code> to append the format string to.
   * @param status Unused.
   *
   * @return The <code>StringBuffer</code> with the format string appended.
   */
Tom Tromey committed
257 258
  public StringBuffer format (double num, StringBuffer appendBuf,
			      FieldPosition pos)
259 260 261 262
  {
    if (choiceLimits.length == 0)
      return appendBuf;

263
    int index = 0;
264 265 266 267
    if (! Double.isNaN(num) && num >= choiceLimits[0])
      {
	for (; index < choiceLimits.length - 1; ++index)
	  {
268
	    if (choiceLimits[index] <= num && num < choiceLimits[index + 1])
269 270 271 272 273 274 275 276 277 278 279 280
	      break;
	  }
      }

    return appendBuf.append(choiceFormats[index]);
  }

  /**
   * This method returns the list of format strings in use.
   *
   * @return The list of format objects.
   */
Tom Tromey committed
281
  public Object[] getFormats ()
282 283 284 285 286 287 288 289 290
  {
    return (Object[]) choiceFormats.clone();
  }

  /**
   * This method returns the list of range terminators in use.
   *
   * @return The list of range terminators.
   */
Tom Tromey committed
291
  public double[] getLimits ()
292 293 294 295 296 297 298 299 300
  {
    return (double[]) choiceLimits.clone();
  }

  /**
   * This method returns a hash value for this object
   * 
   * @return A hash value for this object.
   */
Tom Tromey committed
301
  public int hashCode ()
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
  {
    int hash = 0;
    for (int i = 0; i < choiceLimits.length; ++i)
      {
	long v = Double.doubleToLongBits(choiceLimits[i]);
	hash ^= (v ^ (v >>> 32));
	hash ^= choiceFormats[i].hashCode();
      }
    return hash;
  }

  /**
   * This method returns the lowest possible double greater than the 
   * specified double.  If the specified double value is equal to
   * <code>Double.NaN</code> then that is the value returned.
   *
   * @param d The specified double
   *
   * @return The lowest double value greater than the specified double.
   */
Tom Tromey committed
322
  public static final double nextDouble (double d)
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
  {
    return nextDouble (d, true);
  }

  /**
   * This method returns a double that is either the next highest double
   * or next lowest double compared to the specified double depending on the
   * value of the passed boolean parameter.  If the boolean parameter is
   * <code>true</code>, then the lowest possible double greater than the 
   * specified double will be returned.  Otherwise the highest possible
   * double less than the specified double will be returned.
   *
   * @param d The specified double
   * @param positive <code>true</code> to return the next highest
   *                 double, <code>false</code> otherwise. 
   *
   * @return The next highest or lowest double value.
   */
341
  public static double nextDouble (double d, boolean next)
342 343 344
  {
    if (Double.isInfinite(d) || Double.isNaN(d))
      return d;
Tom Tromey committed
345

346
    long bits = Double.doubleToLongBits(d);
Tom Tromey committed
347

348 349
    long mantMask = (1L << mantissaBits) - 1;
    long mantissa = bits & mantMask;
Tom Tromey committed
350

351 352
    long expMask = (1L << exponentBits) - 1;
    long exponent = (bits >>> mantissaBits) & expMask;
Tom Tromey committed
353

354 355 356 357 358 359
    if (next ^ (bits < 0)) // Increment magnitude
      {
	if (mantissa == (1L << mantissaBits) - 1)
	  {
	    mantissa = 0L;
	    exponent++;
Tom Tromey committed
360
	     
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397
	    // Check for absolute overflow.
	    if (exponent >= (1L << mantissaBits))
	      return (bits > 0) ? Double.POSITIVE_INFINITY 
		: Double.NEGATIVE_INFINITY;		      
	  }
	else
	  mantissa++;
      }
    else // Decrement magnitude
      {
	if (exponent == 0L && mantissa == 0L)
	  {
	    // The only case where there is a change of sign
	    return next ? Double.MIN_VALUE : -Double.MIN_VALUE;
	  }
	else
	  {
	    if (mantissa == 0L)
	      {
		mantissa = (1L << mantissaBits) - 1;
		exponent--;
	      }
	    else
	      mantissa--;
	  }
      }

    long result = bits < 0 ? 1 : 0;
    result = (result << exponentBits) | exponent;
    result = (result << mantissaBits) | mantissa;
    return Double.longBitsToDouble(result);
  }

  /**
   * I'm not sure what this method is really supposed to do, as it is
   * not documented.
   */
Tom Tromey committed
398
  public Number parse (String sourceStr, ParsePosition pos)
399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421
  {
    int index = pos.getIndex();
    for (int i = 0; i < choiceLimits.length; ++i)
      {
	if (sourceStr.startsWith(choiceFormats[i], index))
	  {
	    pos.setIndex(index + choiceFormats[i].length());
	    return new Double (choiceLimits[i]);
	  }
      }
    pos.setErrorIndex(index);
    return new Double (Double.NaN);
  }

  /**
   * This method returns the highest possible double less than the 
   * specified double.  If the specified double value is equal to
   * <code>Double.NaN</code> then that is the value returned.
   *
   * @param d The specified double
   *
   * @return The highest double value less than the specified double.
   */
Tom Tromey committed
422
  public static final double previousDouble (double d)
423 424 425 426 427 428 429 430 431 432 433
  {
    return nextDouble (d, false);
  }

  /**
   * This method sets new range terminators and format strings for this
   * object.
   *
   * @param choiceLimits The new range terminators
   * @param choiceFormats The new choice formats
   */
434
  public void setChoices (double[] choiceLimits, String[] choiceFormats)
435 436 437 438 439 440 441 442
  {
    if (choiceLimits == null || choiceFormats == null)
      throw new NullPointerException ();
    if (choiceLimits.length != choiceFormats.length)
      throw new IllegalArgumentException ();
    this.choiceFormats = (String[]) choiceFormats.clone();
    this.choiceLimits = (double[]) choiceLimits.clone();
  }
Tom Tromey committed
443

444
  private void quoteString (StringBuffer dest, String text)
445 446 447 448 449 450 451
  {
    int max = text.length();
    for (int i = 0; i < max; ++i)
      {
	char c = text.charAt(i);
	if (c == '\'')
	  {
Tom Tromey committed
452
	    dest.append(c);
453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472
	    dest.append(c);
	  }
	else if (c == '#' || c == '|' || c == '\u2064' || c == '<')
	  {
	    dest.append('\'');
	    dest.append(c);
	    dest.append('\'');
	  }
	else
	  dest.append(c);
      }
  }

  /**
   * This method returns the range terminator list and format string list
   * as a <code>String</code> suitable for using with the 
   * <code>applyPattern</code> method.
   *
   * @return A pattern string for this object
   */
Tom Tromey committed
473
  public String toPattern ()
474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
  {
    StringBuffer result = new StringBuffer ();
    for (int i = 0; i < choiceLimits.length; ++i)
      {
	result.append(choiceLimits[i]);
	result.append('#');
	quoteString (result, choiceFormats[i]);
      }
    return result.toString();
  }

  /**
   * This is the list of format strings.  Note that this variable is
   * specified by the serialization spec of this class.
   */
489
  private String[] choiceFormats;
490 491 492 493 494

  /**
   * This is the list of range terminator values.  Note that this variable is
   * specified by the serialization spec of this class.
   */
495
  private double[] choiceLimits;
Tom Tromey committed
496 497 498 499 500

  // Number of mantissa bits in double.
  private static final int mantissaBits = 52;
  // Number of exponent bits in a double.
  private static final int exponentBits = 11;
501 502

  private static final long serialVersionUID = 1795184449645032964L;
Tom Tromey committed
503
}