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