1 /* GENERATED SOURCE. DO NOT MODIFY. */ 2 // 2017 and later: Unicode, Inc. and others. 3 // License & terms of use: http://www.unicode.org/copyright.html#License 4 package android.icu.impl.number; 5 6 import java.text.AttributedCharacterIterator; 7 import java.text.AttributedString; 8 import java.text.FieldPosition; 9 import java.util.Arrays; 10 import java.util.HashMap; 11 import java.util.Map; 12 13 import android.icu.text.NumberFormat; 14 import android.icu.text.NumberFormat.Field; 15 16 /** 17 * A StringBuilder optimized for number formatting. It implements the following key features beyond a normal JDK 18 * StringBuilder: 19 * 20 * <ol> 21 * <li>Efficient prepend as well as append. 22 * <li>Keeps tracks of Fields in an efficient manner. 23 * <li>String operations are fast-pathed to code point operations when possible. 24 * </ol> 25 * @hide Only a subset of ICU is exposed in Android 26 */ 27 public class NumberStringBuilder implements CharSequence { 28 29 /** A constant, empty NumberStringBuilder. Do NOT call mutative operations on this. */ 30 public static final NumberStringBuilder EMPTY = new NumberStringBuilder(); 31 32 private char[] chars; 33 private Field[] fields; 34 private int zero; 35 private int length; 36 37 public NumberStringBuilder() { 38 this(40); 39 } 40 41 public NumberStringBuilder(int capacity) { 42 chars = new char[capacity]; 43 fields = new Field[capacity]; 44 zero = capacity / 2; 45 length = 0; 46 } 47 48 public NumberStringBuilder(NumberStringBuilder source) { 49 copyFrom(source); 50 } 51 52 public void copyFrom(NumberStringBuilder source) { 53 chars = Arrays.copyOf(source.chars, source.chars.length); 54 fields = Arrays.copyOf(source.fields, source.fields.length); 55 zero = source.zero; 56 length = source.length; 57 } 58 59 @Override 60 public int length() { 61 return length; 62 } 63 64 public int codePointCount() { 65 return Character.codePointCount(this, 0, length()); 66 } 67 68 @Override 69 public char charAt(int index) { 70 assert index >= 0; 71 assert index < length; 72 return chars[zero + index]; 73 } 74 75 public Field fieldAt(int index) { 76 assert index >= 0; 77 assert index < length; 78 return fields[zero + index]; 79 } 80 81 public int getFirstCodePoint() { 82 if (length == 0) { 83 return -1; 84 } 85 return Character.codePointAt(chars, zero, zero + length); 86 } 87 88 public int getLastCodePoint() { 89 if (length == 0) { 90 return -1; 91 } 92 return Character.codePointBefore(chars, zero + length, zero); 93 } 94 95 public int codePointAt(int index) { 96 return Character.codePointAt(chars, zero + index, zero + length); 97 } 98 99 public int codePointBefore(int index) { 100 return Character.codePointBefore(chars, zero + index, zero); 101 } 102 103 public NumberStringBuilder clear() { 104 zero = getCapacity() / 2; 105 length = 0; 106 return this; 107 } 108 109 /** 110 * Appends the specified codePoint to the end of the string. 111 * 112 * @return The number of chars added: 1 if the code point is in the BMP, or 2 otherwise. 113 */ 114 public int appendCodePoint(int codePoint, Field field) { 115 return insertCodePoint(length, codePoint, field); 116 } 117 118 /** 119 * Inserts the specified codePoint at the specified index in the string. 120 * 121 * @return The number of chars added: 1 if the code point is in the BMP, or 2 otherwise. 122 */ 123 public int insertCodePoint(int index, int codePoint, Field field) { 124 int count = Character.charCount(codePoint); 125 int position = prepareForInsert(index, count); 126 Character.toChars(codePoint, chars, position); 127 fields[position] = field; 128 if (count == 2) 129 fields[position + 1] = field; 130 return count; 131 } 132 133 /** 134 * Appends the specified CharSequence to the end of the string. 135 * 136 * @return The number of chars added, which is the length of CharSequence. 137 */ 138 public int append(CharSequence sequence, Field field) { 139 return insert(length, sequence, field); 140 } 141 142 /** 143 * Inserts the specified CharSequence at the specified index in the string. 144 * 145 * @return The number of chars added, which is the length of CharSequence. 146 */ 147 public int insert(int index, CharSequence sequence, Field field) { 148 if (sequence.length() == 0) { 149 // Nothing to insert. 150 return 0; 151 } else if (sequence.length() == 1) { 152 // Fast path: on a single-char string, using insertCodePoint below is 70% faster than the 153 // CharSequence method: 12.2 ns versus 41.9 ns for five operations on my Linux x86-64. 154 return insertCodePoint(index, sequence.charAt(0), field); 155 } else { 156 return insert(index, sequence, 0, sequence.length(), field); 157 } 158 } 159 160 /** 161 * Inserts the specified CharSequence at the specified index in the string, reading from the CharSequence from start 162 * (inclusive) to end (exclusive). 163 * 164 * @return The number of chars added, which is the length of CharSequence. 165 */ 166 public int insert(int index, CharSequence sequence, int start, int end, Field field) { 167 int count = end - start; 168 int position = prepareForInsert(index, count); 169 for (int i = 0; i < count; i++) { 170 chars[position + i] = sequence.charAt(start + i); 171 fields[position + i] = field; 172 } 173 return count; 174 } 175 176 /** 177 * Appends the chars in the specified char array to the end of the string, and associates them with the fields in 178 * the specified field array, which must have the same length as chars. 179 * 180 * @return The number of chars added, which is the length of the char array. 181 */ 182 public int append(char[] chars, Field[] fields) { 183 return insert(length, chars, fields); 184 } 185 186 /** 187 * Inserts the chars in the specified char array at the specified index in the string, and associates them with the 188 * fields in the specified field array, which must have the same length as chars. 189 * 190 * @return The number of chars added, which is the length of the char array. 191 */ 192 public int insert(int index, char[] chars, Field[] fields) { 193 assert fields == null || chars.length == fields.length; 194 int count = chars.length; 195 if (count == 0) 196 return 0; // nothing to insert 197 int position = prepareForInsert(index, count); 198 for (int i = 0; i < count; i++) { 199 this.chars[position + i] = chars[i]; 200 this.fields[position + i] = fields == null ? null : fields[i]; 201 } 202 return count; 203 } 204 205 /** 206 * Appends the contents of another {@link NumberStringBuilder} to the end of this instance. 207 * 208 * @return The number of chars added, which is the length of the other {@link NumberStringBuilder}. 209 */ 210 public int append(NumberStringBuilder other) { 211 return insert(length, other); 212 } 213 214 /** 215 * Inserts the contents of another {@link NumberStringBuilder} into this instance at the given index. 216 * 217 * @return The number of chars added, which is the length of the other {@link NumberStringBuilder}. 218 */ 219 public int insert(int index, NumberStringBuilder other) { 220 if (this == other) { 221 throw new IllegalArgumentException("Cannot call insert/append on myself"); 222 } 223 int count = other.length; 224 if (count == 0) { 225 // Nothing to insert. 226 return 0; 227 } 228 int position = prepareForInsert(index, count); 229 for (int i = 0; i < count; i++) { 230 this.chars[position + i] = other.charAt(i); 231 this.fields[position + i] = other.fieldAt(i); 232 } 233 return count; 234 } 235 236 /** 237 * Shifts around existing data if necessary to make room for new characters. 238 * 239 * @param index 240 * The location in the string where the operation is to take place. 241 * @param count 242 * The number of chars (UTF-16 code units) to be inserted at that location. 243 * @return The position in the char array to insert the chars. 244 */ 245 private int prepareForInsert(int index, int count) { 246 if (index == 0 && zero - count >= 0) { 247 // Append to start 248 zero -= count; 249 length += count; 250 return zero; 251 } else if (index == length && zero + length + count < getCapacity()) { 252 // Append to end 253 length += count; 254 return zero + length - count; 255 } else { 256 // Move chars around and/or allocate more space 257 return prepareForInsertHelper(index, count); 258 } 259 } 260 261 private int prepareForInsertHelper(int index, int count) { 262 // Java note: Keeping this code out of prepareForInsert() increases the speed of append operations. 263 int oldCapacity = getCapacity(); 264 int oldZero = zero; 265 char[] oldChars = chars; 266 Field[] oldFields = fields; 267 if (length + count > oldCapacity) { 268 int newCapacity = (length + count) * 2; 269 int newZero = newCapacity / 2 - (length + count) / 2; 270 271 char[] newChars = new char[newCapacity]; 272 Field[] newFields = new Field[newCapacity]; 273 274 // First copy the prefix and then the suffix, leaving room for the new chars that the 275 // caller wants to insert. 276 System.arraycopy(oldChars, oldZero, newChars, newZero, index); 277 System.arraycopy(oldChars, oldZero + index, newChars, newZero + index + count, length - index); 278 System.arraycopy(oldFields, oldZero, newFields, newZero, index); 279 System.arraycopy(oldFields, oldZero + index, newFields, newZero + index + count, length - index); 280 281 chars = newChars; 282 fields = newFields; 283 zero = newZero; 284 length += count; 285 } else { 286 int newZero = oldCapacity / 2 - (length + count) / 2; 287 288 // First copy the entire string to the location of the prefix, and then move the suffix 289 // to make room for the new chars that the caller wants to insert. 290 System.arraycopy(oldChars, oldZero, oldChars, newZero, length); 291 System.arraycopy(oldChars, newZero + index, oldChars, newZero + index + count, length - index); 292 System.arraycopy(oldFields, oldZero, oldFields, newZero, length); 293 System.arraycopy(oldFields, newZero + index, oldFields, newZero + index + count, length - index); 294 295 zero = newZero; 296 length += count; 297 } 298 return zero + index; 299 } 300 301 private int getCapacity() { 302 return chars.length; 303 } 304 305 @Override 306 public CharSequence subSequence(int start, int end) { 307 if (start < 0 || end > length || end < start) { 308 throw new IndexOutOfBoundsException(); 309 } 310 NumberStringBuilder other = new NumberStringBuilder(this); 311 other.zero = zero + start; 312 other.length = end - start; 313 return other; 314 } 315 316 /** 317 * Returns the string represented by the characters in this string builder. 318 * 319 * <p> 320 * For a string intended be used for debugging, use {@link #toDebugString}. 321 */ 322 @Override 323 public String toString() { 324 return new String(chars, zero, length); 325 } 326 327 private static final Map<Field, Character> fieldToDebugChar = new HashMap<Field, Character>(); 328 329 static { 330 fieldToDebugChar.put(NumberFormat.Field.SIGN, '-'); 331 fieldToDebugChar.put(NumberFormat.Field.INTEGER, 'i'); 332 fieldToDebugChar.put(NumberFormat.Field.FRACTION, 'f'); 333 fieldToDebugChar.put(NumberFormat.Field.EXPONENT, 'e'); 334 fieldToDebugChar.put(NumberFormat.Field.EXPONENT_SIGN, '+'); 335 fieldToDebugChar.put(NumberFormat.Field.EXPONENT_SYMBOL, 'E'); 336 fieldToDebugChar.put(NumberFormat.Field.DECIMAL_SEPARATOR, '.'); 337 fieldToDebugChar.put(NumberFormat.Field.GROUPING_SEPARATOR, ','); 338 fieldToDebugChar.put(NumberFormat.Field.PERCENT, '%'); 339 fieldToDebugChar.put(NumberFormat.Field.PERMILLE, ''); 340 fieldToDebugChar.put(NumberFormat.Field.CURRENCY, '$'); 341 } 342 343 /** 344 * Returns a string that includes field information, for debugging purposes. 345 * 346 * <p> 347 * For example, if the string is "-12.345", the debug string will be something like "<NumberStringBuilder 348 * [-123.45] [-iii.ff]>" 349 * 350 * @return A string for debugging purposes. 351 */ 352 public String toDebugString() { 353 StringBuilder sb = new StringBuilder(); 354 sb.append("<NumberStringBuilder ["); 355 sb.append(this.toString()); 356 sb.append("] ["); 357 for (int i = zero; i < zero + length; i++) { 358 if (fields[i] == null) { 359 sb.append('n'); 360 } else { 361 sb.append(fieldToDebugChar.get(fields[i])); 362 } 363 } 364 sb.append("]>"); 365 return sb.toString(); 366 } 367 368 /** @return A new array containing the contents of this string builder. */ 369 public char[] toCharArray() { 370 return Arrays.copyOfRange(chars, zero, zero + length); 371 } 372 373 /** @return A new array containing the field values of this string builder. */ 374 public Field[] toFieldArray() { 375 return Arrays.copyOfRange(fields, zero, zero + length); 376 } 377 378 /** 379 * @return Whether the contents and field values of this string builder are equal to the given chars and fields. 380 * @see #toCharArray 381 * @see #toFieldArray 382 */ 383 public boolean contentEquals(char[] chars, Field[] fields) { 384 if (chars.length != length) 385 return false; 386 if (fields.length != length) 387 return false; 388 for (int i = 0; i < length; i++) { 389 if (this.chars[zero + i] != chars[i]) 390 return false; 391 if (this.fields[zero + i] != fields[i]) 392 return false; 393 } 394 return true; 395 } 396 397 /** 398 * @param other 399 * The instance to compare. 400 * @return Whether the contents of this instance is currently equal to the given instance. 401 */ 402 public boolean contentEquals(NumberStringBuilder other) { 403 if (length != other.length) 404 return false; 405 for (int i = 0; i < length; i++) { 406 if (charAt(i) != other.charAt(i) || fieldAt(i) != other.fieldAt(i)) { 407 return false; 408 } 409 } 410 return true; 411 } 412 413 @Override 414 public int hashCode() { 415 throw new UnsupportedOperationException("Don't call #hashCode() or #equals() on a mutable."); 416 } 417 418 @Override 419 public boolean equals(Object other) { 420 throw new UnsupportedOperationException("Don't call #hashCode() or #equals() on a mutable."); 421 } 422 423 /** 424 * Populates the given {@link FieldPosition} based on this string builder. 425 * 426 * @param fp 427 * The FieldPosition to populate. 428 * @param offset 429 * An offset to add to the field position index; can be zero. 430 */ 431 public void populateFieldPosition(FieldPosition fp, int offset) { 432 java.text.Format.Field rawField = fp.getFieldAttribute(); 433 434 if (rawField == null) { 435 // Backwards compatibility: read from fp.getField() 436 if (fp.getField() == NumberFormat.INTEGER_FIELD) { 437 rawField = NumberFormat.Field.INTEGER; 438 } else if (fp.getField() == NumberFormat.FRACTION_FIELD) { 439 rawField = NumberFormat.Field.FRACTION; 440 } else { 441 // No field is set 442 return; 443 } 444 } 445 446 if (!(rawField instanceof android.icu.text.NumberFormat.Field)) { 447 throw new IllegalArgumentException( 448 "You must pass an instance of android.icu.text.NumberFormat.Field as your FieldPosition attribute. You passed: " 449 + rawField.getClass().toString()); 450 } 451 452 /* android.icu.text.NumberFormat. */ Field field = (Field) rawField; 453 454 boolean seenStart = false; 455 int fractionStart = -1; 456 for (int i = zero; i <= zero + length; i++) { 457 Field _field = (i < zero + length) ? fields[i] : null; 458 if (seenStart && field != _field) { 459 // Special case: GROUPING_SEPARATOR counts as an INTEGER. 460 if (field == NumberFormat.Field.INTEGER && _field == NumberFormat.Field.GROUPING_SEPARATOR) { 461 continue; 462 } 463 fp.setEndIndex(i - zero + offset); 464 break; 465 } else if (!seenStart && field == _field) { 466 fp.setBeginIndex(i - zero + offset); 467 seenStart = true; 468 } 469 if (_field == NumberFormat.Field.INTEGER || _field == NumberFormat.Field.DECIMAL_SEPARATOR) { 470 fractionStart = i - zero + 1; 471 } 472 } 473 474 // Backwards compatibility: FRACTION needs to start after INTEGER if empty 475 if (field == NumberFormat.Field.FRACTION && !seenStart) { 476 fp.setBeginIndex(fractionStart + offset); 477 fp.setEndIndex(fractionStart + offset); 478 } 479 } 480 481 public AttributedCharacterIterator getIterator() { 482 AttributedString as = new AttributedString(toString()); 483 Field current = null; 484 int currentStart = -1; 485 for (int i = 0; i < length; i++) { 486 Field field = fields[i + zero]; 487 if (current == NumberFormat.Field.INTEGER && field == NumberFormat.Field.GROUPING_SEPARATOR) { 488 // Special case: GROUPING_SEPARATOR counts as an INTEGER. 489 as.addAttribute(NumberFormat.Field.GROUPING_SEPARATOR, NumberFormat.Field.GROUPING_SEPARATOR, i, i + 1); 490 } else if (current != field) { 491 if (current != null) { 492 as.addAttribute(current, current, currentStart, i); 493 } 494 current = field; 495 currentStart = i; 496 } 497 } 498 if (current != null) { 499 as.addAttribute(current, current, currentStart, length); 500 } 501 502 return as.getIterator(); 503 } 504 } 505