LEFT | RIGHT |
(no file at all) | |
1 // © 2017 and later: Unicode, Inc. and others. | 1 // © 2017 and later: Unicode, Inc. and others. |
2 // License & terms of use: http://www.unicode.org/copyright.html#License | 2 // License & terms of use: http://www.unicode.org/copyright.html#License |
3 package com.ibm.icu.impl.number; | 3 package com.ibm.icu.impl.number; |
4 | 4 |
5 import java.text.AttributedCharacterIterator; | 5 import java.text.AttributedCharacterIterator; |
6 import java.text.AttributedString; | 6 import java.text.AttributedString; |
7 import java.text.FieldPosition; | 7 import java.text.FieldPosition; |
8 import java.util.Arrays; | 8 import java.util.Arrays; |
9 import java.util.HashMap; | 9 import java.util.HashMap; |
10 import java.util.Map; | 10 import java.util.Map; |
11 | 11 |
12 import com.ibm.icu.text.NumberFormat; | 12 import com.ibm.icu.text.NumberFormat; |
13 import com.ibm.icu.text.NumberFormat.Field; | 13 import com.ibm.icu.text.NumberFormat.Field; |
14 | 14 |
15 /** | 15 /** |
16 * A StringBuilder optimized for number formatting. It implements the following
key features beyond a normal JDK | 16 * A StringBuilder optimized for number formatting. It implements the following
key features beyond a |
17 * StringBuilder: | 17 * normal JDK StringBuilder: |
18 * | 18 * |
19 * <ol> | 19 * <ol> |
20 * <li>Efficient prepend as well as append. | 20 * <li>Efficient prepend as well as append. |
21 * <li>Keeps tracks of Fields in an efficient manner. | 21 * <li>Keeps tracks of Fields in an efficient manner. |
22 * <li>String operations are fast-pathed to code point operations when possible. | 22 * <li>String operations are fast-pathed to code point operations when possible. |
23 * </ol> | 23 * </ol> |
24 */ | 24 */ |
25 public class NumberStringBuilder implements CharSequence { | 25 public class NumberStringBuilder implements CharSequence { |
26 | 26 |
27 /** A constant, empty NumberStringBuilder. Do NOT call mutative operations o
n this. */ | 27 /** A constant, empty NumberStringBuilder. Do NOT call mutative operations o
n this. */ |
(...skipping 121 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
149 } else if (sequence.length() == 1) { | 149 } else if (sequence.length() == 1) { |
150 // Fast path: on a single-char string, using insertCodePoint below i
s 70% faster than the | 150 // Fast path: on a single-char string, using insertCodePoint below i
s 70% faster than the |
151 // CharSequence method: 12.2 ns versus 41.9 ns for five operations o
n my Linux x86-64. | 151 // CharSequence method: 12.2 ns versus 41.9 ns for five operations o
n my Linux x86-64. |
152 return insertCodePoint(index, sequence.charAt(0), field); | 152 return insertCodePoint(index, sequence.charAt(0), field); |
153 } else { | 153 } else { |
154 return insert(index, sequence, 0, sequence.length(), field); | 154 return insert(index, sequence, 0, sequence.length(), field); |
155 } | 155 } |
156 } | 156 } |
157 | 157 |
158 /** | 158 /** |
159 * Inserts the specified CharSequence at the specified index in the string,
reading from the CharSequence from start | 159 * Inserts the specified CharSequence at the specified index in the string,
reading from the |
160 * (inclusive) to end (exclusive). | 160 * CharSequence from start (inclusive) to end (exclusive). |
161 * | 161 * |
162 * @return The number of chars added, which is the length of CharSequence. | 162 * @return The number of chars added, which is the length of CharSequence. |
163 */ | 163 */ |
164 public int insert(int index, CharSequence sequence, int start, int end, Fiel
d field) { | 164 public int insert(int index, CharSequence sequence, int start, int end, Fiel
d field) { |
165 int count = end - start; | 165 int count = end - start; |
166 int position = prepareForInsert(index, count); | 166 int position = prepareForInsert(index, count); |
167 for (int i = 0; i < count; i++) { | 167 for (int i = 0; i < count; i++) { |
168 chars[position + i] = sequence.charAt(start + i); | 168 chars[position + i] = sequence.charAt(start + i); |
169 fields[position + i] = field; | 169 fields[position + i] = field; |
170 } | 170 } |
171 return count; | 171 return count; |
172 } | 172 } |
173 | 173 |
174 /** | 174 /** |
175 * Appends the chars in the specified char array to the end of the string, a
nd associates them with the fields in | 175 * Appends the chars in the specified char array to the end of the string, a
nd associates them with |
176 * the specified field array, which must have the same length as chars. | 176 * the fields in the specified field array, which must have the same length
as chars. |
177 * | 177 * |
178 * @return The number of chars added, which is the length of the char array. | 178 * @return The number of chars added, which is the length of the char array. |
179 */ | 179 */ |
180 public int append(char[] chars, Field[] fields) { | 180 public int append(char[] chars, Field[] fields) { |
181 return insert(length, chars, fields); | 181 return insert(length, chars, fields); |
182 } | 182 } |
183 | 183 |
184 /** | 184 /** |
185 * Inserts the chars in the specified char array at the specified index in t
he string, and associates them with the | 185 * Inserts the chars in the specified char array at the specified index in t
he string, and associates |
186 * fields in the specified field array, which must have the same length as c
hars. | 186 * them with the fields in the specified field array, which must have the sa
me length as chars. |
187 * | 187 * |
188 * @return The number of chars added, which is the length of the char array. | 188 * @return The number of chars added, which is the length of the char array. |
189 */ | 189 */ |
190 public int insert(int index, char[] chars, Field[] fields) { | 190 public int insert(int index, char[] chars, Field[] fields) { |
191 assert fields == null || chars.length == fields.length; | 191 assert fields == null || chars.length == fields.length; |
192 int count = chars.length; | 192 int count = chars.length; |
193 if (count == 0) | 193 if (count == 0) |
194 return 0; // nothing to insert | 194 return 0; // nothing to insert |
195 int position = prepareForInsert(index, count); | 195 int position = prepareForInsert(index, count); |
196 for (int i = 0; i < count; i++) { | 196 for (int i = 0; i < count; i++) { |
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
250 // Append to end | 250 // Append to end |
251 length += count; | 251 length += count; |
252 return zero + length - count; | 252 return zero + length - count; |
253 } else { | 253 } else { |
254 // Move chars around and/or allocate more space | 254 // Move chars around and/or allocate more space |
255 return prepareForInsertHelper(index, count); | 255 return prepareForInsertHelper(index, count); |
256 } | 256 } |
257 } | 257 } |
258 | 258 |
259 private int prepareForInsertHelper(int index, int count) { | 259 private int prepareForInsertHelper(int index, int count) { |
260 // Java note: Keeping this code out of prepareForInsert() increases the
speed of append operations. | 260 // Java note: Keeping this code out of prepareForInsert() increases the
speed of append |
| 261 // operations. |
261 int oldCapacity = getCapacity(); | 262 int oldCapacity = getCapacity(); |
262 int oldZero = zero; | 263 int oldZero = zero; |
263 char[] oldChars = chars; | 264 char[] oldChars = chars; |
264 Field[] oldFields = fields; | 265 Field[] oldFields = fields; |
265 if (length + count > oldCapacity) { | 266 if (length + count > oldCapacity) { |
266 int newCapacity = (length + count) * 2; | 267 int newCapacity = (length + count) * 2; |
267 int newZero = newCapacity / 2 - (length + count) / 2; | 268 int newZero = newCapacity / 2 - (length + count) / 2; |
268 | 269 |
269 char[] newChars = new char[newCapacity]; | 270 char[] newChars = new char[newCapacity]; |
270 Field[] newFields = new Field[newCapacity]; | 271 Field[] newFields = new Field[newCapacity]; |
271 | 272 |
272 // First copy the prefix and then the suffix, leaving room for the n
ew chars that the | 273 // First copy the prefix and then the suffix, leaving room for the n
ew chars that the |
273 // caller wants to insert. | 274 // caller wants to insert. |
274 System.arraycopy(oldChars, oldZero, newChars, newZero, index); | 275 System.arraycopy(oldChars, oldZero, newChars, newZero, index); |
275 System.arraycopy(oldChars, oldZero + index, newChars, newZero + inde
x + count, length - index); | 276 System.arraycopy(oldChars, |
| 277 oldZero + index, |
| 278 newChars, |
| 279 newZero + index + count, |
| 280 length - index); |
276 System.arraycopy(oldFields, oldZero, newFields, newZero, index); | 281 System.arraycopy(oldFields, oldZero, newFields, newZero, index); |
277 System.arraycopy(oldFields, oldZero + index, newFields, newZero + in
dex + count, length - index); | 282 System.arraycopy(oldFields, |
| 283 oldZero + index, |
| 284 newFields, |
| 285 newZero + index + count, |
| 286 length - index); |
278 | 287 |
279 chars = newChars; | 288 chars = newChars; |
280 fields = newFields; | 289 fields = newFields; |
281 zero = newZero; | 290 zero = newZero; |
282 length += count; | 291 length += count; |
283 } else { | 292 } else { |
284 int newZero = oldCapacity / 2 - (length + count) / 2; | 293 int newZero = oldCapacity / 2 - (length + count) / 2; |
285 | 294 |
286 // First copy the entire string to the location of the prefix, and t
hen move the suffix | 295 // First copy the entire string to the location of the prefix, and t
hen move the suffix |
287 // to make room for the new chars that the caller wants to insert. | 296 // to make room for the new chars that the caller wants to insert. |
288 System.arraycopy(oldChars, oldZero, oldChars, newZero, length); | 297 System.arraycopy(oldChars, oldZero, oldChars, newZero, length); |
289 System.arraycopy(oldChars, newZero + index, oldChars, newZero + inde
x + count, length - index); | 298 System.arraycopy(oldChars, |
| 299 newZero + index, |
| 300 oldChars, |
| 301 newZero + index + count, |
| 302 length - index); |
290 System.arraycopy(oldFields, oldZero, oldFields, newZero, length); | 303 System.arraycopy(oldFields, oldZero, oldFields, newZero, length); |
291 System.arraycopy(oldFields, newZero + index, oldFields, newZero + in
dex + count, length - index); | 304 System.arraycopy(oldFields, |
| 305 newZero + index, |
| 306 oldFields, |
| 307 newZero + index + count, |
| 308 length - index); |
292 | 309 |
293 zero = newZero; | 310 zero = newZero; |
294 length += count; | 311 length += count; |
295 } | 312 } |
296 return zero + index; | 313 return zero + index; |
297 } | 314 } |
298 | 315 |
299 private int getCapacity() { | 316 private int getCapacity() { |
300 return chars.length; | 317 return chars.length; |
301 } | 318 } |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
335 fieldToDebugChar.put(NumberFormat.Field.GROUPING_SEPARATOR, ','); | 352 fieldToDebugChar.put(NumberFormat.Field.GROUPING_SEPARATOR, ','); |
336 fieldToDebugChar.put(NumberFormat.Field.PERCENT, '%'); | 353 fieldToDebugChar.put(NumberFormat.Field.PERCENT, '%'); |
337 fieldToDebugChar.put(NumberFormat.Field.PERMILLE, '‰'); | 354 fieldToDebugChar.put(NumberFormat.Field.PERMILLE, '‰'); |
338 fieldToDebugChar.put(NumberFormat.Field.CURRENCY, '$'); | 355 fieldToDebugChar.put(NumberFormat.Field.CURRENCY, '$'); |
339 } | 356 } |
340 | 357 |
341 /** | 358 /** |
342 * Returns a string that includes field information, for debugging purposes. | 359 * Returns a string that includes field information, for debugging purposes. |
343 * | 360 * |
344 * <p> | 361 * <p> |
345 * For example, if the string is "-12.345", the debug string will be somethi
ng like "<NumberStringBuilder | 362 * For example, if the string is "-12.345", the debug string will be somethi
ng like |
346 * [-123.45] [-iii.ff]>" | 363 * "<NumberStringBuilder [-123.45] [-iii.ff]>" |
347 * | 364 * |
348 * @return A string for debugging purposes. | 365 * @return A string for debugging purposes. |
349 */ | 366 */ |
350 public String toDebugString() { | 367 public String toDebugString() { |
351 StringBuilder sb = new StringBuilder(); | 368 StringBuilder sb = new StringBuilder(); |
352 sb.append("<NumberStringBuilder ["); | 369 sb.append("<NumberStringBuilder ["); |
353 sb.append(this.toString()); | 370 sb.append(this.toString()); |
354 sb.append("] ["); | 371 sb.append("] ["); |
355 for (int i = zero; i < zero + length; i++) { | 372 for (int i = zero; i < zero + length; i++) { |
356 if (fields[i] == null) { | 373 if (fields[i] == null) { |
(...skipping 10 matching lines...) Expand all Loading... |
367 public char[] toCharArray() { | 384 public char[] toCharArray() { |
368 return Arrays.copyOfRange(chars, zero, zero + length); | 385 return Arrays.copyOfRange(chars, zero, zero + length); |
369 } | 386 } |
370 | 387 |
371 /** @return A new array containing the field values of this string builder.
*/ | 388 /** @return A new array containing the field values of this string builder.
*/ |
372 public Field[] toFieldArray() { | 389 public Field[] toFieldArray() { |
373 return Arrays.copyOfRange(fields, zero, zero + length); | 390 return Arrays.copyOfRange(fields, zero, zero + length); |
374 } | 391 } |
375 | 392 |
376 /** | 393 /** |
377 * @return Whether the contents and field values of this string builder are
equal to the given chars and fields. | 394 * @return Whether the contents and field values of this string builder are
equal to the given chars |
| 395 * and fields. |
378 * @see #toCharArray | 396 * @see #toCharArray |
379 * @see #toFieldArray | 397 * @see #toFieldArray |
380 */ | 398 */ |
381 public boolean contentEquals(char[] chars, Field[] fields) { | 399 public boolean contentEquals(char[] chars, Field[] fields) { |
382 if (chars.length != length) | 400 if (chars.length != length) |
383 return false; | 401 return false; |
384 if (fields.length != length) | 402 if (fields.length != length) |
385 return false; | 403 return false; |
386 for (int i = 0; i < length; i++) { | 404 for (int i = 0; i < length; i++) { |
387 if (this.chars[zero + i] != chars[i]) | 405 if (this.chars[zero + i] != chars[i]) |
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
448 } | 466 } |
449 | 467 |
450 /* com.ibm.icu.text.NumberFormat. */ Field field = (Field) rawField; | 468 /* com.ibm.icu.text.NumberFormat. */ Field field = (Field) rawField; |
451 | 469 |
452 boolean seenStart = false; | 470 boolean seenStart = false; |
453 int fractionStart = -1; | 471 int fractionStart = -1; |
454 for (int i = zero; i <= zero + length; i++) { | 472 for (int i = zero; i <= zero + length; i++) { |
455 Field _field = (i < zero + length) ? fields[i] : null; | 473 Field _field = (i < zero + length) ? fields[i] : null; |
456 if (seenStart && field != _field) { | 474 if (seenStart && field != _field) { |
457 // Special case: GROUPING_SEPARATOR counts as an INTEGER. | 475 // Special case: GROUPING_SEPARATOR counts as an INTEGER. |
458 if (field == NumberFormat.Field.INTEGER && _field == NumberForma
t.Field.GROUPING_SEPARATOR) { | 476 if (field == NumberFormat.Field.INTEGER |
| 477 && _field == NumberFormat.Field.GROUPING_SEPARATOR) { |
459 continue; | 478 continue; |
460 } | 479 } |
461 fp.setEndIndex(i - zero + offset); | 480 fp.setEndIndex(i - zero + offset); |
462 break; | 481 break; |
463 } else if (!seenStart && field == _field) { | 482 } else if (!seenStart && field == _field) { |
464 fp.setBeginIndex(i - zero + offset); | 483 fp.setBeginIndex(i - zero + offset); |
465 seenStart = true; | 484 seenStart = true; |
466 } | 485 } |
467 if (_field == NumberFormat.Field.INTEGER || _field == NumberFormat.F
ield.DECIMAL_SEPARATOR) { | 486 if (_field == NumberFormat.Field.INTEGER || _field == NumberFormat.F
ield.DECIMAL_SEPARATOR) { |
468 fractionStart = i - zero + 1; | 487 fractionStart = i - zero + 1; |
469 } | 488 } |
470 } | 489 } |
471 | 490 |
472 // Backwards compatibility: FRACTION needs to start after INTEGER if emp
ty | 491 // Backwards compatibility: FRACTION needs to start after INTEGER if emp
ty |
473 if (field == NumberFormat.Field.FRACTION && !seenStart) { | 492 if (field == NumberFormat.Field.FRACTION && !seenStart) { |
474 fp.setBeginIndex(fractionStart + offset); | 493 fp.setBeginIndex(fractionStart + offset); |
475 fp.setEndIndex(fractionStart + offset); | 494 fp.setEndIndex(fractionStart + offset); |
476 } | 495 } |
477 } | 496 } |
478 | 497 |
479 public AttributedCharacterIterator getIterator() { | 498 public AttributedCharacterIterator getIterator() { |
480 AttributedString as = new AttributedString(toString()); | 499 AttributedString as = new AttributedString(toString()); |
481 Field current = null; | 500 Field current = null; |
482 int currentStart = -1; | 501 int currentStart = -1; |
483 for (int i = 0; i < length; i++) { | 502 for (int i = 0; i < length; i++) { |
484 Field field = fields[i + zero]; | 503 Field field = fields[i + zero]; |
485 if (current == NumberFormat.Field.INTEGER && field == NumberFormat.F
ield.GROUPING_SEPARATOR) { | 504 if (current == NumberFormat.Field.INTEGER |
| 505 && field == NumberFormat.Field.GROUPING_SEPARATOR) { |
486 // Special case: GROUPING_SEPARATOR counts as an INTEGER. | 506 // Special case: GROUPING_SEPARATOR counts as an INTEGER. |
487 as.addAttribute(NumberFormat.Field.GROUPING_SEPARATOR, NumberFor
mat.Field.GROUPING_SEPARATOR, i, i + 1); | 507 as.addAttribute(NumberFormat.Field.GROUPING_SEPARATOR, |
| 508 NumberFormat.Field.GROUPING_SEPARATOR, |
| 509 i, |
| 510 i + 1); |
488 } else if (current != field) { | 511 } else if (current != field) { |
489 if (current != null) { | 512 if (current != null) { |
490 as.addAttribute(current, current, currentStart, i); | 513 as.addAttribute(current, current, currentStart, i); |
491 } | 514 } |
492 current = field; | 515 current = field; |
493 currentStart = i; | 516 currentStart = i; |
494 } | 517 } |
495 } | 518 } |
496 if (current != null) { | 519 if (current != null) { |
497 as.addAttribute(current, current, currentStart, length); | 520 as.addAttribute(current, current, currentStart, length); |
498 } | 521 } |
499 | 522 |
500 return as.getIterator(); | 523 return as.getIterator(); |
501 } | 524 } |
502 } | 525 } |
LEFT | RIGHT |