src/java.base/share/classes/java/lang/AbstractStringBuilder.java
Print this page
@@ -29,10 +29,16 @@
import java.util.Arrays;
import java.util.Spliterator;
import java.util.stream.IntStream;
import java.util.stream.StreamSupport;
+import static java.lang.String.COMPACT_STRINGS;
+import static java.lang.String.UTF16;
+import static java.lang.String.LATIN1;
+import static java.lang.String.checkIndex;
+import static java.lang.String.checkOffset;
+
/**
* A mutable sequence of characters.
* <p>
* Implements a modifiable string. At any point in time it contains some
* particular sequence of characters, but the length and content of the
@@ -49,11 +55,16 @@
*/
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
- char[] value;
+ byte[] value;
+
+ /**
+ * The id of the encoding used to encode the bytes in {@code value}.
+ */
+ byte coder;
/**
* The count is the number of characters used.
*/
int count;
@@ -66,11 +77,17 @@
/**
* Creates an AbstractStringBuilder of the specified capacity.
*/
AbstractStringBuilder(int capacity) {
- value = new char[capacity];
+ if (COMPACT_STRINGS) {
+ value = new byte[capacity];
+ coder = LATIN1;
+ } else {
+ value = StringUTF16.newBytesFor(capacity);
+ coder = UTF16;
+ }
}
/**
* Returns the length (character count).
*
@@ -88,11 +105,11 @@
* will occur.
*
* @return the current capacity
*/
public int capacity() {
- return value.length;
+ return value.length >> coder;
}
/**
* Ensures that the capacity is at least equal to the specified minimum.
* If the current capacity is less than the argument, then a new internal
@@ -108,50 +125,76 @@
* actual capacity below that requested here.
*
* @param minimumCapacity the minimum desired capacity.
*/
public void ensureCapacity(int minimumCapacity) {
- if (minimumCapacity > 0)
+ if (minimumCapacity > 0) {
ensureCapacityInternal(minimumCapacity);
}
+ }
/**
* This method has the same contract as ensureCapacity, but is
* never synchronized.
*/
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
- if (minimumCapacity - value.length > 0)
+ int capacity = value.length >> coder;
+ if (minimumCapacity - capacity > 0) {
expandCapacity(minimumCapacity);
}
+ }
/**
* This implements the expansion semantics of ensureCapacity with no
* size check or synchronization.
*/
- void expandCapacity(int minimumCapacity) {
- int newCapacity = value.length * 2 + 2;
- if (newCapacity - minimumCapacity < 0)
+ private void expandCapacity(int minimumCapacity) {
+ int newCapacity = (value.length >> coder) * 2 + 2;
+ if (newCapacity - minimumCapacity < 0) {
newCapacity = minimumCapacity;
+ }
if (newCapacity < 0) {
- if (minimumCapacity < 0) // overflow
+ if (minimumCapacity < 0) {// overflow
throw new OutOfMemoryError();
+ }
newCapacity = Integer.MAX_VALUE;
}
- value = Arrays.copyOf(value, newCapacity);
+ if (coder != LATIN1 && newCapacity > StringUTF16.MAX_LENGTH) {
+ if (minimumCapacity >= StringUTF16.MAX_LENGTH) {
+ throw new OutOfMemoryError();
+ }
+ newCapacity = StringUTF16.MAX_LENGTH;
+ }
+ this.value = Arrays.copyOf(value, newCapacity << coder);
+ }
+
+ /**
+ * If the coder is "isLatin1", this inflates the internal 8-bit storage
+ * to 16-bit <hi=0, low> pair storage.
+ */
+ private void inflate() {
+ if (!isLatin1()) {
+ return;
+ }
+ byte[] buf = StringUTF16.newBytesFor(value.length);
+ StringLatin1.inflateSB(value, buf, 0, count);
+ this.value = buf;
+ this.coder = UTF16;
}
/**
* Attempts to reduce storage used for the character sequence.
* If the buffer is larger than necessary to hold its current sequence of
* characters, then it may be resized to become more space efficient.
* Calling this method may, but is not required to, affect the value
* returned by a subsequent call to the {@link #capacity()} method.
*/
public void trimToSize() {
- if (count < value.length) {
- value = Arrays.copyOf(value, count);
+ int length = count << coder;
+ if (length < value.length) {
+ value = Arrays.copyOf(value, length);
}
}
/**
* Sets the length of the character sequence.
@@ -177,18 +220,21 @@
* @param newLength the new length
* @throws IndexOutOfBoundsException if the
* {@code newLength} argument is negative.
*/
public void setLength(int newLength) {
- if (newLength < 0)
+ if (newLength < 0) {
throw new StringIndexOutOfBoundsException(newLength);
+ }
ensureCapacityInternal(newLength);
-
if (count < newLength) {
- Arrays.fill(value, count, newLength, '\0');
+ if (isLatin1()) {
+ StringLatin1.fillNull(value, count, newLength);
+ } else {
+ StringUTF16.fillNull(value, count, newLength);
+ }
}
-
count = newLength;
}
/**
* Returns the {@code char} value in this sequence at the specified index.
@@ -207,13 +253,15 @@
* @throws IndexOutOfBoundsException if {@code index} is
* negative or greater than or equal to {@code length()}.
*/
@Override
public char charAt(int index) {
- if ((index < 0) || (index >= count))
- throw new StringIndexOutOfBoundsException(index);
- return value[index];
+ checkIndex(index, count);
+ if (isLatin1()) {
+ return (char)(value[index] & 0xff);
+ }
+ return StringUTF16.charAt(value, index);
}
/**
* Returns the character (Unicode code point) at the specified
* index. The index refers to {@code char} values
@@ -234,14 +282,15 @@
* @exception IndexOutOfBoundsException if the {@code index}
* argument is negative or not less than the length of this
* sequence.
*/
public int codePointAt(int index) {
- if ((index < 0) || (index >= count)) {
- throw new StringIndexOutOfBoundsException(index);
+ checkIndex(index, count);
+ if (isLatin1()) {
+ return value[index] & 0xff;
}
- return Character.codePointAtImpl(value, index, count);
+ return StringUTF16.codePointAtSB(value, index, count);
}
/**
* Returns the character (Unicode code point) before the specified
* index. The index refers to {@code char} values
@@ -263,14 +312,17 @@
* argument is less than 1 or greater than the length
* of this sequence.
*/
public int codePointBefore(int index) {
int i = index - 1;
- if ((i < 0) || (i >= count)) {
+ if (i < 0 || i >= count) {
throw new StringIndexOutOfBoundsException(index);
}
- return Character.codePointBeforeImpl(value, index, 0);
+ if (isLatin1()) {
+ return value[i] & 0xff;
+ }
+ return StringUTF16.codePointBeforeSB(value, index);
}
/**
* Returns the number of Unicode code points in the specified text
* range of this sequence. The text range begins at the specified
@@ -293,11 +345,14 @@
*/
public int codePointCount(int beginIndex, int endIndex) {
if (beginIndex < 0 || endIndex > count || beginIndex > endIndex) {
throw new IndexOutOfBoundsException();
}
- return Character.codePointCountImpl(value, beginIndex, endIndex - beginIndex);
+ if (isLatin1()) {
+ return endIndex - beginIndex;
+ }
+ return StringUTF16.codePointCountSB(value, beginIndex, endIndex);
}
/**
* Returns the index within this sequence that is offset from the
* given {@code index} by {@code codePointOffset} code
@@ -319,11 +374,11 @@
*/
public int offsetByCodePoints(int index, int codePointOffset) {
if (index < 0 || index > count) {
throw new IndexOutOfBoundsException();
}
- return Character.offsetByCodePointsImpl(value, 0, count,
+ return Character.offsetByCodePoints(this,
index, codePointOffset);
}
/**
* Characters are copied from this sequence into the
@@ -353,17 +408,18 @@
* {@code dst.length}
* </ul>
*/
public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
{
- if (srcBegin < 0)
- throw new StringIndexOutOfBoundsException(srcBegin);
- if ((srcEnd < 0) || (srcEnd > count))
- throw new StringIndexOutOfBoundsException(srcEnd);
- if (srcBegin > srcEnd)
- throw new StringIndexOutOfBoundsException("srcBegin > srcEnd");
- System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
+ checkRangeSIOOBE(srcBegin, srcEnd, count); // compatible to old version
+ int n = srcEnd - srcBegin;
+ checkRange(dstBegin, dstBegin + n, dst.length);
+ if (isLatin1()) {
+ StringLatin1.getCharsSB(value, srcBegin, srcEnd, dst, dstBegin);
+ } else {
+ StringUTF16.getCharsSB(value, srcBegin, srcEnd, dst, dstBegin);
+ }
}
/**
* The character at the specified index is set to {@code ch}. This
* sequence is altered to represent a new character sequence that is
@@ -377,13 +433,19 @@
* @param ch the new character.
* @throws IndexOutOfBoundsException if {@code index} is
* negative or greater than or equal to {@code length()}.
*/
public void setCharAt(int index, char ch) {
- if ((index < 0) || (index >= count))
- throw new StringIndexOutOfBoundsException(index);
- value[index] = ch;
+ checkIndex(index, count);
+ if (isLatin1() && StringLatin1.canEncode(ch)) {
+ value[index] = (byte)ch;
+ } else {
+ if (isLatin1()) {
+ inflate();
+ }
+ StringUTF16.putCharSB(value, index, ch);
+ }
}
/**
* Appends the string representation of the {@code Object} argument.
* <p>
@@ -416,65 +478,74 @@
*
* @param str a string.
* @return a reference to this object.
*/
public AbstractStringBuilder append(String str) {
- if (str == null)
+ if (str == null) {
return appendNull();
+ }
int len = str.length();
ensureCapacityInternal(count + len);
- str.getChars(0, len, value, count);
+ putStringAt(count, str);
count += len;
return this;
}
// Documentation in subclasses because of synchro difference
public AbstractStringBuilder append(StringBuffer sb) {
- if (sb == null)
- return appendNull();
- int len = sb.length();
- ensureCapacityInternal(count + len);
- sb.getChars(0, len, value, count);
- count += len;
- return this;
+ return this.append((AbstractStringBuilder)sb);
}
/**
* @since 1.8
*/
AbstractStringBuilder append(AbstractStringBuilder asb) {
- if (asb == null)
+ if (asb == null) {
return appendNull();
+ }
int len = asb.length();
ensureCapacityInternal(count + len);
- asb.getChars(0, len, value, count);
+ if (getCoder() != asb.getCoder()) {
+ inflate();
+ }
+ asb.getBytes(value, count, coder);
count += len;
return this;
}
// Documentation in subclasses because of synchro difference
@Override
public AbstractStringBuilder append(CharSequence s) {
- if (s == null)
+ if (s == null) {
return appendNull();
- if (s instanceof String)
+ }
+ if (s instanceof String) {
return this.append((String)s);
- if (s instanceof AbstractStringBuilder)
+ }
+ if (s instanceof AbstractStringBuilder) {
return this.append((AbstractStringBuilder)s);
-
+ }
return this.append(s, 0, s.length());
}
private AbstractStringBuilder appendNull() {
- int c = count;
- ensureCapacityInternal(c + 4);
- final char[] value = this.value;
- value[c++] = 'n';
- value[c++] = 'u';
- value[c++] = 'l';
- value[c++] = 'l';
- count = c;
+ ensureCapacityInternal(count + 4);
+ int count = this.count;
+ byte[] val = this.value;
+ if (isLatin1()) {
+ val[count++] = 'n';
+ val[count++] = 'u';
+ val[count++] = 'l';
+ val[count++] = 'l';
+ } else {
+ checkOffset(count + 4, val.length >> 1);
+ StringUTF16.putChar(val, count++, 'n');
+ StringUTF16.putChar(val, count++, 'u');
+ StringUTF16.putChar(val, count++, 'l');
+ StringUTF16.putChar(val, count++, 'l');
+ }
+ this.count = count;
return this;
}
/**
* Appends a subsequence of the specified {@code CharSequence} to this
@@ -505,25 +576,17 @@
* {@code start} is greater than {@code end} or
* {@code end} is greater than {@code s.length()}
*/
@Override
public AbstractStringBuilder append(CharSequence s, int start, int end) {
- if (s == null)
+ if (s == null) {
s = "null";
- if ((start < 0) || (start > end) || (end > s.length()))
- throw new IndexOutOfBoundsException(
- "start " + start + ", end " + end + ", s.length() "
- + s.length());
+ }
+ checkRange(start, end, s.length());
int len = end - start;
ensureCapacityInternal(count + len);
- if (s instanceof String) {
- ((String)s).getChars(start, end, value, count);
- } else {
- for (int i = start, j = count; i < end; i++, j++)
- value[j] = s.charAt(i);
- }
- count += len;
+ appendChars(s, start, end);
return this;
}
/**
* Appends the string representation of the {@code char} array
@@ -542,12 +605,11 @@
* @return a reference to this object.
*/
public AbstractStringBuilder append(char[] str) {
int len = str.length;
ensureCapacityInternal(count + len);
- System.arraycopy(str, 0, value, count, len);
- count += len;
+ appendChars(str, 0, len);
return this;
}
/**
* Appends the string representation of a subarray of the
@@ -570,14 +632,14 @@
* @throws IndexOutOfBoundsException
* if {@code offset < 0} or {@code len < 0}
* or {@code offset+len > str.length}
*/
public AbstractStringBuilder append(char str[], int offset, int len) {
- if (len > 0) // let arraycopy report AIOOBE for len < 0
+ int end = offset + len;
+ checkRange(offset, end, str.length);
ensureCapacityInternal(count + len);
- System.arraycopy(str, offset, value, count, len);
- count += len;
+ appendChars(str, offset, end);
return this;
}
/**
* Appends the string representation of the {@code boolean}
@@ -590,24 +652,43 @@
*
* @param b a {@code boolean}.
* @return a reference to this object.
*/
public AbstractStringBuilder append(boolean b) {
+ ensureCapacityInternal(count + (b ? 4 : 5));
+ int count = this.count;
+ byte[] val = this.value;
+ if (isLatin1()) {
if (b) {
- ensureCapacityInternal(count + 4);
- value[count++] = 't';
- value[count++] = 'r';
- value[count++] = 'u';
- value[count++] = 'e';
- } else {
- ensureCapacityInternal(count + 5);
- value[count++] = 'f';
- value[count++] = 'a';
- value[count++] = 'l';
- value[count++] = 's';
- value[count++] = 'e';
+ val[count++] = 't';
+ val[count++] = 'r';
+ val[count++] = 'u';
+ val[count++] = 'e';
+ } else {
+ val[count++] = 'f';
+ val[count++] = 'a';
+ val[count++] = 'l';
+ val[count++] = 's';
+ val[count++] = 'e';
+ }
+ } else {
+ if (b) {
+ checkOffset(count + 4, val.length >> 1);
+ StringUTF16.putChar(val, count++, 't');
+ StringUTF16.putChar(val, count++, 'r');
+ StringUTF16.putChar(val, count++, 'u');
+ StringUTF16.putChar(val, count++, 'e');
+ } else {
+ checkOffset(count + 5, val.length >> 1);
+ StringUTF16.putChar(val, count++, 'f');
+ StringUTF16.putChar(val, count++, 'a');
+ StringUTF16.putChar(val, count++, 'l');
+ StringUTF16.putChar(val, count++, 's');
+ StringUTF16.putChar(val, count++, 'e');
}
+ }
+ this.count = count;
return this;
}
/**
* Appends the string representation of the {@code char}
@@ -625,11 +706,18 @@
* @return a reference to this object.
*/
@Override
public AbstractStringBuilder append(char c) {
ensureCapacityInternal(count + 1);
- value[count++] = c;
+ if (isLatin1() && StringLatin1.canEncode(c)) {
+ value[count++] = (byte)c;
+ } else {
+ if (isLatin1()) {
+ inflate();
+ }
+ StringUTF16.putCharSB(value, count++, c);
+ }
return this;
}
/**
* Appends the string representation of the {@code int}
@@ -650,11 +738,17 @@
}
int appendedLength = (i < 0) ? Integer.stringSize(-i) + 1
: Integer.stringSize(i);
int spaceNeeded = count + appendedLength;
ensureCapacityInternal(spaceNeeded);
+ if (isLatin1()) {
Integer.getChars(i, spaceNeeded, value);
+ } else {
+ byte[] val = this.value;
+ checkOffset(spaceNeeded, val.length >> 1);
+ Integer.getCharsUTF16(i, spaceNeeded, val);
+ }
count = spaceNeeded;
return this;
}
/**
@@ -676,11 +770,17 @@
}
int appendedLength = (l < 0) ? Long.stringSize(-l) + 1
: Long.stringSize(l);
int spaceNeeded = count + appendedLength;
ensureCapacityInternal(spaceNeeded);
+ if (isLatin1()) {
Long.getChars(l, spaceNeeded, value);
+ } else {
+ byte[] val = this.value;
+ checkOffset(spaceNeeded, val.length >> 1);
+ Long.getCharsUTF16(l, spaceNeeded, val);
+ }
count = spaceNeeded;
return this;
}
/**
@@ -730,19 +830,17 @@
* @throws StringIndexOutOfBoundsException if {@code start}
* is negative, greater than {@code length()}, or
* greater than {@code end}.
*/
public AbstractStringBuilder delete(int start, int end) {
- if (start < 0)
- throw new StringIndexOutOfBoundsException(start);
- if (end > count)
+ if (end > count) {
end = count;
- if (start > end)
- throw new StringIndexOutOfBoundsException();
+ }
+ checkRangeSIOOBE(start, end, count);
int len = end - start;
if (len > 0) {
- System.arraycopy(value, start+len, value, start, count-end);
+ shift(end, -len);
count -= len;
}
return this;
}
@@ -764,24 +862,14 @@
* @return a reference to this object.
* @exception IllegalArgumentException if the specified
* {@code codePoint} isn't a valid Unicode code point
*/
public AbstractStringBuilder appendCodePoint(int codePoint) {
- final int count = this.count;
-
if (Character.isBmpCodePoint(codePoint)) {
- ensureCapacityInternal(count + 1);
- value[count] = (char) codePoint;
- this.count = count + 1;
- } else if (Character.isValidCodePoint(codePoint)) {
- ensureCapacityInternal(count + 2);
- Character.toSurrogates(codePoint, value, count);
- this.count = count + 2;
- } else {
- throw new IllegalArgumentException();
+ return append((char)codePoint);
}
- return this;
+ return append(Character.toChars(codePoint));
}
/**
* Removes the {@code char} at the specified position in this
* sequence. This sequence is shortened by one {@code char}.
@@ -798,13 +886,12 @@
* @throws StringIndexOutOfBoundsException if the {@code index}
* is negative or greater than or equal to
* {@code length()}.
*/
public AbstractStringBuilder deleteCharAt(int index) {
- if ((index < 0) || (index >= count))
- throw new StringIndexOutOfBoundsException(index);
- System.arraycopy(value, index+1, value, index, count-index-1);
+ checkIndex(index, count);
+ shift(index + 1, -1);
count--;
return this;
}
/**
@@ -825,26 +912,20 @@
* @throws StringIndexOutOfBoundsException if {@code start}
* is negative, greater than {@code length()}, or
* greater than {@code end}.
*/
public AbstractStringBuilder replace(int start, int end, String str) {
- if (start < 0)
- throw new StringIndexOutOfBoundsException(start);
- if (start > count)
- throw new StringIndexOutOfBoundsException("start > length()");
- if (start > end)
- throw new StringIndexOutOfBoundsException("start > end");
-
- if (end > count)
+ if (end > count) {
end = count;
+ }
+ checkRangeSIOOBE(start, end, count);
int len = str.length();
int newCount = count + len - (end - start);
ensureCapacityInternal(newCount);
-
- System.arraycopy(value, end, value, start + len, count - end);
- str.getChars(value, start);
+ shift(end, newCount - count);
count = newCount;
+ putStringAt(start, str);
return this;
}
/**
* Returns a new {@code String} that contains a subsequence of
@@ -905,17 +986,20 @@
* or {@code end} are negative or greater than
* {@code length()}, or {@code start} is
* greater than {@code end}.
*/
public String substring(int start, int end) {
- if (start < 0)
- throw new StringIndexOutOfBoundsException(start);
- if (end > count)
- throw new StringIndexOutOfBoundsException(end);
- if (start > end)
- throw new StringIndexOutOfBoundsException(end - start);
- return new String(value, start, end - start);
+ checkRangeSIOOBE(start, end, count);
+ if (isLatin1()) {
+ return StringLatin1.newString(value, start, end - start);
+ }
+ return StringUTF16.newStringSB(value, start, end - start);
+ }
+
+ private void shift(int offset, int n) {
+ System.arraycopy(value, offset << coder,
+ value, (offset + n) << coder, (count - offset) << coder);
}
/**
* Inserts the string representation of a subarray of the {@code str}
* array argument into this sequence. The subarray begins at the
@@ -938,20 +1022,16 @@
* {@code str.length}.
*/
public AbstractStringBuilder insert(int index, char[] str, int offset,
int len)
{
- if ((index < 0) || (index > length()))
- throw new StringIndexOutOfBoundsException(index);
- if ((offset < 0) || (len < 0) || (offset > str.length - len))
- throw new StringIndexOutOfBoundsException(
- "offset " + offset + ", len " + len + ", str.length "
- + str.length);
+ checkOffset(index, count);
+ checkRangeSIOOBE(offset, offset + len, str.length);
ensureCapacityInternal(count + len);
- System.arraycopy(value, index, value, index + len, count - index);
- System.arraycopy(str, offset, value, index, len);
+ shift(index, len);
count += len;
+ putCharsAt(index, str, offset, offset + len);
return this;
}
/**
* Inserts the string representation of the {@code Object}
@@ -1006,19 +1086,19 @@
* @param str a string.
* @return a reference to this object.
* @throws StringIndexOutOfBoundsException if the offset is invalid.
*/
public AbstractStringBuilder insert(int offset, String str) {
- if ((offset < 0) || (offset > length()))
- throw new StringIndexOutOfBoundsException(offset);
- if (str == null)
+ checkOffset(offset, count);
+ if (str == null) {
str = "null";
+ }
int len = str.length();
ensureCapacityInternal(count + len);
- System.arraycopy(value, offset, value, offset + len, count - offset);
- str.getChars(value, offset);
+ shift(offset, len);
count += len;
+ putStringAt(offset, str);
return this;
}
/**
* Inserts the string representation of the {@code char} array
@@ -1043,17 +1123,16 @@
* @param str a character array.
* @return a reference to this object.
* @throws StringIndexOutOfBoundsException if the offset is invalid.
*/
public AbstractStringBuilder insert(int offset, char[] str) {
- if ((offset < 0) || (offset > length()))
- throw new StringIndexOutOfBoundsException(offset);
+ checkOffset(offset, count);
int len = str.length;
ensureCapacityInternal(count + len);
- System.arraycopy(value, offset, value, offset + len, count - offset);
- System.arraycopy(str, 0, value, offset, len);
+ shift(offset, len);
count += len;
+ putCharsAt(offset, str, 0, len);
return this;
}
/**
* Inserts the specified {@code CharSequence} into this sequence.
@@ -1075,14 +1154,16 @@
* @param s the sequence to be inserted
* @return a reference to this object.
* @throws IndexOutOfBoundsException if the offset is invalid.
*/
public AbstractStringBuilder insert(int dstOffset, CharSequence s) {
- if (s == null)
+ if (s == null) {
s = "null";
- if (s instanceof String)
+ }
+ if (s instanceof String) {
return this.insert(dstOffset, (String)s);
+ }
return this.insert(dstOffset, s, 0, s.length());
}
/**
* Inserts a subsequence of the specified {@code CharSequence} into
@@ -1127,26 +1208,22 @@
* {@code start} or {@code end} are negative, or
* {@code start} is greater than {@code end} or
* {@code end} is greater than {@code s.length()}
*/
public AbstractStringBuilder insert(int dstOffset, CharSequence s,
- int start, int end) {
- if (s == null)
+ int start, int end)
+ {
+ if (s == null) {
s = "null";
- if ((dstOffset < 0) || (dstOffset > this.length()))
- throw new IndexOutOfBoundsException("dstOffset "+dstOffset);
- if ((start < 0) || (end < 0) || (start > end) || (end > s.length()))
- throw new IndexOutOfBoundsException(
- "start " + start + ", end " + end + ", s.length() "
- + s.length());
+ }
+ checkOffset(dstOffset, count);
+ checkRange(start, end, s.length());
int len = end - start;
ensureCapacityInternal(count + len);
- System.arraycopy(value, dstOffset, value, dstOffset + len,
- count - dstOffset);
- for (int i=start; i<end; i++)
- value[dstOffset++] = s.charAt(i);
+ shift(dstOffset, len);
count += len;
+ putCharsAt(dstOffset, s, start, end);
return this;
}
/**
* Inserts the string representation of the {@code boolean}
@@ -1189,14 +1266,22 @@
* @param c a {@code char}.
* @return a reference to this object.
* @throws IndexOutOfBoundsException if the offset is invalid.
*/
public AbstractStringBuilder insert(int offset, char c) {
+ checkOffset(offset, count);
ensureCapacityInternal(count + 1);
- System.arraycopy(value, offset, value, offset + 1, count - offset);
- value[offset] = c;
+ shift(offset, 1);
count += 1;
+ if (isLatin1() && StringLatin1.canEncode(c)) {
+ value[offset] = (byte)c;
+ } else {
+ if (isLatin1()) {
+ inflate();
+ }
+ StringUTF16.putCharSB(value, offset, c);
+ }
return this;
}
/**
* Inserts the string representation of the second {@code int}
@@ -1324,11 +1409,11 @@
* @return the index of the first occurrence of the specified substring,
* starting at the specified index,
* or {@code -1} if there is no such occurrence.
*/
public int indexOf(String str, int fromIndex) {
- return String.indexOf(value, 0, count, str, fromIndex);
+ return String.indexOf(value, coder, count, str, fromIndex);
}
/**
* Returns the index within this string of the last occurrence of the
* specified substring. The last occurrence of the empty string "" is
@@ -1364,11 +1449,11 @@
* @return the index of the last occurrence of the specified substring,
* searching backward from the specified index,
* or {@code -1} if there is no such occurrence.
*/
public int lastIndexOf(String str, int fromIndex) {
- return String.lastIndexOf(value, 0, count, str, fromIndex);
+ return String.lastIndexOf(value, coder, count, str, fromIndex);
}
/**
* Causes this character sequence to be replaced by the reverse of
* the sequence. If there are any surrogate pairs included in the
@@ -1390,38 +1475,51 @@
* a valid surrogate pair.
*
* @return a reference to this object.
*/
public AbstractStringBuilder reverse() {
- boolean hasSurrogates = false;
+ byte[] val = this.value;
+ int count = this.count;
+ int coder = this.coder;
int n = count - 1;
+ if (COMPACT_STRINGS && coder == LATIN1) {
for (int j = (n-1) >> 1; j >= 0; j--) {
int k = n - j;
- char cj = value[j];
- char ck = value[k];
- value[j] = ck;
- value[k] = cj;
+ byte cj = val[j];
+ val[j] = val[k];
+ val[k] = cj;
+ }
+ } else {
+ checkOffset(count, val.length >> 1);
+ boolean hasSurrogates = false;
+ for (int j = (n-1) >> 1; j >= 0; j--) {
+ int k = n - j;
+ char cj = StringUTF16.getChar(val, j);
+ char ck = StringUTF16.getChar(val, k);
+ StringUTF16.putChar(val, j, ck);
+ StringUTF16.putChar(val, k, cj);
if (Character.isSurrogate(cj) ||
Character.isSurrogate(ck)) {
hasSurrogates = true;
}
}
if (hasSurrogates) {
- reverseAllValidSurrogatePairs();
+ reverseAllValidSurrogatePairs(val, count);
+ }
}
return this;
}
/** Outlined helper method for reverse() */
- private void reverseAllValidSurrogatePairs() {
+ private void reverseAllValidSurrogatePairs(byte[] val, int count) {
for (int i = 0; i < count - 1; i++) {
- char c2 = value[i];
+ char c2 = StringUTF16.getChar(val, i);
if (Character.isLowSurrogate(c2)) {
- char c1 = value[i + 1];
+ char c1 = StringUTF16.getChar(val, i + 1);
if (Character.isHighSurrogate(c1)) {
- value[i++] = c1;
- value[i] = c2;
+ StringUTF16.putChar(val, i++, c1);
+ StringUTF16.putChar(val, i, c2);
}
}
}
}
@@ -1442,35 +1540,180 @@
* {@inheritDoc}
* @since 1.9
*/
@Override
public IntStream chars() {
+ byte[] val = this.value; int count = this.count; byte coder = this.coder;
+ checkOffset(count, val.length >> coder);
// Reuse String-based spliterator. This requires a supplier to
// capture the value and count when the terminal operation is executed
return StreamSupport.intStream(
- () -> new String.IntCharArraySpliterator(value, 0, count, 0),
+ () -> coder == LATIN1 ? new StringLatin1.CharsSpliterator(val, 0, count, 0)
+ : new StringUTF16.CharsSpliterator(val, 0, count, 0),
Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED,
false);
}
/**
* {@inheritDoc}
* @since 1.9
*/
@Override
public IntStream codePoints() {
+ byte[] val = this.value; int count = this.count; byte coder = this.coder;
+ checkOffset(count, val.length >> coder);
// Reuse String-based spliterator. This requires a supplier to
// capture the value and count when the terminal operation is executed
return StreamSupport.intStream(
- () -> new String.CodePointsSpliterator(value, 0, count, 0),
+ () -> coder == LATIN1 ? new StringLatin1.CharsSpliterator(val, 0, count, 0)
+ : new StringUTF16.CodePointsSpliterator(val, 0, count, 0),
Spliterator.ORDERED,
false);
}
/**
* Needed by {@code String} for the contentEquals method.
*/
- final char[] getValue() {
+ final byte[] getValue() {
return value;
}
+ /*
+ * Invoker guarantees it is in UTF16 (inflate itself for asb), if two
+ * coders are different and the dstBegin has enough space
+ *
+ * @param dstBegin the char index, not offset of byte[]
+ * @param coder the coder of dst[]
+ */
+ protected void getBytes(byte dst[], int dstBegin, byte coder) {
+ if (this.coder == coder) {
+ System.arraycopy(value, 0, dst, dstBegin << coder, count << coder);
+ } else { // this.coder == LATIN && coder == UTF16
+ StringLatin1.inflateSB(value, dst, dstBegin, count);
+ }
+ }
+
+ /* for readObject() */
+ protected void initBytes(char[] value, int off, int len) {
+ if (String.COMPACT_STRINGS) {
+ this.value = StringUTF16.compress(value, off, len);
+ if (this.value != null) {
+ this.coder = LATIN1;
+ return;
+ }
+ }
+ this.coder = UTF16;
+ this.value = StringUTF16.toBytes(value, off, len);
+ }
+
+ final byte getCoder() {
+ return COMPACT_STRINGS ? coder : UTF16;
+ }
+
+ final boolean isLatin1() {
+ return COMPACT_STRINGS && coder == LATIN1;
+ }
+
+ private final void putCharsAt(int index, char[] s, int off, int end) {
+ if (isLatin1()) {
+ byte[] val = this.value;
+ for (int i = off, j = index; i < end; i++) {
+ char c = s[i];
+ if (StringLatin1.canEncode(c)) {
+ val[j++] = (byte)c;
+ } else {
+ inflate();
+ StringUTF16.putCharsSB(this.value, j, s, i, end);
+ return;
+ }
+ }
+ } else {
+ StringUTF16.putCharsSB(this.value, index, s, off, end);
+ }
+ }
+
+ private final void putCharsAt(int index, CharSequence s, int off, int end) {
+ if (isLatin1()) {
+ byte[] val = this.value;
+ for (int i = off, j = index; i < end; i++) {
+ char c = s.charAt(i);
+ if (StringLatin1.canEncode(c)) {
+ val[j++] = (byte)c;
+ } else {
+ inflate();
+ StringUTF16.putCharsSB(this.value, j, s, i, end);
+ return;
+ }
+ }
+ } else {
+ StringUTF16.putCharsSB(this.value, index, s, off, end);
+ }
+ }
+
+ private final void putStringAt(int index, String str) {
+ if (getCoder() != str.coder()) {
+ inflate();
+ }
+ byte[] val = this.value;
+ byte coder = this.coder;
+ checkOffset(index + str.length(), val.length >> coder);
+ str.getBytes(val, index, coder);
+ }
+
+ private final void appendChars(char[] s, int off, int end) {
+ if (isLatin1()) {
+ byte[] val = this.value;
+ for (int i = off, j = count; i < end; i++) {
+ char c = s[i];
+ if (StringLatin1.canEncode(c)) {
+ val[j++] = (byte)c;
+ } else {
+ count = j;
+ inflate();
+ StringUTF16.putCharsSB(this.value, j, s, i, end);
+ count += end - i;
+ return;
+ }
+ }
+ } else {
+ StringUTF16.putCharsSB(this.value, count, s, off, end);
+ }
+ count += end - off;
+ }
+
+ private final void appendChars(CharSequence s, int off, int end) {
+ if (isLatin1()) {
+ byte[] val = this.value;
+ for (int i = off, j = count; i < end; i++) {
+ char c = s.charAt(i);
+ if (StringLatin1.canEncode(c)) {
+ val[j++] = (byte)c;
+ } else {
+ count = j;
+ inflate();
+ StringUTF16.putCharsSB(this.value, j, s, i, end);
+ count += end - i;
+ return;
+ }
+ }
+ } else {
+ StringUTF16.putCharsSB(this.value, count, s, off, end);
+ }
+ count += end - off;
+ }
+
+ /* IndexOutOfBoundsException, if out of bounds */
+ private static void checkRange(int start, int end, int len) {
+ if (start < 0 || start > end || end > len) {
+ throw new IndexOutOfBoundsException(
+ "start " + start + ", end " + end + ", length " + len);
+ }
+ }
+
+ /* StringIndexOutOfBoundsException, if out of bounds */
+ private static void checkRangeSIOOBE(int start, int end, int len) {
+ if (start < 0 || start > end || end > len) {
+ throw new StringIndexOutOfBoundsException(
+ "start " + start + ", end " + end + ", length " + len);
+ }
+ }
}