1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package java.lang;
19
20import java.io.InvalidObjectException;
21import java.util.Arrays;
22
23import org.apache.harmony.luni.util.Msg;
24
25/**
26 * A modifiable {@link CharSequence sequence of characters} for use in creating
27 * and modifying Strings. This class is intended as a base class for
28 * {@link StringBuffer} and {@link StringBuilder}.
29 *
30 * @see StringBuffer
31 * @see StringBuilder
32 * @since 1.5
33 */
34abstract class AbstractStringBuilder {
35
36    static final int INITIAL_CAPACITY = 16;
37
38    private char[] value;
39
40    private int count;
41
42    private boolean shared;
43
44    /*
45     * Returns the character array.
46     */
47    final char[] getValue() {
48        return value;
49    }
50
51    /*
52     * Returns the underlying buffer and sets the shared flag.
53     */
54    final char[] shareValue() {
55        shared = true;
56        return value;
57    }
58
59    /*
60     * Restores internal state after deserialization.
61     */
62    final void set(char[] val, int len) throws InvalidObjectException {
63        if (val == null) {
64            val = new char[0];
65        }
66        if (val.length < len) {
67            throw new InvalidObjectException(Msg.getString("K0199")); //$NON-NLS-1$
68        }
69
70        shared = false;
71        value = val;
72        count = len;
73    }
74
75    AbstractStringBuilder() {
76        value = new char[INITIAL_CAPACITY];
77    }
78
79    AbstractStringBuilder(int capacity) {
80        if (capacity < 0) {
81            throw new NegativeArraySizeException();
82        }
83        value = new char[capacity];
84    }
85
86    AbstractStringBuilder(String string) {
87        count = string.length();
88        shared = false;
89        value = new char[count + INITIAL_CAPACITY];
90        // BEGIN android-changed
91        string._getChars(0, count, value, 0);
92        // END android-changed
93    }
94
95    private void enlargeBuffer(int min) {
96        int newSize = ((value.length >> 1) + value.length) + 2;
97        char[] newData = new char[min > newSize ? min : newSize];
98        System.arraycopy(value, 0, newData, 0, count);
99        value = newData;
100        shared = false;
101    }
102
103    final void appendNull() {
104        int newSize = count + 4;
105        if (newSize > value.length) {
106            enlargeBuffer(newSize);
107        }
108        value[count++] = 'n';
109        value[count++] = 'u';
110        value[count++] = 'l';
111        value[count++] = 'l';
112    }
113
114    final void append0(char chars[]) {
115        int newSize = count + chars.length;
116        if (newSize > value.length) {
117            enlargeBuffer(newSize);
118        }
119        System.arraycopy(chars, 0, value, count, chars.length);
120        count = newSize;
121    }
122
123    final void append0(char[] chars, int offset, int length) {
124        // Force null check of chars first!
125        if (offset > chars.length || offset < 0) {
126            // K002e=Offset out of bounds \: {0}
127            throw new ArrayIndexOutOfBoundsException(Msg.getString("K002e", offset)); //$NON-NLS-1$
128        }
129        if (length < 0 || chars.length - offset < length) {
130            // K0031=Length out of bounds \: {0}
131            throw new ArrayIndexOutOfBoundsException(Msg.getString("K0031", length)); //$NON-NLS-1$
132        }
133
134        int newSize = count + length;
135        if (newSize > value.length) {
136            enlargeBuffer(newSize);
137        }
138        System.arraycopy(chars, offset, value, count, length);
139        count = newSize;
140    }
141
142    final void append0(char ch) {
143        if (count == value.length) {
144            enlargeBuffer(count + 1);
145        }
146        value[count++] = ch;
147    }
148
149    final void append0(String string) {
150        if (string == null) {
151            appendNull();
152            return;
153        }
154        int adding = string.length();
155        int newSize = count + adding;
156        if (newSize > value.length) {
157            enlargeBuffer(newSize);
158        }
159        // BEGIN android-changed
160        string._getChars(0, adding, value, count);
161        // END android-changed
162        count = newSize;
163    }
164
165    final void append0(CharSequence s, int start, int end) {
166        if (s == null) {
167            s = "null"; //$NON-NLS-1$
168        }
169        if (start < 0 || end < 0 || start > end || end > s.length()) {
170            throw new IndexOutOfBoundsException();
171        }
172
173        // BEGIN android-changed
174        int adding = end - start;
175        int newSize = count + adding;
176        if (newSize > value.length) {
177            enlargeBuffer(newSize);
178        } else if (shared) {
179            value = value.clone();
180            shared = false;
181        }
182
183        if (s instanceof String) {
184            ((String) s)._getChars(start, end, value, count);
185        } else if (s instanceof AbstractStringBuilder) {
186            AbstractStringBuilder other = (AbstractStringBuilder) s;
187            System.arraycopy(other.value, start, value, count, adding);
188        } else {
189            int j = count; // Destination index.
190            for (int i = start; i < end; i++) {
191                value[j++] = s.charAt(i);
192            }
193        }
194
195        this.count = newSize;
196        // END android-changed
197    }
198
199    /**
200     * Returns the number of characters that can be held without growing.
201     *
202     * @return the capacity
203     * @see #ensureCapacity
204     * @see #length
205     */
206    public int capacity() {
207        return value.length;
208    }
209
210    /**
211     * Retrieves the character at the {@code index}.
212     *
213     * @param index
214     *            the index of the character to retrieve.
215     * @return the char value.
216     * @throws IndexOutOfBoundsException
217     *             if {@code index} is negative or greater than or equal to the
218     *             current {@link #length()}.
219     */
220    public char charAt(int index) {
221        if (index < 0 || index >= count) {
222            throw new StringIndexOutOfBoundsException(index);
223        }
224        return value[index];
225    }
226
227    final void delete0(int start, int end) {
228        if (start >= 0) {
229            if (end > count) {
230                end = count;
231            }
232            if (end == start) {
233                return;
234            }
235            if (end > start) {
236                int length = count - end;
237                if (length >= 0) {
238                    if (!shared) {
239                        System.arraycopy(value, end, value, start, length);
240                    } else {
241                        char[] newData = new char[value.length];
242                        System.arraycopy(value, 0, newData, 0, start);
243                        System.arraycopy(value, end, newData, start, length);
244                        value = newData;
245                        shared = false;
246                    }
247                }
248                count -= end - start;
249                return;
250            }
251        }
252        throw new StringIndexOutOfBoundsException();
253    }
254
255    final void deleteCharAt0(int location) {
256        if (0 > location || location >= count) {
257            throw new StringIndexOutOfBoundsException(location);
258        }
259        int length = count - location - 1;
260        if (length > 0) {
261            if (!shared) {
262                System.arraycopy(value, location + 1, value, location, length);
263            } else {
264                char[] newData = new char[value.length];
265                System.arraycopy(value, 0, newData, 0, location);
266                System
267                        .arraycopy(value, location + 1, newData, location,
268                                length);
269                value = newData;
270                shared = false;
271            }
272        }
273        count--;
274    }
275
276    /**
277     * Ensures that this object has a minimum capacity available before
278     * requiring the internal buffer to be enlarged. The general policy of this
279     * method is that if the {@code minimumCapacity} is larger than the current
280     * {@link #capacity()}, then the capacity will be increased to the largest
281     * value of either the {@code minimumCapacity} or the current capacity
282     * multiplied by two plus two. Although this is the general policy, there is
283     * no guarantee that the capacity will change.
284     *
285     * @param min
286     *            the new minimum capacity to set.
287     */
288    public void ensureCapacity(int min) {
289        if (min > value.length) {
290            int twice = (value.length << 1) + 2;
291            enlargeBuffer(twice > min ? twice : min);
292        }
293    }
294
295    /**
296     * Copies the requested sequence of characters to the {@code char[]} passed
297     * starting at {@code destStart}.
298     *
299     * @param start
300     *            the inclusive start index of the characters to copy.
301     * @param end
302     *            the exclusive end index of the characters to copy.
303     * @param dest
304     *            the {@code char[]} to copy the characters to.
305     * @param destStart
306     *            the inclusive start index of {@code dest} to begin copying to.
307     * @throws IndexOutOfBoundsException
308     *             if the {@code start} is negative, the {@code destStart} is
309     *             negative, the {@code start} is greater than {@code end}, the
310     *             {@code end} is greater than the current {@link #length()} or
311     *             {@code destStart + end - begin} is greater than
312     *             {@code dest.length}.
313     */
314    public void getChars(int start, int end, char[] dest, int destStart) {
315        if (start > count || end > count || start > end) {
316            throw new StringIndexOutOfBoundsException();
317        }
318        System.arraycopy(value, start, dest, destStart, end - start);
319    }
320
321    final void insert0(int index, char[] chars) {
322        if (0 > index || index > count) {
323            throw new StringIndexOutOfBoundsException(index);
324        }
325        if (chars.length != 0) {
326            move(chars.length, index);
327            System.arraycopy(chars, 0, value, index, chars.length);
328            count += chars.length;
329        }
330    }
331
332    final void insert0(int index, char[] chars, int start, int length) {
333        if (0 <= index && index <= count) {
334            // start + length could overflow, start/length maybe MaxInt
335            if (start >= 0 && 0 <= length && length <= chars.length - start) {
336                if (length != 0) {
337                    move(length, index);
338                    System.arraycopy(chars, start, value, index, length);
339                    count += length;
340                }
341                return;
342            }
343            throw new StringIndexOutOfBoundsException("offset " + start //$NON-NLS-1$
344                    + ", length " + length //$NON-NLS-1$
345                    + ", char[].length " + chars.length); //$NON-NLS-1$
346        }
347        throw new StringIndexOutOfBoundsException(index);
348    }
349
350    final void insert0(int index, char ch) {
351        if (0 > index || index > count) {
352            // RI compatible exception type
353            throw new ArrayIndexOutOfBoundsException(index);
354        }
355        move(1, index);
356        value[index] = ch;
357        count++;
358    }
359
360    final void insert0(int index, String string) {
361        if (0 <= index && index <= count) {
362            if (string == null) {
363                string = "null"; //$NON-NLS-1$
364            }
365            int min = string.length();
366            if (min != 0) {
367                move(min, index);
368                // BEGIN android-changed
369                string._getChars(0, min, value, index);
370                // END android-changed
371                count += min;
372            }
373        } else {
374            throw new StringIndexOutOfBoundsException(index);
375        }
376    }
377
378    final void insert0(int index, CharSequence s, int start, int end) {
379        if (s == null) {
380            s = "null"; //$NON-NLS-1$
381        }
382        if (index < 0 || index > count || start < 0 || end < 0 || start > end
383                || end > s.length()) {
384            throw new IndexOutOfBoundsException();
385        }
386        insert0(index, s.subSequence(start, end).toString());
387    }
388
389    /**
390     * The current length.
391     *
392     * @return the number of characters contained in this instance.
393     */
394    public int length() {
395        return count;
396    }
397
398    private void move(int size, int index) {
399        int newSize;
400        if (value.length - count >= size) {
401            if (!shared) {
402                System.arraycopy(value, index, value, index + size, count
403                        - index); // index == count case is no-op
404                return;
405            }
406            newSize = value.length;
407        } else {
408            int a = count + size, b = (value.length << 1) + 2;
409            newSize = a > b ? a : b;
410        }
411
412        char[] newData = new char[newSize];
413        System.arraycopy(value, 0, newData, 0, index);
414        // index == count case is no-op
415        System.arraycopy(value, index, newData, index + size, count - index);
416        value = newData;
417        shared = false;
418    }
419
420    final void replace0(int start, int end, String string) {
421        if (start >= 0) {
422            if (end > count) {
423                end = count;
424            }
425            if (end > start) {
426                int stringLength = string.length();
427                int diff = end - start - stringLength;
428                if (diff > 0) { // replacing with fewer characters
429                    if (!shared) {
430                        // index == count case is no-op
431                        System.arraycopy(value, end, value, start
432                                + stringLength, count - end);
433                    } else {
434                        char[] newData = new char[value.length];
435                        System.arraycopy(value, 0, newData, 0, start);
436                        // index == count case is no-op
437                        System.arraycopy(value, end, newData, start
438                                + stringLength, count - end);
439                        value = newData;
440                        shared = false;
441                    }
442                } else if (diff < 0) {
443                    // replacing with more characters...need some room
444                    move(-diff, end);
445                } else if (shared) {
446                    value = value.clone();
447                    shared = false;
448                }
449                // BEGIN android-changed
450                string._getChars(0, stringLength, value, start);
451                // END android-changed
452                count -= diff;
453                return;
454            }
455            if (start == end) {
456                if (string == null) {
457                    throw new NullPointerException();
458                }
459                insert0(start, string);
460                return;
461            }
462        }
463        throw new StringIndexOutOfBoundsException();
464    }
465
466    final void reverse0() {
467        if (count < 2) {
468            return;
469        }
470        if (!shared) {
471            int end = count - 1;
472            char frontHigh = value[0];
473            char endLow = value[end];
474            boolean allowFrontSur = true, allowEndSur = true;
475            for (int i = 0, mid = count / 2; i < mid; i++, --end) {
476                char frontLow = value[i + 1];
477                char endHigh = value[end - 1];
478                boolean surAtFront = allowFrontSur && frontLow >= 0xdc00
479                        && frontLow <= 0xdfff && frontHigh >= 0xd800
480                        && frontHigh <= 0xdbff;
481                if (surAtFront && (count < 3)) {
482                    return;
483                }
484                boolean surAtEnd = allowEndSur && endHigh >= 0xd800
485                        && endHigh <= 0xdbff && endLow >= 0xdc00
486                        && endLow <= 0xdfff;
487                allowFrontSur = allowEndSur = true;
488                if (surAtFront == surAtEnd) {
489                    if (surAtFront) {
490                        // both surrogates
491                        value[end] = frontLow;
492                        value[end - 1] = frontHigh;
493                        value[i] = endHigh;
494                        value[i + 1] = endLow;
495                        frontHigh = value[i + 2];
496                        endLow = value[end - 2];
497                        i++;
498                        end--;
499                    } else {
500                        // neither surrogates
501                        value[end] = frontHigh;
502                        value[i] = endLow;
503                        frontHigh = frontLow;
504                        endLow = endHigh;
505                    }
506                } else {
507                    if (surAtFront) {
508                        // surrogate only at the front
509                        value[end] = frontLow;
510                        value[i] = endLow;
511                        endLow = endHigh;
512                        allowFrontSur = false;
513                    } else {
514                        // surrogate only at the end
515                        value[end] = frontHigh;
516                        value[i] = endHigh;
517                        frontHigh = frontLow;
518                        allowEndSur = false;
519                    }
520                }
521            }
522            if ((count & 1) == 1 && (!allowFrontSur || !allowEndSur)) {
523                value[end] = allowFrontSur ? endLow : frontHigh;
524            }
525        } else {
526            char[] newData = new char[value.length];
527            for (int i = 0, end = count; i < count; i++) {
528                char high = value[i];
529                if ((i + 1) < count && high >= 0xd800 && high <= 0xdbff) {
530                    char low = value[i + 1];
531                    if (low >= 0xdc00 && low <= 0xdfff) {
532                        newData[--end] = low;
533                        i++;
534                    }
535                }
536                newData[--end] = high;
537            }
538            value = newData;
539            shared = false;
540        }
541    }
542
543    /**
544     * Sets the character at the {@code index}.
545     *
546     * @param index
547     *            the zero-based index of the character to replace.
548     * @param ch
549     *            the character to set.
550     * @throws IndexOutOfBoundsException
551     *             if {@code index} is negative or greater than or equal to the
552     *             current {@link #length()}.
553     */
554    public void setCharAt(int index, char ch) {
555        if (0 > index || index >= count) {
556            throw new StringIndexOutOfBoundsException(index);
557        }
558        if (shared) {
559            value = value.clone();
560            shared = false;
561        }
562        value[index] = ch;
563    }
564
565    /**
566     * Sets the current length to a new value. If the new length is larger than
567     * the current length, then the new characters at the end of this object
568     * will contain the {@code char} value of {@code \u0000}.
569     *
570     * @param length
571     *            the new length of this StringBuffer.
572     * @exception IndexOutOfBoundsException
573     *                if {@code length < 0}.
574     * @see #length
575     */
576    public void setLength(int length) {
577        if (length < 0) {
578            throw new StringIndexOutOfBoundsException(length);
579        }
580        if (length > value.length) {
581            enlargeBuffer(length);
582        } else {
583            if (shared) {
584                char[] newData = new char[value.length];
585                System.arraycopy(value, 0, newData, 0, count);
586                value = newData;
587                shared = false;
588            } else {
589                if (count < length) {
590                    Arrays.fill(value, count, length, (char) 0);
591                }
592            }
593        }
594        count = length;
595    }
596
597    /**
598     * Returns the String value of the subsequence from the {@code start} index
599     * to the current end.
600     *
601     * @param start
602     *            the inclusive start index to begin the subsequence.
603     * @return a String containing the subsequence.
604     * @throws StringIndexOutOfBoundsException
605     *             if {@code start} is negative or greater than the current
606     *             {@link #length()}.
607     */
608    public String substring(int start) {
609        if (0 <= start && start <= count) {
610            if (start == count) {
611                return ""; //$NON-NLS-1$
612            }
613
614            // Remove String sharing for more performance
615            return new String(value, start, count - start);
616        }
617        throw new StringIndexOutOfBoundsException(start);
618    }
619
620    /**
621     * Returns the String value of the subsequence from the {@code start} index
622     * to the {@code end} index.
623     *
624     * @param start
625     *            the inclusive start index to begin the subsequence.
626     * @param end
627     *            the exclusive end index to end the subsequence.
628     * @return a String containing the subsequence.
629     * @throws StringIndexOutOfBoundsException
630     *             if {@code start} is negative, greater than {@code end} or if
631     *             {@code end} is greater than the current {@link #length()}.
632     */
633    public String substring(int start, int end) {
634        if (0 <= start && start <= end && end <= count) {
635            if (start == end) {
636                return ""; //$NON-NLS-1$
637            }
638
639            // Remove String sharing for more performance
640            return new String(value, start, end - start);
641        }
642        throw new StringIndexOutOfBoundsException();
643    }
644
645    /**
646     * Returns the current String representation.
647     *
648     * @return a String containing the characters in this instance.
649     */
650    @Override
651    public String toString() {
652        if (count == 0) {
653            return ""; //$NON-NLS-1$
654        }
655        // Optimize String sharing for more performance
656        int wasted = value.length - count;
657        if (wasted >= 256
658                || (wasted >= INITIAL_CAPACITY && wasted >= (count >> 1))) {
659            return new String(value, 0, count);
660        }
661        shared = true;
662        return new String(0, count, value);
663    }
664
665    /**
666     * Returns a {@code CharSequence} of the subsequence from the {@code start}
667     * index to the {@code end} index.
668     *
669     * @param start
670     *            the inclusive start index to begin the subsequence.
671     * @param end
672     *            the exclusive end index to end the subsequence.
673     * @return a CharSequence containing the subsequence.
674     * @throws IndexOutOfBoundsException
675     *             if {@code start} is negative, greater than {@code end} or if
676     *             {@code end} is greater than the current {@link #length()}.
677     * @since 1.4
678     */
679    public CharSequence subSequence(int start, int end) {
680        return substring(start, end);
681    }
682
683    /**
684     * Searches for the first index of the specified character. The search for
685     * the character starts at the beginning and moves towards the end.
686     *
687     * @param string
688     *            the string to find.
689     * @return the index of the specified character, -1 if the character isn't
690     *         found.
691     * @see #lastIndexOf(String)
692     * @since 1.4
693     */
694    public int indexOf(String string) {
695        return indexOf(string, 0);
696    }
697
698    /**
699     * Searches for the index of the specified character. The search for the
700     * character starts at the specified offset and moves towards the end.
701     *
702     * @param subString
703     *            the string to find.
704     * @param start
705     *            the starting offset.
706     * @return the index of the specified character, -1 if the character isn't
707     *         found
708     * @see #lastIndexOf(String,int)
709     * @since 1.4
710     */
711    public int indexOf(String subString, int start) {
712        if (start < 0) {
713            start = 0;
714        }
715        int subCount = subString.length();
716        if (subCount > 0) {
717            if (subCount + start > count) {
718                return -1;
719            }
720            // TODO optimize charAt to direct array access
721            char firstChar = subString.charAt(0);
722            while (true) {
723                int i = start;
724                boolean found = false;
725                for (; i < count; i++) {
726                    if (value[i] == firstChar) {
727                        found = true;
728                        break;
729                    }
730                }
731                if (!found || subCount + i > count) {
732                    return -1; // handles subCount > count || start >= count
733                }
734                int o1 = i, o2 = 0;
735                while (++o2 < subCount && value[++o1] == subString.charAt(o2)) {
736                    // Intentionally empty
737                }
738                if (o2 == subCount) {
739                    return i;
740                }
741                start = i + 1;
742            }
743        }
744        return (start < count || start == 0) ? start : count;
745    }
746
747    /**
748     * Searches for the last index of the specified character. The search for
749     * the character starts at the end and moves towards the beginning.
750     *
751     * @param string
752     *            the string to find.
753     * @return the index of the specified character, -1 if the character isn't
754     *         found.
755     * @throws NullPointerException
756     *             if {@code string} is {@code null}.
757     * @see String#lastIndexOf(java.lang.String)
758     * @since 1.4
759     */
760    public int lastIndexOf(String string) {
761        return lastIndexOf(string, count);
762    }
763
764    /**
765     * Searches for the index of the specified character. The search for the
766     * character starts at the specified offset and moves towards the beginning.
767     *
768     * @param subString
769     *            the string to find.
770     * @param start
771     *            the starting offset.
772     * @return the index of the specified character, -1 if the character isn't
773     *         found.
774     * @throws NullPointerException
775     *             if {@code subString} is {@code null}.
776     * @see String#lastIndexOf(String,int)
777     * @since 1.4
778     */
779    public int lastIndexOf(String subString, int start) {
780        int subCount = subString.length();
781        if (subCount <= count && start >= 0) {
782            if (subCount > 0) {
783                if (start > count - subCount) {
784                    start = count - subCount; // count and subCount are both
785                }
786                // >= 1
787                // TODO optimize charAt to direct array access
788                char firstChar = subString.charAt(0);
789                while (true) {
790                    int i = start;
791                    boolean found = false;
792                    for (; i >= 0; --i) {
793                        if (value[i] == firstChar) {
794                            found = true;
795                            break;
796                        }
797                    }
798                    if (!found) {
799                        return -1;
800                    }
801                    int o1 = i, o2 = 0;
802                    while (++o2 < subCount
803                            && value[++o1] == subString.charAt(o2)) {
804                        // Intentionally empty
805                    }
806                    if (o2 == subCount) {
807                        return i;
808                    }
809                    start = i - 1;
810                }
811            }
812            return start < count ? start : count;
813        }
814        return -1;
815    }
816
817    /**
818     * Trims off any extra capacity beyond the current length. Note, this method
819     * is NOT guaranteed to change the capacity of this object.
820     *
821     * @since 1.5
822     */
823    public void trimToSize() {
824        if (count < value.length) {
825            char[] newValue = new char[count];
826            System.arraycopy(value, 0, newValue, 0, count);
827            value = newValue;
828            shared = false;
829        }
830    }
831
832    /**
833     * Retrieves the Unicode code point value at the {@code index}.
834     *
835     * @param index
836     *            the index to the {@code char} code unit.
837     * @return the Unicode code point value.
838     * @throws IndexOutOfBoundsException
839     *             if {@code index} is negative or greater than or equal to
840     *             {@link #length()}.
841     * @see Character
842     * @see Character#codePointAt(char[], int, int)
843     * @since 1.5
844     */
845    public int codePointAt(int index) {
846        if (index < 0 || index >= count) {
847            throw new StringIndexOutOfBoundsException(index);
848        }
849        return Character.codePointAt(value, index, count);
850    }
851
852    /**
853     * Retrieves the Unicode code point value that precedes the {@code index}.
854     *
855     * @param index
856     *            the index to the {@code char} code unit within this object.
857     * @return the Unicode code point value.
858     * @throws IndexOutOfBoundsException
859     *             if {@code index} is less than 1 or greater than
860     *             {@link #length()}.
861     * @see Character
862     * @see Character#codePointBefore(char[], int, int)
863     * @since 1.5
864     */
865    public int codePointBefore(int index) {
866        if (index < 1 || index > count) {
867            throw new StringIndexOutOfBoundsException(index);
868        }
869        return Character.codePointBefore(value, index);
870    }
871
872    /**
873     * Calculates the number of Unicode code points between {@code beginIndex}
874     * and {@code endIndex}.
875     *
876     * @param beginIndex
877     *            the inclusive beginning index of the subsequence.
878     * @param endIndex
879     *            the exclusive end index of the subsequence.
880     * @return the number of Unicode code points in the subsequence.
881     * @throws IndexOutOfBoundsException
882     *             if {@code beginIndex} is negative or greater than
883     *             {@code endIndex} or {@code endIndex} is greater than
884     *             {@link #length()}.
885     * @see Character
886     * @see Character#codePointCount(char[], int, int)
887     * @since 1.5
888     */
889    public int codePointCount(int beginIndex, int endIndex) {
890        if (beginIndex < 0 || endIndex > count || beginIndex > endIndex) {
891            throw new StringIndexOutOfBoundsException();
892        }
893        return Character.codePointCount(value, beginIndex, endIndex
894                - beginIndex);
895    }
896
897    /**
898     * Returns the index that is offset {@code codePointOffset} code points from
899     * {@code index}.
900     *
901     * @param index
902     *            the index to calculate the offset from.
903     * @param codePointOffset
904     *            the number of code points to count.
905     * @return the index that is {@code codePointOffset} code points away from
906     *         index.
907     * @throws IndexOutOfBoundsException
908     *             if {@code index} is negative or greater than
909     *             {@link #length()} or if there aren't enough code points
910     *             before or after {@code index} to match
911     *             {@code codePointOffset}.
912     * @see Character
913     * @see Character#offsetByCodePoints(char[], int, int, int, int)
914     * @since 1.5
915     */
916    public int offsetByCodePoints(int index, int codePointOffset) {
917        return Character.offsetByCodePoints(value, 0, count, index,
918                codePointOffset);
919    }
920}
921