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