diff options
Diffstat (limited to 'libart/src/main/java/java/lang/AbstractStringBuilder.java')
-rw-r--r-- | libart/src/main/java/java/lang/AbstractStringBuilder.java | 886 |
1 files changed, 886 insertions, 0 deletions
diff --git a/libart/src/main/java/java/lang/AbstractStringBuilder.java b/libart/src/main/java/java/lang/AbstractStringBuilder.java new file mode 100644 index 0000000..c8c8c5a --- /dev/null +++ b/libart/src/main/java/java/lang/AbstractStringBuilder.java @@ -0,0 +1,886 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.lang; + +import libcore.util.EmptyArray; + +import java.io.InvalidObjectException; +import java.util.Arrays; + +/** + * A modifiable {@link CharSequence sequence of characters} for use in creating + * and modifying Strings. This class is intended as a base class for + * {@link StringBuffer} and {@link StringBuilder}. + * + * @see StringBuffer + * @see StringBuilder + * @since 1.5 + */ +abstract class AbstractStringBuilder { + + static final int INITIAL_CAPACITY = 16; + + private char[] value; + + private int count; + + private boolean shared; + + /* + * Returns the character array. + */ + final char[] getValue() { + return value; + } + + /* + * Returns the underlying buffer and sets the shared flag. + */ + final char[] shareValue() { + shared = true; + return value; + } + + /* + * Restores internal state after deserialization. + */ + final void set(char[] val, int len) throws InvalidObjectException { + if (val == null) { + val = EmptyArray.CHAR; + } + if (val.length < len) { + throw new InvalidObjectException("count out of range"); + } + + shared = false; + value = val; + count = len; + } + + AbstractStringBuilder() { + value = new char[INITIAL_CAPACITY]; + } + + AbstractStringBuilder(int capacity) { + if (capacity < 0) { + throw new NegativeArraySizeException(Integer.toString(capacity)); + } + value = new char[capacity]; + } + + AbstractStringBuilder(String string) { + count = string.length(); + shared = false; + value = new char[count + INITIAL_CAPACITY]; + string.getCharsNoCheck(0, count, value, 0); + } + + private void enlargeBuffer(int min) { + int newCount = ((value.length >> 1) + value.length) + 2; + char[] newData = new char[min > newCount ? min : newCount]; + System.arraycopy(value, 0, newData, 0, count); + value = newData; + shared = false; + } + + final void appendNull() { + int newCount = count + 4; + if (newCount > value.length) { + enlargeBuffer(newCount); + } + value[count++] = 'n'; + value[count++] = 'u'; + value[count++] = 'l'; + value[count++] = 'l'; + } + + final void append0(char[] chars) { + int newCount = count + chars.length; + if (newCount > value.length) { + enlargeBuffer(newCount); + } + System.arraycopy(chars, 0, value, count, chars.length); + count = newCount; + } + + final void append0(char[] chars, int offset, int length) { + Arrays.checkOffsetAndCount(chars.length, offset, length); + int newCount = count + length; + if (newCount > value.length) { + enlargeBuffer(newCount); + } + System.arraycopy(chars, offset, value, count, length); + count = newCount; + } + + final void append0(char ch) { + if (count == value.length) { + enlargeBuffer(count + 1); + } + value[count++] = ch; + } + + final void append0(String string) { + if (string == null) { + appendNull(); + return; + } + int length = string.length(); + int newCount = count + length; + if (newCount > value.length) { + enlargeBuffer(newCount); + } + string.getCharsNoCheck(0, length, value, count); + count = newCount; + } + + final void append0(CharSequence s, int start, int end) { + if (s == null) { + s = "null"; + } + if ((start | end) < 0 || start > end || end > s.length()) { + throw new IndexOutOfBoundsException(); + } + + int length = end - start; + int newCount = count + length; + if (newCount > value.length) { + enlargeBuffer(newCount); + } else if (shared) { + value = value.clone(); + shared = false; + } + + if (s instanceof String) { + ((String) s).getCharsNoCheck(start, end, value, count); + } else if (s instanceof AbstractStringBuilder) { + AbstractStringBuilder other = (AbstractStringBuilder) s; + System.arraycopy(other.value, start, value, count, length); + } else { + int j = count; // Destination index. + for (int i = start; i < end; i++) { + value[j++] = s.charAt(i); + } + } + + this.count = newCount; + } + + /** + * Returns the number of characters that can be held without growing. + * + * @return the capacity + * @see #ensureCapacity + * @see #length + */ + public int capacity() { + return value.length; + } + + /** + * Returns the character at {@code index}. + * @throws IndexOutOfBoundsException if {@code index < 0} or {@code index >= length()}. + */ + public char charAt(int index) { + if (index < 0 || index >= count) { + throw indexAndLength(index); + } + return value[index]; + } + + private StringIndexOutOfBoundsException indexAndLength(int index) { + throw new StringIndexOutOfBoundsException(count, index); + } + + private StringIndexOutOfBoundsException startEndAndLength(int start, int end) { + throw new StringIndexOutOfBoundsException(count, start, end - start); + } + + final void delete0(int start, int end) { + // NOTE: StringBuilder#delete(int, int) is specified not to throw if + // the end index is >= count, as long as it's >= start. This means + // we have to clamp it to count here. + if (end > count) { + end = count; + } + + if (start < 0 || start > count || start > end) { + throw startEndAndLength(start, end); + } + + // NOTE: StringBuilder#delete(int, int) throws only if start > count + // (start == count is considered valid, oddly enough). Since 'end' is + // already a clamped value, that case is handled here. + if (end == start) { + return; + } + + // At this point we know for sure that end > start. + int length = count - end; + if (length >= 0) { + if (!shared) { + System.arraycopy(value, end, value, start, length); + } else { + char[] newData = new char[value.length]; + System.arraycopy(value, 0, newData, 0, start); + System.arraycopy(value, end, newData, start, length); + value = newData; + shared = false; + } + } + count -= end - start; + } + + final void deleteCharAt0(int index) { + if (index < 0 || index >= count) { + throw indexAndLength(index); + } + + delete0(index, index + 1); + } + + /** + * Ensures that this object has a minimum capacity available before + * requiring the internal buffer to be enlarged. The general policy of this + * method is that if the {@code minimumCapacity} is larger than the current + * {@link #capacity()}, then the capacity will be increased to the largest + * value of either the {@code minimumCapacity} or the current capacity + * multiplied by two plus two. Although this is the general policy, there is + * no guarantee that the capacity will change. + * + * @param min + * the new minimum capacity to set. + */ + public void ensureCapacity(int min) { + if (min > value.length) { + int ourMin = value.length*2 + 2; + enlargeBuffer(Math.max(ourMin, min)); + } + } + + /** + * Copies the requested sequence of characters into {@code dst} passed + * starting at {@code dst}. + * + * @param start + * the inclusive start index of the characters to copy. + * @param end + * the exclusive end index of the characters to copy. + * @param dst + * the {@code char[]} to copy the characters to. + * @param dstStart + * the inclusive start index of {@code dst} to begin copying to. + * @throws IndexOutOfBoundsException + * if the {@code start} is negative, the {@code dstStart} is + * negative, the {@code start} is greater than {@code end}, the + * {@code end} is greater than the current {@link #length()} or + * {@code dstStart + end - begin} is greater than + * {@code dst.length}. + */ + public void getChars(int start, int end, char[] dst, int dstStart) { + if (start > count || end > count || start > end) { + throw startEndAndLength(start, end); + } + System.arraycopy(value, start, dst, dstStart, end - start); + } + + final void insert0(int index, char[] chars) { + if (index < 0 || index > count) { + throw indexAndLength(index); + } + if (chars.length != 0) { + move(chars.length, index); + System.arraycopy(chars, 0, value, index, chars.length); + count += chars.length; + } + } + + final void insert0(int index, char[] chars, int start, int length) { + if (index >= 0 && index <= count) { + // start + length could overflow, start/length maybe MaxInt + if (start >= 0 && length >= 0 && length <= chars.length - start) { + if (length != 0) { + move(length, index); + System.arraycopy(chars, start, value, index, length); + count += length; + } + return; + } + } + throw new StringIndexOutOfBoundsException("this.length=" + count + + "; index=" + index + "; chars.length=" + chars.length + + "; start=" + start + "; length=" + length); + } + + final void insert0(int index, char ch) { + if (index < 0 || index > count) { + // RI compatible exception type + throw new ArrayIndexOutOfBoundsException(count, index); + } + move(1, index); + value[index] = ch; + count++; + } + + final void insert0(int index, String string) { + if (index >= 0 && index <= count) { + if (string == null) { + string = "null"; + } + int min = string.length(); + if (min != 0) { + move(min, index); + string.getCharsNoCheck(0, min, value, index); + count += min; + } + } else { + throw indexAndLength(index); + } + } + + final void insert0(int index, CharSequence s, int start, int end) { + if (s == null) { + s = "null"; + } + if ((index | start | end) < 0 || index > count || start > end || end > s.length()) { + throw new IndexOutOfBoundsException(); + } + insert0(index, s.subSequence(start, end).toString()); + } + + /** + * The current length. + * + * @return the number of characters contained in this instance. + */ + public int length() { + return count; + } + + private void move(int size, int index) { + int newCount; + if (value.length - count >= size) { + if (!shared) { + // index == count case is no-op + System.arraycopy(value, index, value, index + size, count - index); + return; + } + newCount = value.length; + } else { + newCount = Math.max(count + size, value.length*2 + 2); + } + + char[] newData = new char[newCount]; + System.arraycopy(value, 0, newData, 0, index); + // index == count case is no-op + System.arraycopy(value, index, newData, index + size, count - index); + value = newData; + shared = false; + } + + final void replace0(int start, int end, String string) { + if (start >= 0) { + if (end > count) { + end = count; + } + if (end > start) { + int stringLength = string.length(); + int diff = end - start - stringLength; + if (diff > 0) { // replacing with fewer characters + if (!shared) { + // index == count case is no-op + System.arraycopy(value, end, value, start + + stringLength, count - end); + } else { + char[] newData = new char[value.length]; + System.arraycopy(value, 0, newData, 0, start); + // index == count case is no-op + System.arraycopy(value, end, newData, start + + stringLength, count - end); + value = newData; + shared = false; + } + } else if (diff < 0) { + // replacing with more characters...need some room + move(-diff, end); + } else if (shared) { + value = value.clone(); + shared = false; + } + string.getCharsNoCheck(0, stringLength, value, start); + count -= diff; + return; + } + if (start == end) { + if (string == null) { + throw new NullPointerException("string == null"); + } + insert0(start, string); + return; + } + } + throw startEndAndLength(start, end); + } + + final void reverse0() { + if (count < 2) { + return; + } + if (!shared) { + int end = count - 1; + char frontHigh = value[0]; + char endLow = value[end]; + boolean allowFrontSur = true, allowEndSur = true; + for (int i = 0, mid = count / 2; i < mid; i++, --end) { + char frontLow = value[i + 1]; + char endHigh = value[end - 1]; + boolean surAtFront = allowFrontSur && frontLow >= 0xdc00 + && frontLow <= 0xdfff && frontHigh >= 0xd800 + && frontHigh <= 0xdbff; + if (surAtFront && (count < 3)) { + return; + } + boolean surAtEnd = allowEndSur && endHigh >= 0xd800 + && endHigh <= 0xdbff && endLow >= 0xdc00 + && endLow <= 0xdfff; + allowFrontSur = allowEndSur = true; + if (surAtFront == surAtEnd) { + if (surAtFront) { + // both surrogates + value[end] = frontLow; + value[end - 1] = frontHigh; + value[i] = endHigh; + value[i + 1] = endLow; + frontHigh = value[i + 2]; + endLow = value[end - 2]; + i++; + end--; + } else { + // neither surrogates + value[end] = frontHigh; + value[i] = endLow; + frontHigh = frontLow; + endLow = endHigh; + } + } else { + if (surAtFront) { + // surrogate only at the front + value[end] = frontLow; + value[i] = endLow; + endLow = endHigh; + allowFrontSur = false; + } else { + // surrogate only at the end + value[end] = frontHigh; + value[i] = endHigh; + frontHigh = frontLow; + allowEndSur = false; + } + } + } + if ((count & 1) == 1 && (!allowFrontSur || !allowEndSur)) { + value[end] = allowFrontSur ? endLow : frontHigh; + } + } else { + char[] newData = new char[value.length]; + for (int i = 0, end = count; i < count; i++) { + char high = value[i]; + if ((i + 1) < count && high >= 0xd800 && high <= 0xdbff) { + char low = value[i + 1]; + if (low >= 0xdc00 && low <= 0xdfff) { + newData[--end] = low; + i++; + } + } + newData[--end] = high; + } + value = newData; + shared = false; + } + } + + /** + * Sets the character at the {@code index}. + * + * @param index + * the zero-based index of the character to replace. + * @param ch + * the character to set. + * @throws IndexOutOfBoundsException + * if {@code index} is negative or greater than or equal to the + * current {@link #length()}. + */ + public void setCharAt(int index, char ch) { + if (index < 0 || index >= count) { + throw indexAndLength(index); + } + if (shared) { + value = value.clone(); + shared = false; + } + value[index] = ch; + } + + /** + * Sets the current length to a new value. If the new length is larger than + * the current length, then the new characters at the end of this object + * will contain the {@code char} value of {@code \u0000}. + * + * @param length + * the new length of this StringBuffer. + * @throws IndexOutOfBoundsException + * if {@code length < 0}. + * @see #length + */ + public void setLength(int length) { + if (length < 0) { + throw new StringIndexOutOfBoundsException("length < 0: " + length); + } + if (length > value.length) { + enlargeBuffer(length); + } else { + if (shared) { + char[] newData = new char[value.length]; + System.arraycopy(value, 0, newData, 0, count); + value = newData; + shared = false; + } else { + if (count < length) { + Arrays.fill(value, count, length, (char) 0); + } + } + } + count = length; + } + + /** + * Returns the String value of the subsequence from the {@code start} index + * to the current end. + * + * @param start + * the inclusive start index to begin the subsequence. + * @return a String containing the subsequence. + * @throws StringIndexOutOfBoundsException + * if {@code start} is negative or greater than the current + * {@link #length()}. + */ + public String substring(int start) { + if (start >= 0 && start <= count) { + if (start == count) { + return ""; + } + + // Remove String sharing for more performance + return new String(value, start, count - start); + } + throw indexAndLength(start); + } + + /** + * Returns the String value of the subsequence from the {@code start} index + * to the {@code end} index. + * + * @param start + * the inclusive start index to begin the subsequence. + * @param end + * the exclusive end index to end the subsequence. + * @return a String containing the subsequence. + * @throws StringIndexOutOfBoundsException + * if {@code start} is negative, greater than {@code end} or if + * {@code end} is greater than the current {@link #length()}. + */ + public String substring(int start, int end) { + if (start >= 0 && start <= end && end <= count) { + if (start == end) { + return ""; + } + + // Remove String sharing for more performance + return new String(value, start, end - start); + } + throw startEndAndLength(start, end); + } + + /** + * Returns the current String representation. + * + * @return a String containing the characters in this instance. + */ + @Override + public String toString() { + if (count == 0) { + return ""; + } + return StringFactory.newStringFromChars(0, count, value); + } + + /** + * Returns a {@code CharSequence} of the subsequence from the {@code start} + * index to the {@code end} index. + * + * @param start + * the inclusive start index to begin the subsequence. + * @param end + * the exclusive end index to end the subsequence. + * @return a CharSequence containing the subsequence. + * @throws IndexOutOfBoundsException + * if {@code start} is negative, greater than {@code end} or if + * {@code end} is greater than the current {@link #length()}. + * @since 1.4 + */ + public CharSequence subSequence(int start, int end) { + return substring(start, end); + } + + /** + * Searches for the first index of the specified character. The search for + * the character starts at the beginning and moves towards the end. + * + * @param string + * the string to find. + * @return the index of the specified character, -1 if the character isn't + * found. + * @see #lastIndexOf(String) + * @since 1.4 + */ + public int indexOf(String string) { + return indexOf(string, 0); + } + + /** + * Searches for the index of the specified character. The search for the + * character starts at the specified offset and moves towards the end. + * + * @param subString + * the string to find. + * @param start + * the starting offset. + * @return the index of the specified character, -1 if the character isn't + * found + * @see #lastIndexOf(String,int) + * @since 1.4 + */ + public int indexOf(String subString, int start) { + if (start < 0) { + start = 0; + } + int subCount = subString.length(); + if (subCount > 0) { + if (subCount + start > count) { + return -1; + } + // TODO optimize charAt to direct array access + char firstChar = subString.charAt(0); + while (true) { + int i = start; + boolean found = false; + for (; i < count; i++) { + if (value[i] == firstChar) { + found = true; + break; + } + } + if (!found || subCount + i > count) { + return -1; // handles subCount > count || start >= count + } + int o1 = i, o2 = 0; + while (++o2 < subCount && value[++o1] == subString.charAt(o2)) { + // Intentionally empty + } + if (o2 == subCount) { + return i; + } + start = i + 1; + } + } + return (start < count || start == 0) ? start : count; + } + + /** + * Searches for the last index of the specified character. The search for + * the character starts at the end and moves towards the beginning. + * + * @param string + * the string to find. + * @return the index of the specified character, -1 if the character isn't + * found. + * @throws NullPointerException + * if {@code string} is {@code null}. + * @see String#lastIndexOf(java.lang.String) + * @since 1.4 + */ + public int lastIndexOf(String string) { + return lastIndexOf(string, count); + } + + /** + * Searches for the index of the specified character. The search for the + * character starts at the specified offset and moves towards the beginning. + * + * @param subString + * the string to find. + * @param start + * the starting offset. + * @return the index of the specified character, -1 if the character isn't + * found. + * @throws NullPointerException + * if {@code subString} is {@code null}. + * @see String#lastIndexOf(String,int) + * @since 1.4 + */ + public int lastIndexOf(String subString, int start) { + int subCount = subString.length(); + if (subCount <= count && start >= 0) { + if (subCount > 0) { + if (start > count - subCount) { + start = count - subCount; // count and subCount are both + } + // >= 1 + // TODO optimize charAt to direct array access + char firstChar = subString.charAt(0); + while (true) { + int i = start; + boolean found = false; + for (; i >= 0; --i) { + if (value[i] == firstChar) { + found = true; + break; + } + } + if (!found) { + return -1; + } + int o1 = i, o2 = 0; + while (++o2 < subCount + && value[++o1] == subString.charAt(o2)) { + // Intentionally empty + } + if (o2 == subCount) { + return i; + } + start = i - 1; + } + } + return start < count ? start : count; + } + return -1; + } + + /** + * Trims off any extra capacity beyond the current length. Note, this method + * is NOT guaranteed to change the capacity of this object. + * + * @since 1.5 + */ + public void trimToSize() { + if (count < value.length) { + char[] newValue = new char[count]; + System.arraycopy(value, 0, newValue, 0, count); + value = newValue; + shared = false; + } + } + + /** + * Retrieves the Unicode code point value at the {@code index}. + * + * @param index + * the index to the {@code char} code unit. + * @return the Unicode code point value. + * @throws IndexOutOfBoundsException + * if {@code index} is negative or greater than or equal to + * {@link #length()}. + * @see Character + * @see Character#codePointAt(char[], int, int) + * @since 1.5 + */ + public int codePointAt(int index) { + if (index < 0 || index >= count) { + throw indexAndLength(index); + } + return Character.codePointAt(value, index, count); + } + + /** + * Retrieves the Unicode code point value that precedes the {@code index}. + * + * @param index + * the index to the {@code char} code unit within this object. + * @return the Unicode code point value. + * @throws IndexOutOfBoundsException + * if {@code index} is less than 1 or greater than + * {@link #length()}. + * @see Character + * @see Character#codePointBefore(char[], int, int) + * @since 1.5 + */ + public int codePointBefore(int index) { + if (index < 1 || index > count) { + throw indexAndLength(index); + } + return Character.codePointBefore(value, index); + } + + /** + * Calculates the number of Unicode code points between {@code start} + * and {@code end}. + * + * @param start + * the inclusive beginning index of the subsequence. + * @param end + * the exclusive end index of the subsequence. + * @return the number of Unicode code points in the subsequence. + * @throws IndexOutOfBoundsException + * if {@code start} is negative or greater than + * {@code end} or {@code end} is greater than + * {@link #length()}. + * @see Character + * @see Character#codePointCount(char[], int, int) + * @since 1.5 + */ + public int codePointCount(int start, int end) { + if (start < 0 || end > count || start > end) { + throw startEndAndLength(start, end); + } + return Character.codePointCount(value, start, end - start); + } + + /** + * Returns the index that is offset {@code codePointOffset} code points from + * {@code index}. + * + * @param index + * the index to calculate the offset from. + * @param codePointOffset + * the number of code points to count. + * @return the index that is {@code codePointOffset} code points away from + * index. + * @throws IndexOutOfBoundsException + * if {@code index} is negative or greater than + * {@link #length()} or if there aren't enough code points + * before or after {@code index} to match + * {@code codePointOffset}. + * @see Character + * @see Character#offsetByCodePoints(char[], int, int, int, int) + * @since 1.5 + */ + public int offsetByCodePoints(int index, int codePointOffset) { + return Character.offsetByCodePoints(value, 0, count, index, + codePointOffset); + } +} |