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 ohos.global.icu.impl; 5 6 import java.util.Arrays; 7 import java.util.HashMap; 8 import java.util.Map; 9 10 // NumberFormat is imported only for the toDebugString() implementation. 11 import ohos.global.icu.text.NumberFormat; 12 13 /** 14 * A StringBuilder optimized for formatting. It implements the following key features beyond a 15 * normal JDK StringBuilder: 16 * 17 * <ol> 18 * <li>Efficient prepend as well as append. 19 * <li>Keeps tracks of Fields in an efficient manner. 20 * <li>String operations are fast-pathed to code point operations when possible. 21 * </ol> 22 * 23 * See also FormattedValueStringBuilderImpl. 24 * 25 * @author sffc (Shane Carr) 26 * @hide exposed on OHOS 27 */ 28 public class FormattedStringBuilder implements CharSequence, Appendable { 29 30 /** A constant, empty FormattedStringBuilder. Do NOT call mutative operations on this. */ 31 public static final FormattedStringBuilder EMPTY = new FormattedStringBuilder(); 32 33 char[] chars; 34 Object[] fields; 35 int zero; 36 int length; 37 38 /** Number of characters from the end where .append() operations insert. */ 39 int appendOffset = 0; 40 41 /** Field applied when Appendable methods are used. */ 42 Object appendableField = null; 43 FormattedStringBuilder()44 public FormattedStringBuilder() { 45 this(40); 46 } 47 FormattedStringBuilder(int capacity)48 public FormattedStringBuilder(int capacity) { 49 chars = new char[capacity]; 50 fields = new Object[capacity]; 51 zero = capacity / 2; 52 length = 0; 53 } 54 FormattedStringBuilder(FormattedStringBuilder source)55 public FormattedStringBuilder(FormattedStringBuilder source) { 56 copyFrom(source); 57 } 58 copyFrom(FormattedStringBuilder source)59 public void copyFrom(FormattedStringBuilder source) { 60 chars = Arrays.copyOf(source.chars, source.chars.length); 61 fields = Arrays.copyOf(source.fields, source.fields.length); 62 zero = source.zero; 63 length = source.length; 64 } 65 66 @Override length()67 public int length() { 68 return length; 69 } 70 codePointCount()71 public int codePointCount() { 72 return Character.codePointCount(this, 0, length()); 73 } 74 75 @Override charAt(int index)76 public char charAt(int index) { 77 assert index >= 0; 78 assert index < length; 79 return chars[zero + index]; 80 } 81 82 public Object fieldAt(int index) { 83 assert index >= 0; 84 assert index < length; 85 return fields[zero + index]; 86 } 87 88 public int getFirstCodePoint() { 89 if (length == 0) { 90 return -1; 91 } 92 return Character.codePointAt(chars, zero, zero + length); 93 } 94 95 public int getLastCodePoint() { 96 if (length == 0) { 97 return -1; 98 } 99 return Character.codePointBefore(chars, zero + length, zero); 100 } 101 102 public int codePointAt(int index) { 103 return Character.codePointAt(chars, zero + index, zero + length); 104 } 105 106 public int codePointBefore(int index) { 107 return Character.codePointBefore(chars, zero + index, zero); 108 } 109 110 public FormattedStringBuilder clear() { 111 zero = getCapacity() / 2; 112 length = 0; 113 return this; 114 } 115 116 /** 117 * Sets the index at which append operations insert. Defaults to the end. 118 * 119 * @param index The index at which append operations should insert. 120 */ 121 public void setAppendIndex(int index) { 122 appendOffset = length - index; 123 } 124 125 public int appendChar16(char codeUnit, Object field) { 126 return insertChar16(length - appendOffset, codeUnit, field); 127 } 128 129 public int insertChar16(int index, char codeUnit, Object field) { 130 int count = 1; 131 int position = prepareForInsert(index, count); 132 chars[position] = codeUnit; 133 fields[position] = field; 134 return count; 135 } 136 137 /** 138 * Appends the specified codePoint to the end of the string. 139 * 140 * @return The number of chars added: 1 if the code point is in the BMP, or 2 otherwise. 141 */ 142 public int appendCodePoint(int codePoint, Object field) { 143 return insertCodePoint(length - appendOffset, codePoint, field); 144 } 145 146 /** 147 * Inserts the specified codePoint at the specified index in the string. 148 * 149 * @return The number of chars added: 1 if the code point is in the BMP, or 2 otherwise. 150 */ 151 public int insertCodePoint(int index, int codePoint, Object field) { 152 int count = Character.charCount(codePoint); 153 int position = prepareForInsert(index, count); 154 Character.toChars(codePoint, chars, position); 155 fields[position] = field; 156 if (count == 2) 157 fields[position + 1] = field; 158 return count; 159 } 160 161 /** 162 * Appends the specified CharSequence to the end of the string. 163 * 164 * @return The number of chars added, which is the length of CharSequence. 165 */ 166 public int append(CharSequence sequence, Object field) { 167 return insert(length - appendOffset, sequence, field); 168 } 169 170 /** 171 * Inserts the specified CharSequence at the specified index in the string. 172 * 173 * @return The number of chars added, which is the length of CharSequence. 174 */ 175 public int insert(int index, CharSequence sequence, Object field) { 176 if (sequence.length() == 0) { 177 // Nothing to insert. 178 return 0; 179 } else if (sequence.length() == 1) { 180 // Fast path: on a single-char string, using insertCodePoint below is 70% faster than the 181 // CharSequence method: 12.2 ns versus 41.9 ns for five operations on my Linux x86-64. 182 return insertCodePoint(index, sequence.charAt(0), field); 183 } else { 184 return insert(index, sequence, 0, sequence.length(), field); 185 } 186 } 187 188 /** 189 * Inserts the specified CharSequence at the specified index in the string, reading from the 190 * CharSequence from start (inclusive) to end (exclusive). 191 * 192 * @return The number of chars added, which is the length of CharSequence. 193 */ 194 public int insert(int index, CharSequence sequence, int start, int end, Object field) { 195 int count = end - start; 196 int position = prepareForInsert(index, count); 197 for (int i = 0; i < count; i++) { 198 chars[position + i] = sequence.charAt(start + i); 199 fields[position + i] = field; 200 } 201 return count; 202 } 203 204 /** 205 * Replaces the chars between startThis and endThis with the chars between startOther and endOther of 206 * the given CharSequence. Calling this method with startThis == endThis is equivalent to calling 207 * insert. 208 * 209 * @return The number of chars added, which may be negative if the removed segment is longer than the 210 * length of the CharSequence segment that was inserted. 211 */ 212 public int splice( 213 int startThis, 214 int endThis, 215 CharSequence sequence, 216 int startOther, 217 int endOther, 218 Object field) { 219 int thisLength = endThis - startThis; 220 int otherLength = endOther - startOther; 221 int count = otherLength - thisLength; 222 int position; 223 if (count > 0) { 224 // Overall, chars need to be added. 225 position = prepareForInsert(startThis, count); 226 } else { 227 // Overall, chars need to be removed or kept the same. 228 position = remove(startThis, -count); 229 } 230 for (int i = 0; i < otherLength; i++) { 231 chars[position + i] = sequence.charAt(startOther + i); 232 fields[position + i] = field; 233 } 234 return count; 235 } 236 237 /** 238 * Appends the chars in the specified char array to the end of the string, and associates them with 239 * the fields in the specified field array, which must have the same length as chars. 240 * 241 * @return The number of chars added, which is the length of the char array. 242 */ 243 public int append(char[] chars, Object[] fields) { 244 return insert(length - appendOffset, chars, fields); 245 } 246 247 /** 248 * Inserts the chars in the specified char array at the specified index in the string, and associates 249 * them with the fields in the specified field array, which must have the same length as chars. 250 * 251 * @return The number of chars added, which is the length of the char array. 252 */ 253 public int insert(int index, char[] chars, Object[] fields) { 254 assert fields == null || chars.length == fields.length; 255 int count = chars.length; 256 if (count == 0) 257 return 0; // nothing to insert 258 int position = prepareForInsert(index, count); 259 for (int i = 0; i < count; i++) { 260 this.chars[position + i] = chars[i]; 261 this.fields[position + i] = fields == null ? null : fields[i]; 262 } 263 return count; 264 } 265 266 /** 267 * Appends the contents of another {@link FormattedStringBuilder} to the end of this instance. 268 * 269 * @return The number of chars added, which is the length of the other {@link FormattedStringBuilder}. 270 */ 271 public int append(FormattedStringBuilder other) { 272 return insert(length - appendOffset, other); 273 } 274 275 /** 276 * Inserts the contents of another {@link FormattedStringBuilder} into this instance at the given index. 277 * 278 * @return The number of chars added, which is the length of the other {@link FormattedStringBuilder}. 279 */ 280 public int insert(int index, FormattedStringBuilder other) { 281 if (this == other) { 282 throw new IllegalArgumentException("Cannot call insert/append on myself"); 283 } 284 int count = other.length; 285 if (count == 0) { 286 // Nothing to insert. 287 return 0; 288 } 289 int position = prepareForInsert(index, count); 290 for (int i = 0; i < count; i++) { 291 this.chars[position + i] = other.charAt(i); 292 this.fields[position + i] = other.fieldAt(i); 293 } 294 return count; 295 } 296 297 /** 298 * Shifts around existing data if necessary to make room for new characters. 299 * 300 * @param index 301 * The location in the string where the operation is to take place. 302 * @param count 303 * The number of chars (UTF-16 code units) to be inserted at that location. 304 * @return The position in the char array to insert the chars. 305 */ 306 private int prepareForInsert(int index, int count) { 307 if (index == -1) { 308 index = length; 309 } 310 if (index == 0 && zero - count >= 0) { 311 // Append to start 312 zero -= count; 313 length += count; 314 return zero; 315 } else if (index == length && zero + length + count < getCapacity()) { 316 // Append to end 317 length += count; 318 return zero + length - count; 319 } else { 320 // Move chars around and/or allocate more space 321 return prepareForInsertHelper(index, count); 322 } 323 } 324 325 private int prepareForInsertHelper(int index, int count) { 326 // Java note: Keeping this code out of prepareForInsert() increases the speed of append 327 // operations. 328 int oldCapacity = getCapacity(); 329 int oldZero = zero; 330 char[] oldChars = chars; 331 Object[] oldFields = fields; 332 if (length + count > oldCapacity) { 333 int newCapacity = (length + count) * 2; 334 int newZero = newCapacity / 2 - (length + count) / 2; 335 336 char[] newChars = new char[newCapacity]; 337 Object[] newFields = new Object[newCapacity]; 338 339 // First copy the prefix and then the suffix, leaving room for the new chars that the 340 // caller wants to insert. 341 System.arraycopy(oldChars, oldZero, newChars, newZero, index); 342 System.arraycopy(oldChars, 343 oldZero + index, 344 newChars, 345 newZero + index + count, 346 length - index); 347 System.arraycopy(oldFields, oldZero, newFields, newZero, index); 348 System.arraycopy(oldFields, 349 oldZero + index, 350 newFields, 351 newZero + index + count, 352 length - index); 353 354 chars = newChars; 355 fields = newFields; 356 zero = newZero; 357 length += count; 358 } else { 359 int newZero = oldCapacity / 2 - (length + count) / 2; 360 361 // First copy the entire string to the location of the prefix, and then move the suffix 362 // to make room for the new chars that the caller wants to insert. 363 System.arraycopy(oldChars, oldZero, oldChars, newZero, length); 364 System.arraycopy(oldChars, 365 newZero + index, 366 oldChars, 367 newZero + index + count, 368 length - index); 369 System.arraycopy(oldFields, oldZero, oldFields, newZero, length); 370 System.arraycopy(oldFields, 371 newZero + index, 372 oldFields, 373 newZero + index + count, 374 length - index); 375 376 zero = newZero; 377 length += count; 378 } 379 return zero + index; 380 } 381 382 /** 383 * Removes the "count" chars starting at "index". Returns the position at which the chars were 384 * removed. 385 */ 386 private int remove(int index, int count) { 387 int position = index + zero; 388 System.arraycopy(chars, position + count, chars, position, length - index - count); 389 System.arraycopy(fields, position + count, fields, position, length - index - count); 390 length -= count; 391 return position; 392 } 393 394 private int getCapacity() { 395 return chars.length; 396 } 397 398 /** Note: this returns a FormattedStringBuilder. Do not return publicly. */ 399 @Override 400 @Deprecated 401 public CharSequence subSequence(int start, int end) { 402 assert start >= 0; 403 assert end <= length; 404 assert end >= start; 405 FormattedStringBuilder other = new FormattedStringBuilder(this); 406 other.zero = zero + start; 407 other.length = end - start; 408 return other; 409 } 410 411 /** Use this instead of subSequence if returning publicly. */ 412 public String subString(int start, int end) { 413 if (start < 0 || end > length || end < start) { 414 throw new IndexOutOfBoundsException(); 415 } 416 return new String(chars, start + zero, end - start); 417 } 418 419 /** 420 * Returns the string represented by the characters in this string builder. 421 * 422 * <p> 423 * For a string intended be used for debugging, use {@link #toDebugString}. 424 */ 425 @Override 426 public String toString() { 427 return new String(chars, zero, length); 428 } 429 430 private static final Map<Object, Character> fieldToDebugChar = new HashMap<>(); 431 432 static { 433 fieldToDebugChar.put(NumberFormat.Field.SIGN, '-'); 434 fieldToDebugChar.put(NumberFormat.Field.INTEGER, 'i'); 435 fieldToDebugChar.put(NumberFormat.Field.FRACTION, 'f'); 436 fieldToDebugChar.put(NumberFormat.Field.EXPONENT, 'e'); 437 fieldToDebugChar.put(NumberFormat.Field.EXPONENT_SIGN, '+'); 438 fieldToDebugChar.put(NumberFormat.Field.EXPONENT_SYMBOL, 'E'); 439 fieldToDebugChar.put(NumberFormat.Field.DECIMAL_SEPARATOR, '.'); 440 fieldToDebugChar.put(NumberFormat.Field.GROUPING_SEPARATOR, ','); 441 fieldToDebugChar.put(NumberFormat.Field.PERCENT, '%'); 442 fieldToDebugChar.put(NumberFormat.Field.PERMILLE, '‰'); 443 fieldToDebugChar.put(NumberFormat.Field.CURRENCY, '$'); 444 fieldToDebugChar.put(NumberFormat.Field.MEASURE_UNIT, 'u'); 445 fieldToDebugChar.put(NumberFormat.Field.COMPACT, 'C'); 446 } 447 448 /** 449 * Returns a string that includes field information, for debugging purposes. 450 * 451 * <p> 452 * For example, if the string is "-12.345", the debug string will be something like 453 * "<FormattedStringBuilder [-123.45] [-iii.ff]>" 454 * 455 * @return A string for debugging purposes. 456 */ 457 public String toDebugString() { 458 StringBuilder sb = new StringBuilder(); 459 sb.append("<FormattedStringBuilder ["); 460 sb.append(this.toString()); 461 sb.append("] ["); 462 for (int i = zero; i < zero + length; i++) { 463 if (fields[i] == null) { 464 sb.append('n'); 465 } else if (fieldToDebugChar.containsKey(fields[i])) { 466 sb.append(fieldToDebugChar.get(fields[i])); 467 } else { 468 sb.append('?'); 469 } 470 } 471 sb.append("]>"); 472 return sb.toString(); 473 } 474 475 /** @return A new array containing the contents of this string builder. */ 476 public char[] toCharArray() { 477 return Arrays.copyOfRange(chars, zero, zero + length); 478 } 479 480 /** @return A new array containing the field values of this string builder. */ 481 public Object[] toFieldArray() { 482 return Arrays.copyOfRange(fields, zero, zero + length); 483 } 484 485 /** 486 * Call this method before using any of the Appendable overrides. 487 * 488 * @param field The field used when inserting strings. 489 */ 490 public void setAppendableField(Object field) { 491 appendableField = field; 492 } 493 494 /** 495 * This method is provided for Java Appendable compatibility. In most cases, please use the append methods that take 496 * a Field parameter. If you do use this method, you must call {@link #setAppendableField} first. 497 */ 498 @Override 499 public Appendable append(CharSequence csq) { 500 assert appendableField != null; 501 insert(length - appendOffset, csq, appendableField); 502 return this; 503 } 504 505 /** 506 * This method is provided for Java Appendable compatibility. In most cases, please use the append methods that take 507 * a Field parameter. If you do use this method, you must call {@link #setAppendableField} first. 508 */ 509 @Override 510 public Appendable append(CharSequence csq, int start, int end) { 511 assert appendableField != null; 512 insert(length - appendOffset, csq, start, end, appendableField); 513 return this; 514 } 515 516 /** 517 * This method is provided for Java Appendable compatibility. In most cases, please use the append methods that take 518 * a Field parameter. If you do use this method, you must call {@link #setAppendableField} first. 519 */ 520 @Override 521 public Appendable append(char c) { 522 assert appendableField != null; 523 insertChar16(length - appendOffset, c, appendableField); 524 return this; 525 } 526 527 /** 528 * @return Whether the contents and field values of this string builder are equal to the given chars 529 * and fields. 530 * @see #toCharArray 531 * @see #toFieldArray 532 */ 533 public boolean contentEquals(char[] chars, Object[] fields) { 534 if (chars.length != length) 535 return false; 536 if (fields.length != length) 537 return false; 538 for (int i = 0; i < length; i++) { 539 if (this.chars[zero + i] != chars[i]) 540 return false; 541 if (this.fields[zero + i] != fields[i]) 542 return false; 543 } 544 return true; 545 } 546 547 /** 548 * @param other 549 * The instance to compare. 550 * @return Whether the contents of this instance is currently equal to the given instance. 551 */ 552 public boolean contentEquals(FormattedStringBuilder other) { 553 if (length != other.length) 554 return false; 555 for (int i = 0; i < length; i++) { 556 if (charAt(i) != other.charAt(i) || fieldAt(i) != other.fieldAt(i)) { 557 return false; 558 } 559 } 560 return true; 561 } 562 563 @Override 564 public int hashCode() { 565 throw new UnsupportedOperationException("Don't call #hashCode() or #equals() on a mutable."); 566 } 567 568 @Override 569 public boolean equals(Object other) { 570 throw new UnsupportedOperationException("Don't call #hashCode() or #equals() on a mutable."); 571 } 572 } 573