1/* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package java.text; 19 20import java.util.ArrayList; 21import java.util.Arrays; 22import java.util.List; 23import java.util.Locale; 24 25/** 26 * Returns a fixed string based on a numeric value. The class can be used in 27 * conjunction with the {@link MessageFormat} class to handle plurals in 28 * messages. {@code ChoiceFormat} enables users to attach a format to a range of 29 * numbers. The choice is specified with an ascending list of doubles, where 30 * each item specifies a half-open interval up to the next item as in the 31 * following: X matches j if and only if {@code limit[j] <= X < limit[j+1]}. 32 * <p> 33 * If there is no match, then either the first or last index is used. The first 34 * or last index is used depending on whether the number is too low or too high. 35 * The length of the format array must be the same as the length of the limits 36 * array. 37 * <h5>Examples:</h5> 38 * <blockquote> 39 * 40 * <pre> 41 * double[] limits = {1, 2, 3, 4, 5, 6, 7}; 42 * String[] fmts = {"Sun", "Mon", "Tue", "Wed", "Thur", "Fri", "Sat"}; 43 * 44 * double[] limits2 = {0, 1, ChoiceFormat.nextDouble(1)}; 45 * String[] fmts2 = {"no files", "one file", "many files"}; 46 * </pre> 47 * </blockquote> 48 * <p> 49 * ChoiceFormat.nextDouble(double) allows to get the double following the one 50 * passed to the method. This is used to create half open intervals. 51 * <p> 52 * {@code ChoiceFormat} objects also may be converted to and from patterns. 53 * The conversion can be done programmatically, as in the example above, or 54 * by using a pattern like the following: 55 * <blockquote> 56 * 57 * <pre> 58 * "1#Sun|2#Mon|3#Tue|4#Wed|5#Thur|6#Fri|7#Sat" 59 * "0#are no files|1#is one file|1<are many files" 60 * </pre> 61 * 62 * </blockquote> 63 * <p> 64 * where: 65 * <ul> 66 * <li><number>"#"</number> specifies an inclusive limit value;</li> 67 * <li><number>"<"</number> specifies an exclusive limit value.</li> 68 * </ul> 69 */ 70public class ChoiceFormat extends NumberFormat { 71 72 private static final long serialVersionUID = 1795184449645032964L; 73 74 private double[] choiceLimits; 75 76 private String[] choiceFormats; 77 78 /** 79 * Constructs a new {@code ChoiceFormat} with the specified double values 80 * and associated strings. When calling 81 * {@link #format(double, StringBuffer, FieldPosition) format} with a double 82 * value {@code d}, then the element {@code i} in {@code formats} is 83 * selected where {@code i} fulfills {@code limits[i] <= d < limits[i+1]}. 84 * <p> 85 * The length of the {@code limits} and {@code formats} arrays must be the 86 * same. 87 * 88 * @param limits 89 * an array of doubles in ascending order. The lowest and highest 90 * possible values are negative and positive infinity. 91 * @param formats 92 * the strings associated with the ranges defined through {@code 93 * limits}. The lower bound of the associated range is at the 94 * same index as the string. 95 */ 96 public ChoiceFormat(double[] limits, String[] formats) { 97 setChoices(limits, formats); 98 } 99 100 /** 101 * Constructs a new {@code ChoiceFormat} with the strings and limits parsed 102 * from the specified pattern. 103 * 104 * @param template 105 * the pattern of strings and ranges. 106 * @throws IllegalArgumentException 107 * if an error occurs while parsing the pattern. 108 */ 109 public ChoiceFormat(String template) { 110 applyPattern(template); 111 } 112 113 /** 114 * Parses the pattern to determine new strings and ranges for this 115 * {@code ChoiceFormat}. 116 * 117 * @param template 118 * the pattern of strings and ranges. 119 * @throws IllegalArgumentException 120 * if an error occurs while parsing the pattern. 121 */ 122 public void applyPattern(String template) { 123 double[] limits = new double[5]; 124 List<String> formats = new ArrayList<String>(); 125 int length = template.length(), limitCount = 0, index = 0; 126 StringBuffer buffer = new StringBuffer(); 127 NumberFormat format = NumberFormat.getInstance(Locale.US); 128 ParsePosition position = new ParsePosition(0); 129 while (true) { 130 index = skipWhitespace(template, index); 131 if (index >= length) { 132 if (limitCount == limits.length) { 133 choiceLimits = limits; 134 } else { 135 choiceLimits = new double[limitCount]; 136 System.arraycopy(limits, 0, choiceLimits, 0, limitCount); 137 } 138 choiceFormats = new String[formats.size()]; 139 for (int i = 0; i < formats.size(); i++) { 140 choiceFormats[i] = formats.get(i); 141 } 142 return; 143 } 144 145 position.setIndex(index); 146 Number value = format.parse(template, position); 147 index = skipWhitespace(template, position.getIndex()); 148 if (position.getErrorIndex() != -1 || index >= length) { 149 // Fix Harmony 540 150 choiceLimits = new double[0]; 151 choiceFormats = new String[0]; 152 return; 153 } 154 char ch = template.charAt(index++); 155 if (limitCount == limits.length) { 156 double[] newLimits = new double[limitCount * 2]; 157 System.arraycopy(limits, 0, newLimits, 0, limitCount); 158 limits = newLimits; 159 } 160 double next; 161 switch (ch) { 162 case '#': 163 case '\u2264': 164 next = value.doubleValue(); 165 break; 166 case '<': 167 next = nextDouble(value.doubleValue()); 168 break; 169 default: 170 throw new IllegalArgumentException(); 171 } 172 if (limitCount > 0 && next <= limits[limitCount - 1]) { 173 throw new IllegalArgumentException(); 174 } 175 buffer.setLength(0); 176 position.setIndex(index); 177 upTo(template, position, buffer, '|'); 178 index = position.getIndex(); 179 limits[limitCount++] = next; 180 formats.add(buffer.toString()); 181 } 182 } 183 184 /** 185 * Returns a new instance of {@code ChoiceFormat} with the same ranges and 186 * strings as this {@code ChoiceFormat}. 187 * 188 * @return a shallow copy of this {@code ChoiceFormat}. 189 * 190 * @see java.lang.Cloneable 191 */ 192 @Override 193 public Object clone() { 194 ChoiceFormat clone = (ChoiceFormat) super.clone(); 195 clone.choiceLimits = choiceLimits.clone(); 196 clone.choiceFormats = choiceFormats.clone(); 197 return clone; 198 } 199 200 /** 201 * Compares the specified object with this {@code ChoiceFormat}. The object 202 * must be an instance of {@code ChoiceFormat} and have the same limits and 203 * formats to be equal to this instance. 204 * 205 * @param object 206 * the object to compare with this instance. 207 * @return {@code true} if the specified object is equal to this instance; 208 * {@code false} otherwise. 209 * @see #hashCode 210 */ 211 @Override 212 public boolean equals(Object object) { 213 if (this == object) { 214 return true; 215 } 216 if (!(object instanceof ChoiceFormat)) { 217 return false; 218 } 219 ChoiceFormat choice = (ChoiceFormat) object; 220 return Arrays.equals(choiceLimits, choice.choiceLimits) 221 && Arrays.equals(choiceFormats, choice.choiceFormats); 222 } 223 224 /** 225 * Appends the string associated with the range in which the specified 226 * double value fits to the specified string buffer. 227 * 228 * @param value 229 * the double to format. 230 * @param buffer 231 * the target string buffer to append the formatted value to. 232 * @param field 233 * a {@code FieldPosition} which is ignored. 234 * @return the string buffer. 235 */ 236 @Override 237 public StringBuffer format(double value, StringBuffer buffer, 238 FieldPosition field) { 239 for (int i = choiceLimits.length - 1; i >= 0; i--) { 240 if (choiceLimits[i] <= value) { 241 return buffer.append(choiceFormats[i]); 242 } 243 } 244 return choiceFormats.length == 0 ? buffer : buffer 245 .append(choiceFormats[0]); 246 } 247 248 /** 249 * Appends the string associated with the range in which the specified long 250 * value fits to the specified string buffer. 251 * 252 * @param value 253 * the long to format. 254 * @param buffer 255 * the target string buffer to append the formatted value to. 256 * @param field 257 * a {@code FieldPosition} which is ignored. 258 * @return the string buffer. 259 */ 260 @Override 261 public StringBuffer format(long value, StringBuffer buffer, 262 FieldPosition field) { 263 return format((double) value, buffer, field); 264 } 265 266 /** 267 * Returns the strings associated with the ranges of this {@code 268 * ChoiceFormat}. 269 * 270 * @return an array of format strings. 271 */ 272 public Object[] getFormats() { 273 return choiceFormats; 274 } 275 276 /** 277 * Returns the limits of this {@code ChoiceFormat}. 278 * 279 * @return the array of doubles which make up the limits of this {@code 280 * ChoiceFormat}. 281 */ 282 public double[] getLimits() { 283 return choiceLimits; 284 } 285 286 /** 287 * Returns an integer hash code for the receiver. Objects which are equal 288 * return the same value for this method. 289 * 290 * @return the receiver's hash. 291 * 292 * @see #equals 293 */ 294 @Override 295 public int hashCode() { 296 int hashCode = 0; 297 for (int i = 0; i < choiceLimits.length; i++) { 298 long v = Double.doubleToLongBits(choiceLimits[i]); 299 hashCode += (int) (v ^ (v >>> 32)) + choiceFormats[i].hashCode(); 300 } 301 return hashCode; 302 } 303 304 /** 305 * Returns the double value which is closest to the specified double but 306 * larger. 307 * 308 * @param value 309 * a double value. 310 * @return the next larger double value. 311 */ 312 public static final double nextDouble(double value) { 313 if (value == Double.POSITIVE_INFINITY) { 314 return value; 315 } 316 long bits; 317 // Handle -0.0 318 if (value == 0) { 319 bits = 0; 320 } else { 321 bits = Double.doubleToLongBits(value); 322 } 323 return Double.longBitsToDouble(value < 0 ? bits - 1 : bits + 1); 324 } 325 326 /** 327 * Returns the double value which is closest to the specified double but 328 * either larger or smaller as specified. 329 * 330 * @param value 331 * a double value. 332 * @param increment 333 * {@code true} to get the next larger value, {@code false} to 334 * get the previous smaller value. 335 * @return the next larger or smaller double value. 336 */ 337 public static double nextDouble(double value, boolean increment) { 338 return increment ? nextDouble(value) : previousDouble(value); 339 } 340 341 /** 342 * Parses a double from the specified string starting at the index specified 343 * by {@code position}. The string is compared to the strings of this 344 * {@code ChoiceFormat} and if a match occurs then the lower bound of the 345 * corresponding range in the limits array is returned. If the string is 346 * successfully parsed then the index of the {@code ParsePosition} passed to 347 * this method is updated to the index following the parsed text. 348 * <p> 349 * If one of the format strings of this {@code ChoiceFormat} instance is 350 * found in {@code string} starting at {@code position.getIndex()} then 351 * <ul> 352 * <li>the index in {@code position} is set to the index following the 353 * parsed text; 354 * <li>the {@link java.lang.Double Double} corresponding to the format 355 * string is returned.</li> 356 * </ul> 357 * <p> 358 * If none of the format strings is found in {@code string} then 359 * <ul> 360 * <li>the error index in {@code position} is set to the current index in 361 * {@code position};</li> 362 * <li> {@link java.lang.Double#NaN Double.NaN} is returned. 363 * </ul> 364 * @param string 365 * the source string to parse. 366 * @param position 367 * input/output parameter, specifies the start index in {@code 368 * string} from where to start parsing. See the <em>Returns</em> 369 * section for a description of the output values. 370 * @return a Double resulting from the parse, or Double.NaN if there is an 371 * error 372 */ 373 @Override 374 public Number parse(String string, ParsePosition position) { 375 int offset = position.getIndex(); 376 for (int i = 0; i < choiceFormats.length; i++) { 377 if (string.startsWith(choiceFormats[i], offset)) { 378 position.setIndex(offset + choiceFormats[i].length()); 379 return new Double(choiceLimits[i]); 380 } 381 } 382 position.setErrorIndex(offset); 383 return new Double(Double.NaN); 384 } 385 386 /** 387 * Returns the double value which is closest to the specified double but 388 * smaller. 389 * 390 * @param value 391 * a double value. 392 * @return the next smaller double value. 393 */ 394 public static final double previousDouble(double value) { 395 if (value == Double.NEGATIVE_INFINITY) { 396 return value; 397 } 398 long bits; 399 // Handle 0.0 400 if (value == 0) { 401 bits = 0x8000000000000000L; 402 } else { 403 bits = Double.doubleToLongBits(value); 404 } 405 return Double.longBitsToDouble(value <= 0 ? bits + 1 : bits - 1); 406 } 407 408 /** 409 * Sets the double values and associated strings of this ChoiceFormat. When 410 * calling {@link #format(double, StringBuffer, FieldPosition) format} with 411 * a double value {@code d}, then the element {@code i} in {@code formats} 412 * is selected where {@code i} fulfills 413 * {@code limits[i] <= d < limits[i+1]}. 414 * <p> 415 * The length of the {@code limits} and {@code formats} arrays must be the 416 * same. 417 * 418 * @param limits 419 * an array of doubles in ascending order. The lowest and highest 420 * possible values are negative and positive infinity. 421 * @param formats 422 * the strings associated with the ranges defined through {@code 423 * limits}. The lower bound of the associated range is at the 424 * same index as the string. 425 */ 426 public void setChoices(double[] limits, String[] formats) { 427 if (limits.length != formats.length) { 428 throw new IllegalArgumentException(); 429 } 430 choiceLimits = limits; 431 choiceFormats = formats; 432 } 433 434 private int skipWhitespace(String string, int index) { 435 int length = string.length(); 436 while (index < length && Character.isWhitespace(string.charAt(index))) { 437 index++; 438 } 439 return index; 440 } 441 442 /** 443 * Returns the pattern of this {@code ChoiceFormat} which specifies the 444 * ranges and their associated strings. 445 * 446 * @return the pattern. 447 */ 448 public String toPattern() { 449 StringBuilder buffer = new StringBuilder(); 450 for (int i = 0; i < choiceLimits.length; i++) { 451 if (i != 0) { 452 buffer.append('|'); 453 } 454 String previous = String.valueOf(previousDouble(choiceLimits[i])); 455 String limit = String.valueOf(choiceLimits[i]); 456 if (previous.length() < limit.length()) { 457 buffer.append(previous); 458 buffer.append('<'); 459 } else { 460 buffer.append(limit); 461 buffer.append('#'); 462 } 463 boolean quote = (choiceFormats[i].indexOf('|') != -1); 464 if (quote) { 465 buffer.append('\''); 466 } 467 buffer.append(choiceFormats[i]); 468 if (quote) { 469 buffer.append('\''); 470 } 471 } 472 return buffer.toString(); 473 } 474} 475