• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      * "&lt;FormattedStringBuilder [-123.45] [-iii.ff]&gt;"
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