Home | History | Annotate | Download | only in number
      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 "&lt;NumberStringBuilder
    348      * [-123.45] [-iii.ff]&gt;"
    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