1 /* ChoiceFormat.java -- Format over a range of numbers 2 Copyright (C) 1998, 1999, 2000, 2001, 2002, 2004, 2005 3 Free Software Foundation, Inc. 4 5 This file is part of GNU Classpath. 6 7 GNU Classpath is free software; you can redistribute it and/or modify 8 it under the terms of the GNU General Public License as published by 9 the Free Software Foundation; either version 2, or (at your option) 10 any later version. 11 12 GNU Classpath is distributed in the hope that it will be useful, but 13 WITHOUT ANY WARRANTY; without even the implied warranty of 14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 General Public License for more details. 16 17 You should have received a copy of the GNU General Public License 18 along with GNU Classpath; see the file COPYING. If not, write to the 19 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 20 02110-1301 USA. 21 22 Linking this library statically or dynamically with other modules is 23 making a combined work based on this library. Thus, the terms and 24 conditions of the GNU General Public License cover the whole 25 combination. 26 27 As a special exception, the copyright holders of this library give you 28 permission to link this library with independent modules to produce an 29 executable, regardless of the license terms of these independent 30 modules, and to copy and distribute the resulting executable under 31 terms of your choice, provided that you also meet, for each linked 32 independent module, the terms and conditions of the license of that 33 module. An independent module is a module which is not derived from 34 or based on this library. If you modify this library, you may extend 35 this exception to your version of the library, but you are not 36 obligated to do so. If you do not wish to do so, delete this 37 exception statement from your version. */ 38 39 40 package java.text; 41 42 import java.util.Vector; 43 44 /** 45 * This class allows a format to be specified based on a range of numbers. 46 * To use this class, first specify two lists of formats and range terminators. 47 * These lists must be arrays of equal length. The format of index 48 * <code>i</code> will be selected for value <code>X</code> if 49 * <code>terminator[i] <= X < limit[i + 1]</code>. If the value X is not 50 * included in any range, then either the first or last format will be 51 * used depending on whether the value X falls outside the range. 52 * <p> 53 * This sounds complicated, but that is because I did a poor job of 54 * explaining it. Consider the following example: 55 * <p> 56 * 57 <pre>terminators = { 1, ChoiceFormat.nextDouble(1) } 58 formats = { "file", "files" }</pre> 59 * 60 * <p> 61 * In this case if the actual number tested is one or less, then the word 62 * "file" is used as the format value. If the number tested is greater than 63 * one, then "files" is used. This allows plurals to be handled 64 * gracefully. Note the use of the method <code>nextDouble</code>. This 65 * method selects the next highest double number than its argument. This 66 * effectively makes any double greater than 1.0 cause the "files" string 67 * to be selected. (Note that all terminator values are specified as 68 * doubles. 69 * <p> 70 * Note that in order for this class to work properly, the range terminator 71 * array must be sorted in ascending order and the format string array 72 * must be the same length as the terminator array. 73 * 74 * @author Tom Tromey (tromey@cygnus.com) 75 * @author Aaron M. Renn (arenn@urbanophile.com) 76 * @date March 9, 1999 77 */ 78 /* Written using "Java Class Libraries", 2nd edition, plus online 79 * API docs for JDK 1.2 from http://www.javasoft.com. 80 * Status: Believed complete and correct to 1.1. 81 */ 82 public class ChoiceFormat extends NumberFormat 83 { 84 /** 85 * This method sets new range terminators and format strings for this 86 * object based on the specified pattern. This pattern is of the form 87 * "term#string|term#string...". For example "1#Sunday|2#Monday|#Tuesday". 88 * 89 * @param newPattern The pattern of terminators and format strings. 90 * 91 * @exception IllegalArgumentException If the pattern is not valid 92 */ applyPattern(String newPattern)93 public void applyPattern (String newPattern) 94 { 95 // Note: we assume the same kind of quoting rules apply here. 96 // This isn't explicitly documented. But for instance we accept 97 // '#' as a literal hash in a format string. 98 int index = 0, max = newPattern.length(); 99 Vector stringVec = new Vector (); 100 Vector limitVec = new Vector (); 101 StringBuffer buf = new StringBuffer (); 102 103 while (true) 104 { 105 // Find end of double. 106 int dstart = index; 107 while (index < max) 108 { 109 char c = newPattern.charAt(index); 110 if (c == '#' || c == '\u2064' || c == '<') 111 break; 112 ++index; 113 } 114 115 if (index == max) 116 throw new IllegalArgumentException ("unexpected end of text"); 117 Double d = new Double (newPattern.substring(dstart, index)); 118 119 if (newPattern.charAt(index) == '<') 120 d = new Double (nextDouble (d.doubleValue())); 121 122 limitVec.addElement(d); 123 124 // Scan text. 125 ++index; 126 buf.setLength(0); 127 while (index < max) 128 { 129 char c = newPattern.charAt(index); 130 if (c == '\'' && index < max + 1 131 && newPattern.charAt(index + 1) == '\'') 132 { 133 buf.append(c); 134 ++index; 135 } 136 else if (c == '\'' && index < max + 2) 137 { 138 buf.append(newPattern.charAt(index + 1)); 139 index += 2; 140 } 141 else if (c == '|') 142 break; 143 else 144 buf.append(c); 145 ++index; 146 } 147 148 stringVec.addElement(buf.toString()); 149 if (index == max) 150 break; 151 ++index; 152 } 153 154 choiceFormats = new String[stringVec.size()]; 155 stringVec.copyInto(choiceFormats); 156 157 choiceLimits = new double[limitVec.size()]; 158 for (int i = 0; i < choiceLimits.length; ++i) 159 { 160 Double d = (Double) limitVec.elementAt(i); 161 choiceLimits[i] = d.doubleValue(); 162 } 163 } 164 165 /** 166 * This method initializes a new instance of <code>ChoiceFormat</code> that 167 * generates its range terminator and format string arrays from the 168 * specified pattern. This pattern is of the form 169 * "term#string|term#string...". For example "1#Sunday|2#Monday|#Tuesday". 170 * This is the same pattern type used by the <code>applyPattern</code> 171 * method. 172 * 173 * @param newPattern The pattern of terminators and format strings. 174 * 175 * @exception IllegalArgumentException If the pattern is not valid 176 */ ChoiceFormat(String newPattern)177 public ChoiceFormat (String newPattern) 178 { 179 super (); 180 applyPattern (newPattern); 181 } 182 183 /** 184 * This method initializes a new instance of <code>ChoiceFormat</code> that 185 * will use the specified range terminators and format strings. 186 * 187 * @param choiceLimits The array of range terminators 188 * @param choiceFormats The array of format strings 189 */ ChoiceFormat(double[] choiceLimits, String[] choiceFormats)190 public ChoiceFormat (double[] choiceLimits, String[] choiceFormats) 191 { 192 super (); 193 setChoices (choiceLimits, choiceFormats); 194 } 195 196 /** 197 * This method tests this object for equality with the specified 198 * object. This will be true if and only if: 199 * <ul> 200 * <li>The specified object is not <code>null</code>.</li> 201 * <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 * </ul> 205 * 206 * @param obj The object to test for equality against. 207 * 208 * @return <code>true</code> if the specified object is equal to 209 * this one, <code>false</code> otherwise. 210 */ equals(Object obj)211 public boolean equals (Object obj) 212 { 213 if (! (obj instanceof ChoiceFormat)) 214 return false; 215 ChoiceFormat cf = (ChoiceFormat) obj; 216 if (choiceLimits.length != cf.choiceLimits.length) 217 return false; 218 for (int i = choiceLimits.length - 1; i >= 0; --i) 219 { 220 if (choiceLimits[i] != cf.choiceLimits[i] 221 || !choiceFormats[i].equals(cf.choiceFormats[i])) 222 return false; 223 } 224 return true; 225 } 226 227 /** 228 * This method appends the appropriate format string to the specified 229 * <code>StringBuffer</code> based on the supplied <code>long</code> 230 * argument. 231 * 232 * @param num The number used for determine (based on the range 233 * terminators) which format string to append. 234 * @param appendBuf The <code>StringBuffer</code> to append the format string 235 * to. 236 * @param pos Unused. 237 * 238 * @return The <code>StringBuffer</code> with the format string appended. 239 */ format(long num, StringBuffer appendBuf, FieldPosition pos)240 public StringBuffer format (long num, StringBuffer appendBuf, 241 FieldPosition pos) 242 { 243 return format ((double) num, appendBuf, pos); 244 } 245 246 /** 247 * This method appends the appropriate format string to the specified 248 * <code>StringBuffer</code> based on the supplied <code>double</code> 249 * argument. 250 * 251 * @param num The number used for determine (based on the range 252 * terminators) which format string to append. 253 * @param appendBuf The <code>StringBuffer</code> to append the format string to. 254 * @param pos Unused. 255 * 256 * @return The <code>StringBuffer</code> with the format string appended. 257 */ format(double num, StringBuffer appendBuf, FieldPosition pos)258 public StringBuffer format (double num, StringBuffer appendBuf, 259 FieldPosition pos) 260 { 261 if (choiceLimits.length == 0) 262 return appendBuf; 263 264 int index = 0; 265 if (! Double.isNaN(num) && num >= choiceLimits[0]) 266 { 267 for (; index < choiceLimits.length - 1; ++index) 268 { 269 if (choiceLimits[index] <= num && num < choiceLimits[index + 1]) 270 break; 271 } 272 } 273 274 return appendBuf.append(choiceFormats[index]); 275 } 276 277 /** 278 * This method returns the list of format strings in use. 279 * 280 * @return The list of format objects. 281 */ getFormats()282 public Object[] getFormats () 283 { 284 return (Object[]) choiceFormats.clone(); 285 } 286 287 /** 288 * This method returns the list of range terminators in use. 289 * 290 * @return The list of range terminators. 291 */ getLimits()292 public double[] getLimits () 293 { 294 return (double[]) choiceLimits.clone(); 295 } 296 297 /** 298 * This method returns a hash value for this object 299 * 300 * @return A hash value for this object. 301 */ hashCode()302 public int hashCode () 303 { 304 int hash = 0; 305 for (int i = 0; i < choiceLimits.length; ++i) 306 { 307 long v = Double.doubleToLongBits(choiceLimits[i]); 308 hash ^= (v ^ (v >>> 32)); 309 hash ^= choiceFormats[i].hashCode(); 310 } 311 return hash; 312 } 313 314 /** 315 * This method returns the lowest possible double greater than the 316 * specified double. If the specified double value is equal to 317 * <code>Double.NaN</code> then that is the value returned. 318 * 319 * @param d The specified double 320 * 321 * @return The lowest double value greater than the specified double. 322 */ nextDouble(double d)323 public static final double nextDouble (double d) 324 { 325 return nextDouble (d, true); 326 } 327 328 /** 329 * This method returns a double that is either the next highest double 330 * or next lowest double compared to the specified double depending on the 331 * value of the passed boolean parameter. If the boolean parameter is 332 * <code>true</code>, then the lowest possible double greater than the 333 * specified double will be returned. Otherwise the highest possible 334 * double less than the specified double will be returned. 335 * 336 * @param d The specified double 337 * @param next <code>true</code> to return the next highest 338 * double, <code>false</code> otherwise. 339 * 340 * @return The next highest or lowest double value. 341 */ nextDouble(double d, boolean next)342 public static double nextDouble (double d, boolean next) 343 { 344 if (Double.isInfinite(d) || Double.isNaN(d)) 345 return d; 346 347 long bits = Double.doubleToLongBits(d); 348 349 long mantMask = (1L << mantissaBits) - 1; 350 long mantissa = bits & mantMask; 351 352 long expMask = (1L << exponentBits) - 1; 353 long exponent = (bits >>> mantissaBits) & expMask; 354 355 if (next ^ (bits < 0)) // Increment magnitude 356 { 357 if (mantissa == (1L << mantissaBits) - 1) 358 { 359 mantissa = 0L; 360 exponent++; 361 362 // Check for absolute overflow. 363 if (exponent >= (1L << mantissaBits)) 364 return (bits > 0) ? Double.POSITIVE_INFINITY 365 : Double.NEGATIVE_INFINITY; 366 } 367 else 368 mantissa++; 369 } 370 else // Decrement magnitude 371 { 372 if (exponent == 0L && mantissa == 0L) 373 { 374 // The only case where there is a change of sign 375 return next ? Double.MIN_VALUE : -Double.MIN_VALUE; 376 } 377 else 378 { 379 if (mantissa == 0L) 380 { 381 mantissa = (1L << mantissaBits) - 1; 382 exponent--; 383 } 384 else 385 mantissa--; 386 } 387 } 388 389 long result = bits < 0 ? 1 : 0; 390 result = (result << exponentBits) | exponent; 391 result = (result << mantissaBits) | mantissa; 392 return Double.longBitsToDouble(result); 393 } 394 395 /** 396 * I'm not sure what this method is really supposed to do, as it is 397 * not documented. 398 */ 399 public Number parse (String sourceStr, ParsePosition pos) 400 { 401 int index = pos.getIndex(); 402 for (int i = 0; i < choiceLimits.length; ++i) 403 { 404 if (sourceStr.startsWith(choiceFormats[i], index)) 405 { 406 pos.setIndex(index + choiceFormats[i].length()); 407 return new Double (choiceLimits[i]); 408 } 409 } 410 pos.setErrorIndex(index); 411 return new Double (Double.NaN); 412 } 413 414 /** 415 * This method returns the highest possible double less than the 416 * specified double. If the specified double value is equal to 417 * <code>Double.NaN</code> then that is the value returned. 418 * 419 * @param d The specified double 420 * 421 * @return The highest double value less than the specified double. 422 */ 423 public static final double previousDouble (double d) 424 { 425 return nextDouble (d, false); 426 } 427 428 /** 429 * This method sets new range terminators and format strings for this 430 * object. 431 * 432 * @param choiceLimits The new range terminators 433 * @param choiceFormats The new choice formats 434 */ 435 public void setChoices (double[] choiceLimits, String[] choiceFormats) 436 { 437 if (choiceLimits == null || choiceFormats == null) 438 throw new NullPointerException (); 439 if (choiceLimits.length != choiceFormats.length) 440 throw new IllegalArgumentException (); 441 this.choiceFormats = (String[]) choiceFormats.clone(); 442 this.choiceLimits = (double[]) choiceLimits.clone(); 443 } 444 445 private void quoteString (StringBuffer dest, String text) 446 { 447 int max = text.length(); 448 for (int i = 0; i < max; ++i) 449 { 450 char c = text.charAt(i); 451 if (c == '\'') 452 { 453 dest.append(c); 454 dest.append(c); 455 } 456 else if (c == '#' || c == '|' || c == '\u2064' || c == '<') 457 { 458 dest.append('\''); 459 dest.append(c); 460 dest.append('\''); 461 } 462 else 463 dest.append(c); 464 } 465 } 466 467 /** 468 * This method returns the range terminator list and format string list 469 * as a <code>String</code> suitable for using with the 470 * <code>applyPattern</code> method. 471 * 472 * @return A pattern string for this object 473 */ 474 public String toPattern () 475 { 476 StringBuffer result = new StringBuffer (); 477 for (int i = 0; i < choiceLimits.length; ++i) 478 { 479 result.append(choiceLimits[i]); 480 result.append('#'); 481 quoteString (result, choiceFormats[i]); 482 } 483 return result.toString(); 484 } 485 486 /** 487 * This is the list of format strings. Note that this variable is 488 * specified by the serialization spec of this class. 489 */ 490 private String[] choiceFormats; 491 492 /** 493 * This is the list of range terminator values. Note that this variable is 494 * specified by the serialization spec of this class. 495 */ 496 private double[] choiceLimits; 497 498 // Number of mantissa bits in double. 499 private static final int mantissaBits = 52; 500 // Number of exponent bits in a double. 501 private static final int exponentBits = 11; 502 503 private static final long serialVersionUID = 1795184449645032964L; 504 } 505