/* * DSI utilities * * Copyright (C) 2002-2023 Paolo Boldi and Sebastiano Vigna * * This program and the accompanying materials are made available under the * terms of the GNU Lesser General Public License v2.1 or later, * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html, * or the Apache Software License 2.0, which is available at * https://www.apache.org/licenses/LICENSE-2.0. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. * * SPDX-License-Identifier: LGPL-2.1-or-later OR Apache-2.0 */ package it.unimi.dsi.lang; import java.io.DataInput; import java.io.DataOutput; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.PrintStream; import java.io.PrintWriter; import java.io.Reader; import java.io.Serializable; import java.io.UTFDataFormatException; import java.io.Writer; import it.unimi.dsi.fastutil.chars.Char2CharMap; import it.unimi.dsi.fastutil.chars.CharArrays; import it.unimi.dsi.fastutil.chars.CharList; import it.unimi.dsi.fastutil.chars.CharSet; import it.unimi.dsi.fastutil.objects.ObjectArrays; import it.unimi.dsi.util.TextPattern; /** * Fast, compact, optimized & versatile mutable strings. * *
* The classical Java string classes, {@link java.lang.String} and {@link java.lang.StringBuffer} * (or {@link StringBuilder}), lie at the extreme of a spectrum (immutable and mutable). * *
* However, large-scale text indexing requires some features that are not provided by these classes: * in particular, the possibility of using a mutable string, once frozen, in the same optimized way * of an immutable string. * *
* In a typical scenario you are dividing text into words (so you use a mutable string to
* accumulate characters). Once you've got your word, you would like to check whether this word is
* in a dictionary without creating a new object. However, equality of {@link StringBuilder
* string builders} is not defined on their content, and storing words after a conversion to
* String will not help either, as then you would need to convert the current mutable
* string into an immutable one (thus creating a new object) before deciding whether you need to
* store it.
*
*
* This class tries to make the best of both worlds, and thus aims at being a Better * Mousetrap™. * *
* You can read more details about the design of MutableString in Paolo Boldi and
* Sebastiano Vigna, “Mutable strings in
* Java: Design, implementation and lightweight text-search algorithms”, Sci. Comput.
* Programming, 54(1):3-23, 2005.
*
*
this, so you can chain
* methods as in s.length(0).append("foo").append("bar");
*
* String by using
* {@link #write(Writer)}, {@link #print(PrintWriter)} and {@link #println(PrintWriter)}; you can
* read it back using {@link #read(Reader,int)}.
*
* * Committing to use this class for such an ubiquitous data structure as strings may seem dangerous, * as standard string classes are by now tested and stable. However, this class has been heavily * regression (and torture) tested on all methods, and we believe it is very reliable. * *
* To simplify the transition to mutable strings, we have tried to make mixing string classes
* simpler by providing polymorphic versions of all methods accepting one or more
* strings—whenever you must specify a string you can usually provide a
* MutableString, a String, or a generic CharSequence.
*
*
* Note that usually we provide a specific method for String. This duplication may seem
* useless, as String implements CharSequence. However, invoking methods
* on an interface is slower than invoking methods on a class, and we expect constant strings to
* appear often in such methods.
*
*
* Backing array reallocations use a heuristic based on looseness. Whenever an operation changes the * length, compact strings are resized to fit exactly the new content, whereas the capacity * of a loose string is never shortened, and enlargements maximize the new length required with the * double of the current capacity. * *
* The effect of this policy is that loose strings will get large buffers quickly, but compact * strings will occupy little space and perform very well in data structures using hash codes. * *
* For instance, you can easily reuse a loose mutable string calling {@link #length(int) length(0)} * (which does not reallocate the backing array). * *
* In any case, you can call {@link #compact()} and {@link #loose()} to force the respective * condition. * *
* The main disadvantage of mutable strings is that their substrings cannot share their backing
* arrays, so if you need to generate many substrings you may want to use String.
* However, {@link #subSequence(int,int) subSequence()} returns a {@link CharSequence} that shares
* the backing array.
*
*
Strings and CharSequences, so that you
* can easily do checks like
*
*
* mutableString.equals("Hello")
*
*
* Thus, you must not mix mutable strings with CharSequences in collections as
* equality between objects of those types is not symmetric.
*
* null, used for implementing {@link Appendable}'s semantics. */
private final static MutableString NULL = new MutableString("null");
/** The backing array. */
protected transient char[] array;
/** This mutable string is compact iff this attribute is negative.
* It the string is compact, the attribute is its hash code (-1 denotes the invalid
* hash code). If the string is loose, the attribute is the number of
* characters actually stored in the backing array. */
protected transient int hashLength;
public static final long serialVersionUID = -518929984008928417L;
/** Creates a new loose empty mutable string with capacity 2. */
public MutableString() {
this(2);
}
/** Creates a new loose empty mutable string with given capacity.
*
* @param capacity the required capacity.
*/
public MutableString(final int capacity) {
array = capacity != 0 ? new char[capacity] : CharArrays.EMPTY_ARRAY;
}
/** Creates a new compact mutable string with given length.
*
* @param length the desired length of the new string.
*/
private void makeCompactMutableString(final int length) {
array = length != 0 ? new char[length] : CharArrays.EMPTY_ARRAY;
hashLength = -1;
}
/** Creates a new compact mutable string copying a given mutable string.
*
* @param s the initial contents of the string.
*/
public MutableString(final MutableString s) {
makeCompactMutableString(s.length());
System.arraycopy(s.array, 0, array, 0, array.length);
}
/** Creates a new compact mutable string copying a given String.
*
* @param s the initial contents of the string.
*/
public MutableString(final String s) {
makeCompactMutableString(s.length());
s.getChars(0, array.length, array, 0);
}
/** Creates a new compact mutable string copying a given CharSequence.
*
* @param s the initial contents of the string.
*/
public MutableString(final CharSequence s) {
makeCompactMutableString(s.length());
getChars(s, 0, array.length, array, 0);
}
/** Creates a new compact mutable string copying a given character array.
*
* @param a the initial contents of the string.
*/
public MutableString(final char[] a) {
makeCompactMutableString(a.length);
System.arraycopy(a, 0, array, 0, array.length);
}
/** Creates a new compact mutable string copying a part of a given character array.
*
* @param a a character array.
* @param offset an offset into the array.
* @param len how many characters to copy.
*/
public MutableString(final char[] a, final int offset, final int len) {
makeCompactMutableString(len);
System.arraycopy(a, offset, array, 0, len);
}
/** Creates a new compact mutable string by copying this one.
*
* @return a compact copy of this mutable string.
*/
public MutableString copy() {
return new MutableString(this);
}
/** Creates a new compact mutable string by copying this one.
*
* This method is identical to {@link #copy}, but the latter returns
* a more specific type.
*
* @return a compact copy of this mutable string.
*/
@Override
public Object clone() {
return new MutableString(this);
}
/** Commodity static method implementing {@link
* java.lang.String#getChars(int,int,char[],int)} for a CharSequences.
*
* @param s a CharSequence.
* @param start copy start from this index (inclusive).
* @param end copy ends at this index (exclusive).
* @param dest destination array.
* @param destStart the first character will be copied in dest[destStart].
* @see java.lang.String#getChars(int,int,char[],int)
*/
public static void getChars(final CharSequence s, final int start, final int end, final char[] dest, final int destStart) {
int j = destStart, i = start;
while(i < end) dest[j++] = s.charAt(i++);
}
/** Returns the number of characters in this mutable string.
*
* @return the length of this mutable string.
*/
@Override
public final int length() {
return hashLength >= 0 ? hashLength : array.length;
}
/** Returns whether this mutable string is empty.
*
* @return whether this mutable string is empty.
*/
public final boolean isEmpty() {
return (hashLength >= 0 ? hashLength : array.length) == 0;
}
/** Returns the current length of the backing array.
*
* @return the current length of the backing array.
*/
public final int capacity() {
return array.length;
}
/** Gets the backing array.
*
*
For fast, repeated access to the characters of this mutable string,
* you can obtain the actual backing array. Be careful, and if this mutable
* string is compact do not modify its backing array without
* calling {@link #changed()} immediately afterwards (or the cached hash
* code will get out of sync, with unforeseeable consequences).
*
* @see #changed()
* @return the backing array.
*/
public final char[] array() {
return array;
}
/** Characters with indices from start (inclusive) to
* index end (exclusive) are copied from this
* mutable string into the array dest, starting
* from index destStart.
*
* @param start copy start from this index (inclusive).
* @param end copy ends at this index (exclusive).
* @param dest destination array.
* @param destStart the first character will be copied in dest[destStart].
*
* @see String#getChars(int,int,char[],int)
* @throws NullPointerException if dest is {@code null}.
* @throws IndexOutOfBoundsException if any of the following is true:
*
start or end is negative
* start is greater than
* end
* end is greater than
* {@link #length()}
* end-start+destStart is greater than
* dest.length
* * The new capacity of this string will be exactly equal to the provided argument if this * mutable string is compact (this differs markedly from * {@link java.lang.StringBuffer#ensureCapacity(int) StringBuffer}). If this mutable string is * loose, the provided argument is maximized with the current capacity doubled. * *
* Note that if the given argument is greater than the current length, you will make this string * loose (see the {@linkplain MutableString class description}). * * @param minimumCapacity we want at least this number of characters, but no more. * @return this mutable string. */ public final MutableString ensureCapacity(final int minimumCapacity) { final int length = length(); expand(minimumCapacity); if (length < minimumCapacity) hashLength = length; return this; } /** Ensures that at least the given number of characters can be stored in this string. * *
If necessary, enlarges the backing array. If the string is compact, * we expand it exactly to the given capacity; otherwise, expand to the * maximum between the given capacity and the double of the current capacity. * *
This method works even with a {@code null} backing array (which * will be considered of length 0). * *
After a call to this method, we may be in an inconsistent state: if * you expand a compact string, {@link #hashLength} will be negative, * but there will be spurious characters in the string. Be sure to * fill them suitably. * * @param minimumCapacity we want at least this number of characters. */ private void expand(final int minimumCapacity) { final int c = array == null ? 0 : array.length; // This can happen only deserialising. if (minimumCapacity <= c && array != null) return; final int length = hashLength >= 0 ? hashLength : c; final char[] newArray = new char[ hashLength >= 0 && c * 2 > minimumCapacity ? c * 2 // loose : minimumCapacity // compact ]; if (length != 0) System.arraycopy(array, 0, newArray, 0, length); // We check because array could be null during deserialisation. array = newArray; } /** Ensures that exactly the given number of characters can be stored in this string. * *
If necessary, reallocates the backing array. If the new capacity is smaller than * the string length, the string will be truncated. * *
After a call to this method, we may be in an inconsistent state: if * you expand a compact string, {@link #hashLength} will be negative, * but there will be additional NUL characters in the string. Be sure to * substitute them suitably. * * @param capacity we want exactly this number of characters. */ private void setCapacity(final int capacity) { final int c = array.length; if (capacity == c) return; final int length = hashLength >= 0 ? hashLength : c; final char[] newArray = capacity != 0 ? new char[capacity] : CharArrays.EMPTY_ARRAY; System.arraycopy(array, 0, newArray, 0, length < capacity ? length : capacity); array = newArray; } /** Sets the length. * *
If the provided length is greater than that of the current string, * the string is padded with zeros. If it is shorter, the string is * truncated to the given length. We do not reallocate the backing * array, to increase object reuse. Use rather {@link #compact()} for that * purpose. * *
Note that shortening a string will make it loose (see the {@linkplain * MutableString class description}). * * @param newLength the new length for this mutable string. * @return this mutable string. * @see #compact() */ public final MutableString length(final int newLength) { if (newLength < 0) throw new IllegalArgumentException("Negative length (" + newLength + ")"); if (hashLength < 0) { if (array.length == newLength) return this; hashLength = -1; setCapacity(newLength); // For compact strings, length and capacity coincide. } else { final int length = hashLength; if (newLength == length) return this; if (newLength > array.length) expand(newLength); // In this case, the array is already filled with zeroes. else if (newLength > length) java.util.Arrays.fill(array, length, newLength, '\0'); hashLength = newLength; } return this; } /** A nickname for {@link #length(int)}. * * @param newLength the new length for this mutable string. * @return this mutable string. * @see #length(int) */ public final MutableString setLength(final int newLength) { return length(newLength); } /** Makes this mutable string compact (see the {@linkplain MutableString class description}). * *
Note that this operation may require reallocating * the backing array (of course, with a shorter length). * * @return this mutable string. * @see #isCompact() */ public final MutableString compact() { if (hashLength >= 0) { setCapacity(hashLength); hashLength = -1; } return this; } /** Makes this mutable string loose. * * @return this mutable string. * @see #isLoose() */ public final MutableString loose() { if (hashLength < 0) hashLength = array.length; return this; } /** Returns whether this mutable string is compact (see the {@linkplain MutableString class description}). * * @return whether this mutable string is compact. * @see #compact() */ public final boolean isCompact() { return hashLength < 0; } /** Returns whether this mutable string is loose (see the {@linkplain MutableString class description}). * * @return whether this mutable string is loose. * @see #loose() */ public final boolean isLoose() { return hashLength >= 0; } /** Invalidates the current cached hash code if this mutable string is compact. * *
You will need to call this method only if you change the backing * array of a compact mutable string {@linkplain #array() directly}. * * @return this mutable string. */ public final MutableString changed() { if (hashLength < 0) hashLength = -1; return this; } /** Wraps a given character array in a compact mutable string. * *
The returned mutable string will be compact and backed by the given character array. * * @param a a character array. * @return a compact mutable string backed by the given array. */ static public MutableString wrap(final char a[]) { final MutableString s = new MutableString(0); s.array = a; s.hashLength = -1; return s; } /** Wraps a given character array for a given length in a loose mutable string. * *
The returned mutable string will be loose and backed by the given character array. * * @param a a character array. * @param length a length. * @return a loose mutable string backed by the given array with the given length. */ static public MutableString wrap(final char[] a, final int length) { final MutableString s = new MutableString(0); s.array = a; s.hashLength = length; return s; } /** Gets a character. * *
If you end up calling repeatedly this method, you * should consider using {@link #array()} instead. * * @param index the index of a character. * @return the chracter at that index. */ @Override public final char charAt(final int index) { if (index >= length()) throw new StringIndexOutOfBoundsException(index); return array[index]; } /** A nickname for {@link #charAt(int,char)}. * * @param index the index of a character. * @param c the new character. * @return this mutable string. * @see #charAt(int,char) */ public final MutableString setCharAt(final int index, final char c) { charAt(index, c); return this; } /** Sets the character at the given index. * *
If you end up calling repeatedly this method, you should consider * using {@link #array()} instead. * * @param index the index of a character. * @param c the new character. * @return this mutable string. */ public final MutableString charAt(final int index, final char c) { if (index >= length()) throw new StringIndexOutOfBoundsException(index); array[index] = c; changed(); return this; } /** Returns the first character of this mutable string. * * @return the first character. * @throws StringIndexOutOfBoundsException when called on the empty string. */ public final char firstChar() { if (length() == 0) throw new StringIndexOutOfBoundsException(0); return array[0]; } /** Returns the last character of this mutable string. * * @return the last character. * @throws ArrayIndexOutOfBoundsException when called on the empty string. */ public final char lastChar() { return array[length() - 1]; } /** Converts this string to a new character array. * * @return a newly allocated character array with the same length and content of this mutable string. */ public final char[] toCharArray() { return CharArrays.copy(array, 0, length()); } /** Returns a substring of this mutable string. * *
The creation of a substring implies the creation of a new backing * array. The returned mutable string will be compact. * * @param start first character of the substring (inclusive). * @param end last character of the substring (exclusive). * @return a substring defined as above. */ public final MutableString substring(final int start, final int end) { if (end > length()) throw new StringIndexOutOfBoundsException(end); return new MutableString(array, start, end - start); } /** Returns a substring of this mutable string. * * @param start first character of the substring (inclusive). * @return a substring ranging from the given position to the end of this string. * @see #substring(int,int) */ public final MutableString substring(final int start) { return substring(start, length()); } /** A class representing a subsequence. * *
Subsequences represented by this class share the backing array. Equality
* is content-based; hash codes are identical to those of a mutable string with the same content.
*/
private class SubSequence implements CharSequence {
final int from, to;
private SubSequence(final int from, final int to) {
this.from = from;
this.to = to;
}
@Override
public char charAt(final int index) { return array[from + index]; }
@Override
public int length() { return to - from; }
@Override
public CharSequence subSequence(final int start, final int end) {
if (start < 0) throw new StringIndexOutOfBoundsException(start);
if (end < start || end > length()) throw new StringIndexOutOfBoundsException(end);
return new SubSequence(this.from + start, this.from + end);
}
/** For convenience, the hash code of a subsequence is equal
* to that of a String with the same content with the
* 31st bit set.
*
* @return the hash code.
*/
@Override
public int hashCode() {
int h = 0;
final char[] a = array;
for (int i = from; i < to; i++) h = 31 * h + a[i];
return h | (1 << 31);
}
@Override
public boolean equals(final Object o) {
if (o instanceof CharSequence) {
final CharSequence s = (CharSequence)o;
int n = length();
if (n == s.length()) {
while(n-- != 0) if (charAt(n) != s.charAt(n)) return false;
return true;
}
}
return false;
}
@Override
public String toString() { return new String(array, from, to - from); }
}
/** Returns a subsequence of this mutable string.
*
*
Subsequences share the backing array. Thus, you should
* not use a subsequence after changing the characters of this
* mutable string between start and end.
*
*
Equality of CharSequences returned by this method is defined by
* content equality, and hash codes are identical to mutable strings with the same
* content. Thus, you can mix mutable strings and CharSequences
* returned by this method in data structures, as the contracts of {@link java.lang.Object#equals(Object) equals()} and
* {@link java.lang.Object#hashCode() hashCode()} are honoured.
*
* @param start first character of the subsequence (inclusive).
* @param end last character of the subsequence (exclusive).
* @return a subsequence defined as above.
* @see #substring(int,int)
*/
@Override
public final CharSequence subSequence(final int start, final int end) {
if (start < 0) throw new StringIndexOutOfBoundsException();
if (start > end || end > length()) throw new StringIndexOutOfBoundsException();
return new SubSequence(start, end);
}
/** Appends the given mutable string to this mutable string.
*
* @param s the mutable string to append.
* @return this mutable string.
*/
public final MutableString append(MutableString s) {
if (s == null) s = NULL;
final int l = s.length();
if (l == 0) return this;
final int newLength = length() + l;
expand(newLength);
System.arraycopy(s.array, 0, array, newLength - l, l);
hashLength = hashLength < 0 ? -1 : newLength;
return this;
}
/**
* Appends the given String to this mutable string.
*
* @param s a String ({@code null} is not allowed).
* @return this mutable string.
* @throws NullPointerException if the argument is {@code null}
*/
public final MutableString append(final String s) {
if (s == null) return append(NULL);
final int l = s.length();
if (l == 0) return this;
final int newLength = length() + l;
expand(newLength);
s.getChars(0, l, array, newLength - l);
hashLength = hashLength < 0 ? -1 : newLength;
return this;
}
/**
* Appends the given CharSequence to this mutable string.
*
* @param s a CharSequence or {@code null}.
* @return this mutable string.
*
* @see Appendable#append(java.lang.CharSequence)
*/
@Override
public final MutableString append(final CharSequence s) {
if (s == null) return append(NULL);
final int l = s.length();
if (l == 0) return this;
final int newLength = length() + l;
expand(newLength);
getChars(s, 0, l, array, newLength - l);
hashLength = hashLength < 0 ? -1 : newLength;
return this;
}
/**
* Appends a subsequence of the given CharSequence to this mutable string.
*
* Warning: the semantics of this method of that of
* {@link #append(char[], int, int)} are different.
*
* @param s a CharSequence or {@code null}.
* @param start the index of the first character of the subsequence to append.
* @param end the index of the character after the last character in the subsequence.
* @return this mutable string.
*
* @see Appendable#append(java.lang.CharSequence, int, int)
*/
@Override
public final MutableString append(final CharSequence s, final int start, final int end) {
if (s == null) return append(NULL, start, end);
final int len = end - start;
if (len < 0 || start < 0 || end > s.length())
throw new IndexOutOfBoundsException("start: " + start + " end: " + end + " length():" + s.length());
final int newLength = length() + len;
expand(newLength);
try {
getChars(s, start, end, array, newLength - len);
}
catch (final IndexOutOfBoundsException e) {
if (hashLength < 0) setCapacity(newLength - len);
else hashLength = newLength - len;
throw e;
}
if (len != 0) hashLength = hashLength < 0 ? -1 : newLength;
return this;
}
/**
* Appends the given character sequences to this mutable string using the given separator.
*
* @param a an array.
* @param offset the index of the first character sequence to append.
* @param length the number of character sequences to append.
* @param separator a separator that will be appended inbetween the character sequences.
* @return this mutable string.
*/
public final MutableString append(final CharSequence[] a, final int offset, final int length, final CharSequence separator) {
ObjectArrays.ensureOffsetLength(a, offset, length);
if (length == 0) return this;
// Precompute the length of the resulting string
int m = 0;
for(int i = 0; i < length; i++) m += a[offset + i].length();
final int separatorLength = separator.length();
m += (length - 1) * separatorLength;
final int l = length();
ensureCapacity(l + m);
m = 0;
for(int i = 0; i < length; i++) {
if (i != 0) {
getChars(separator, 0, separatorLength, array, l + m);
m += separatorLength;
}
getChars(a[i], 0, a[i + offset].length(), array, l + m);
m += a[i].length();
}
if (hashLength < 0) hashLength = -1;
else hashLength = l + m;
return this;
}
/** Appends the given character sequences to this mutable string using the given separator.
*
* @param a an array.
* @param separator a separator that will be appended inbetween the character sequences.
* @return this mutable string.
*/
public final MutableString append(final CharSequence[] a, final CharSequence separator) {
return append(a, 0, a.length, separator);
}
/** Appends the string representations of the given objects to this mutable string using the given separator.
*
* @param a an array of objects.
* @param offset the index of the first object to append.
* @param length the number of objects to append.
* @param separator a separator that will be appended inbetween the string representations of the given objects.
* @return this mutable string.
*/
public final MutableString append(final Object[] a, final int offset, final int length, final CharSequence separator) {
final String s[] = new String[a.length];
for(int i = 0; i < length; i++) s[i] = a[offset + i].toString();
return append(s, offset, length, separator);
}
/** Appends the string representations of the given objects to this mutable string using the given separator.
*
* @param a an array of objects.
* @param separator a separator that will be appended inbetween the string representations of the given objects.
* @return this mutable string.
*/
public final MutableString append(final Object[] a, final CharSequence separator) {
return append(a, 0, a.length, separator);
}
/** Appends the given character array to this mutable string.
*
* @param a an array to append.
* @return this mutable string.
*/
public final MutableString append(final char a[]) {
final int l = a.length;
if (l == 0) return this;
final int newLength = length() + l;
expand(newLength);
System.arraycopy(a, 0, array, newLength - l, l);
hashLength = hashLength < 0 ? -1 : newLength;
return this;
}
/** Appends a part of the given character array to this mutable string.
*
* Warning: the semantics of this method of that of
* {@link #append(CharSequence, int, int)} are different.
*
* @param a an array.
* @param offset the index of the first character to append.
* @param len the number of characters to append.
* @return this mutable string.
*/
public final MutableString append(final char[] a, final int offset, final int len) {
final int newLength = length() + len;
expand(newLength);
try {
System.arraycopy(a, offset, array, newLength - len, len);
}
catch(final IndexOutOfBoundsException e) {
if (hashLength < 0) setCapacity(newLength - len);
else hashLength = newLength - len;
throw e;
}
if (len != 0) hashLength = hashLength < 0 ? -1 : newLength;
return this;
}
/** Appends the given character list to this mutable string.
*
* @param list the list to append.
* @return this mutable string.
*/
public final MutableString append(final CharList list) {
final int l = list.size();
if (l == 0) return this;
final int newLength = length() + l;
expand(newLength);
list.getElements(0, array, newLength - l, l);
hashLength = hashLength < 0 ? -1 : newLength;
return this;
}
/** Appends a part of the given character list to this mutable string.
*
* Warning: the semantics of this method of that of
* {@link #append(CharSequence, int, int)} are different.
*
* @param list a character list.
* @param offset the index of the first character to append.
* @param len the number of characters to append.
* @return this mutable string.
*/
public final MutableString append(final CharList list, final int offset, final int len) {
final int newLength = length() + len;
expand(newLength);
try {
list.getElements(offset, array, newLength - len, len);
}
catch(final IndexOutOfBoundsException e) {
if (hashLength < 0) setCapacity(newLength - len);
else hashLength = newLength - len;
throw e;
}
if (len != 0) hashLength = hashLength < 0 ? -1 : newLength;
return this;
}
/** Appends a boolean to this mutable string.
*
* @param b the boolean to be appended.
* @return this mutable string.
*/
public final MutableString append(final boolean b) { return append(String.valueOf(b)); }
/** Appends a character to this mutable string.
*
*
Note that this method will reallocate the backing array of a compact
* mutable string for each character appended. Do not call it
* lightly.
*
* @param c a character.
* @return this mutable string.
*/
@Override
public final MutableString append(final char c) {
final int newLength = length() + 1;
expand(newLength);
array[newLength - 1] = c;
hashLength = hashLength < 0 ? -1 : newLength;
return this;
}
/** Appends an integer to this mutable string.
*
* @param i the integer to be appended.
* @return this mutable string.
*/
public final MutableString append(final int i) { return append(String.valueOf(i)); }
/** Appends a long to this mutable string.
*
* @param l the long to be appended.
* @return this mutable string.
*/
public final MutableString append(final long l) { return append(String.valueOf(l)); }
/** Appends a float to this mutable string.
*
* @param f the float to be appended.
* @return this mutable string.
*/
public final MutableString append(final float f) { return append(String.valueOf(f)); }
/** Appends a double to this mutable string.
*
* @param d the double to be appended.
* @return this mutable string.
*/
public final MutableString append(final double d) { return append(String.valueOf(d)); }
/** Appends the string representation of an object to this mutable string.
*
* @param o the object to append.
* @return a reference to this mutable string.
*/
public final MutableString append(final Object o) {
return append(String.valueOf(o));
}
/** Inserts a mutable string in this mutable string, starting from index index.
*
* @param index position at which to insert the String.
* @param s the mutable string to be inserted.
* @return this mutable string.
* @throws NullPointerException if s is {@code null}
* @throws IndexOutOfBoundsException if index
* is negative or greater than {@link #length()}.
*/
public final MutableString insert(final int index, final MutableString s) {
final int length = length();
if (index > length) throw new StringIndexOutOfBoundsException();
final int l = s.length();
if (l == 0) return this;
final int newLength = length + l;
expand(newLength);
System.arraycopy(array, index, array, index + l, length - index);
System.arraycopy(s.array, 0, array, index, l);
hashLength = hashLength < 0 ? -1 : newLength;
return this;
}
/** Inserts a String in this mutable string, starting from index index.
*
* @param index position at which to insert the String.
* @param s the String to be inserted.
* @return this mutable string.
* @throws NullPointerException if s is {@code null}
* @throws IndexOutOfBoundsException if index
* is negative or greater than {@link #length()}.
*/
public final MutableString insert(final int index, final String s) {
final int length = length();
if (index > length) throw new StringIndexOutOfBoundsException();
final int l = s.length();
if (l == 0) return this;
final int newLength = length + l;
expand(newLength);
System.arraycopy(array, index, array, index + l, length - index);
s.getChars(0, l, array, index);
hashLength = hashLength < 0 ? -1 : newLength;
return this;
}
/** Inserts a CharSequence in this mutable string, starting from index index.
*
* @param index position at which to insert the CharSequence.
* @param s the CharSequence to be inserted.
* @return this mutable string.
* @throws NullPointerException if s is {@code null}
* @throws IndexOutOfBoundsException if index
* is negative or greater than {@link #length()}.
*/
public final MutableString insert(final int index, final CharSequence s) {
final int length = length();
if (index > length) throw new StringIndexOutOfBoundsException();
final int l = s.length();
if (l == 0) return this;
final int newLength = length + l;
if (newLength >= array.length) expand(newLength);
System.arraycopy(array, index, array, index + l, length - index);
getChars(s, 0, l, array, index);
hashLength = hashLength < 0 ? -1 : newLength;
return this;
}
/** Inserts characters in this mutable string. All of the characters
* of the array c are inserted in this mutable string,
* and the first inserted character is going to have index index.
*
* @param index position at which to insert subarray.
* @param c the character array.
* @return this mutable string.
* @throws NullPointerException if c is {@code null}
* @throws IndexOutOfBoundsException if index
* is negative or greater than {@link #length()}.
*/
public final MutableString insert(final int index, final char[] c) {
final int length = length();
if (index > length) throw new StringIndexOutOfBoundsException();
final int l = c.length;
if (l == 0) return this;
final int newLength = length + l;
expand(newLength);
System.arraycopy(array, index, array, index + l, length - index);
System.arraycopy(c, 0, array, index, l);
hashLength = hashLength < 0 ? -1 : newLength;
return this;
}
/** Inserts characters in this mutable string. len characters
* of the array c, with indices starting from
* offset, are inserted in this mutable string,
* and the first inserted character is going to have index index.
*
* @param index position at which to insert subarray.
* @param c the character array.
* @param offset the index of the first character of c to
* to be inserted.
* @param len the number of characters of c to
* to be inserted.
* @return this mutable string.
* @throws NullPointerException if c is {@code null}
* @throws IndexOutOfBoundsException if index
* is negative or greater than {@link #length()}, or
* offset or len are negative, or
* offset+len is greater than
* c.length.
*/
public final MutableString insert(final int index, final char[] c, final int offset, final int len) {
final int length = length();
if (index > length) throw new StringIndexOutOfBoundsException();
if (offset < 0 || offset + len < 0 || offset + len > c.length) throw new StringIndexOutOfBoundsException(offset);
if (len == 0) return this;
final int newLength = length + len;
expand(newLength);
System.arraycopy(array, index, array, index + len, length - index);
System.arraycopy(c, offset, array, index, len);
hashLength = hashLength < 0 ? -1 : newLength;
return this;
}
/** Inserts a boolean in this mutable string, starting from index index.
*
* @param index position at which to insert the String.
* @param b the boolean to be inserted.
* @return this mutable string.
* @throws IndexOutOfBoundsException if index
* is negative or greater than {@link #length()}.
*/
public final MutableString insert(final int index, final boolean b) { return insert(index, String.valueOf(b)); }
/** Inserts a char in this mutable string, starting from index index.
*
*
Note that this method will not expand the capacity of a compact
* mutable string by more than one character. Do not call it
* lightly.
*
* @param index position at which to insert the String.
* @param c the char to be inserted.
* @return this mutable string.
* @throws IndexOutOfBoundsException if index
* is negative or greater than {@link #length()}.
*/
public final MutableString insert(final int index, final char c) { return insert(index, String.valueOf(c)); }
/** Inserts a double in this mutable string, starting from index index.
*
* @param index position at which to insert the String.
* @param d the double to be inserted.
* @return this mutable string.
* @throws IndexOutOfBoundsException if index
* is negative or greater than {@link #length()}.
*/
public final MutableString insert(final int index, final double d) { return insert(index, String.valueOf(d)); }
/** Inserts a float in this mutable string, starting from index index.
*
* @param index position at which to insert the String.
* @param f the float to be inserted.
* @return this mutable string.
* @throws IndexOutOfBoundsException if index
* is negative or greater than {@link #length()}.
*/
public final MutableString insert(final int index, final float f) { return insert(index, String.valueOf(f)); }
/** Inserts an int in this mutable string, starting from index index.
*
* @param index position at which to insert the String.
* @param x the int to be inserted.
* @return this mutable string.
* @throws IndexOutOfBoundsException if index
* is negative or greater than {@link #length()}.
*/
public final MutableString insert(final int index, final int x) { return insert(index, String.valueOf(x)); }
/** Inserts a long in this mutable string, starting from index index.
*
* @param index position at which to insert the String.
* @param l the long to be inserted.
* @return this mutable string.
* @throws IndexOutOfBoundsException if index
* is negative or greater than {@link #length()}.
*/
public final MutableString insert(final int index, final long l) { return insert(index, String.valueOf(l)); }
/** Inserts the string representation of an object in this mutable string, starting from index index.
*
* @param index position at which to insert the String.
* @param o the object to be inserted.
* @return this mutable string.
* @throws IndexOutOfBoundsException if index
* is negative or greater than {@link #length()}.
*/
public final MutableString insert(final int index, final Object o) { return insert(index, String.valueOf(o)); }
/** Removes the characters of this mutable string with
* indices in the range from start (inclusive) to end
* (exclusive). If end is greater than or equal to the length
* of this mutable string, all characters with indices greater
* than or equal to start are deleted.
*
* @param start The beginning index (inclusive).
* @param end The ending index (exclusive).
* @return this mutable string.
* @throws IndexOutOfBoundsException if start
* is greater than {@link #length()}, or greater than end.
*/
public final MutableString delete(final int start, int end) {
final int length = length();
if (end > length) end = length;
if (start > end) throw new StringIndexOutOfBoundsException();
final int l = end - start;
if (l > 0) {
System.arraycopy(array, start + l, array, start, length - end);
if (hashLength < 0) {
setCapacity(length - l);
hashLength = -1;
}
else hashLength -= l;
}
return this;
}
/** Removes the character at the given index.
*
*
Note that this method will reallocate the backing array of a compact
* mutable string for each character deleted. Do not call it
* lightly.
*
* @param index Index of character to remove
* @return this mutable string.
* @throws IndexOutOfBoundsException if index
* is negative, or greater than or equal to {@link #length()}.
*/
public final MutableString deleteCharAt(final int index) {
final int length = length();
if (index >= length) throw new StringIndexOutOfBoundsException();
System.arraycopy(array, index + 1, array, index, length - index - 1);
if (hashLength < 0) {
setCapacity(length - 1);
hashLength = -1;
}
else hashLength--;
return this;
}
/** Removes all occurrences of the given character.
*
* @param c the character to remove.
* @return this mutable string.
*/
public final MutableString delete(final char c) {
final int length = length();
final char[] a = array;
int l = 0;
for(int i = 0; i < length; i++) if (a[i] != c) a[l++] = a[i];
if (l != length) if (hashLength < 0) {
hashLength = -1;
array = CharArrays.trim(array, l);
}
else hashLength = l;
return this;
}
/** Removes all occurrences of the given characters.
*
* @param s the set of characters to remove.
* @return this mutable string.
*/
public final MutableString delete(final CharSet s) {
final int length = length();
final char[] a = array;
int l = 0;
for(int i = 0; i < length; i++) if (! s.contains(a[i])) a[l++] = a[i];
if (l != length) if (hashLength < 0) {
hashLength = -1;
array = CharArrays.trim(array, l);
}
else hashLength = l;
return this;
}
/** Removes all occurrences of the given characters.
*
* @param c an array containing the characters to remove.
* @return this mutable string.
*/
public final MutableString delete(final char[] c) {
final int n = c.length;
if (n == 0) return this;
final char[] a = array;
final int length = length();
int i = length, k, bloomFilter = 0;
k = n;
while (k-- != 0) bloomFilter |= 1 << (c[k] & 0x1F);
int l = 0;
for(i = 0; i < length; i++) {
if ((bloomFilter & (1 << (a[i] & 0x1F))) != 0) {
k = n;
while (k-- != 0) if (a[i] == c[k]) break;
if (k >= 0) continue;
}
a[l++] = a[i];
}
if (l != length) if (hashLength < 0) {
hashLength = -1;
array = CharArrays.trim(array, l);
}
else hashLength = l;
return this;
}
/** Replaces the characters with indices ranging from start (inclusive)
* to end (exclusive) with the given mutable string.
*
* @param start The starting index (inclusive).
* @param end The ending index (exclusive).
* @param s The mutable string to be copied.
* @return this mutable string.
* @throws IndexOutOfBoundsException if start is negative,
* greater than length(), or greater than end.
*/
public final MutableString replace(final int start, int end, final MutableString s) {
final int length = length();
if (end > length) end = length;
if (start > end) throw new StringIndexOutOfBoundsException();
final int l = s.length();
final int newLength = length + l - end + start;
if (l == 0 && newLength == length) return this;
if (newLength >= length) {
expand(newLength);
System.arraycopy(array, end, array, start + l, length - end);
System.arraycopy(s.array, 0, array, start, l);
hashLength = hashLength < 0 ? -1 : newLength;
}
else {
System.arraycopy(array, end, array, start + l, length - end);
System.arraycopy(s.array, 0, array, start, l);
if (hashLength < 0) {
setCapacity(newLength);
hashLength = -1;
}
else hashLength = newLength;
}
return this;
}
/** Replaces the characters with indices ranging from start (inclusive)
* to end (exclusive) with the given String.
*
* @param start The starting index (inclusive).
* @param end The ending index (exclusive).
* @param s The String to be copied.
* @return this mutable string.
* @throws IndexOutOfBoundsException if start is negative,
* greater than length(), or greater than end.
*/
public final MutableString replace(final int start, int end, final String s) {
final int length = length();
if (end > length) end = length;
if (start > end) throw new StringIndexOutOfBoundsException();
final int l = s.length();
final int newLength = length + l - end + start;
if (l == 0 && newLength == length) return this;
if (newLength >= length) {
expand(newLength);
System.arraycopy(array, end, array, start + l, length - end);
s.getChars(0, l, array, start);
hashLength = hashLength < 0 ? -1 : newLength;
}
else {
System.arraycopy(array, end, array, start + l, length - end);
s.getChars(0, l, array, start);
if (hashLength < 0) {
setCapacity(newLength);
hashLength = -1;
}
else hashLength = newLength;
}
return this;
}
/** Replaces the characters with indices ranging from start (inclusive)
* to end (exclusive) with the given CharSequence.
*
* @param start The starting index (inclusive).
* @param end The ending index (exclusive).
* @param s The CharSequence to be copied.
* @return this mutable string.
* @throws IndexOutOfBoundsException if start is negative,
* greater than length(), or greater than end.
*/
public final MutableString replace(final int start, int end, final CharSequence s) {
final int length = length();
if (end > length) end = length;
if (start > end) throw new StringIndexOutOfBoundsException();
final int l = s.length();
final int newLength = length + l - end + start;
if (l == 0 && newLength == length) return this;
if (newLength >= length) {
expand(newLength);
System.arraycopy(array, end, array, start + l, length - end);
getChars(s, 0, l, array, start);
hashLength = hashLength < 0 ? -1 : newLength;
}
else {
System.arraycopy(array, end, array, start + l, length - end);
getChars(s, 0, l, array, start);
if (hashLength < 0) {
setCapacity(newLength);
hashLength = -1;
}
else hashLength = newLength;
}
return this;
}
/** Replaces the characters with indices ranging from start (inclusive)
* to end (exclusive) with the given character.
*
* @param start The starting index (inclusive).
* @param end The ending index (exclusive).
* @param c The character to be copied.
* @return this mutable string.
* @throws IndexOutOfBoundsException if start is negative,
* greater than length(), or greater than end.
*/
public final MutableString replace(final int start, int end, final char c) {
final int length = length();
if (end > length) end = length;
if (start > end) throw new StringIndexOutOfBoundsException();
final int newLength = length + 1 - end + start;
if (newLength >= length) {
expand(newLength);
System.arraycopy(array, end, array, start + 1, length - end);
array[start] = c;
hashLength = hashLength < 0 ? -1 : newLength;
}
else {
System.arraycopy(array, end, array, start + 1, length - end);
array[start] = c;
if (hashLength < 0) {
setCapacity(newLength);
hashLength = -1;
}
else hashLength = newLength;
}
return this;
}
/** Replaces the content of this mutable string with the given mutable string.
*
* @param s the mutable string whose content will replace the present one.
* @return this mutable string.
*/
public final MutableString replace(final MutableString s) {
return replace(0, Integer.MAX_VALUE, s);
}
/** Replaces the content of this mutable string with the given string.
*
* @param s the string whose content will replace the present one.
* @return this mutable string.
*/
public final MutableString replace(final String s) {
return replace(0, Integer.MAX_VALUE, s);
}
/** Replaces the content of this mutable string with the given character sequence.
*
* @param s the character sequence whose content will replace the present one.
* @return this mutable string.
*/
public final MutableString replace(final CharSequence s) {
return replace(0, Integer.MAX_VALUE, s);
}
/** Replaces the content of this mutable string with the given character.
*
* @param c the character whose content will replace the present content.
* @return this mutable string.
*/
public final MutableString replace(final char c) {
return replace(0, Integer.MAX_VALUE, c);
}
/** Replaces each occurrence of a set of characters with a corresponding mutable string.
*
*
Each occurrences of the character c[i] in this mutable
* string will be replaced by s[i]. Note that
* c and s must have the same length, and that
* c must not contain duplicates. Moreover, each replacement
* string must be nonempty.
*
*
This method uses a Bloom filter to avoid repeated linear scans of the * character array for {@link #length()} times; however, this optimisation * will be most effective with arrays of less than twenty characters, * and, in fact, will very slightly slow down replacements with more than one hundred characters. * *
This method will try at most one reallocation. * * @param c an array of characters to be replaced. * @param s an array of replacement mutable strings. * @return this mutable string. * @throws IllegalArgumentException if one of the replacement strings is empty. */ public final MutableString replace(final char[] c, final MutableString[] s) { final int n = c.length; if (n == 0) return this; final int length = length(); char[] a = array; int i, j, k, l, newLength = length, bloomFilter = 0; k = n; while(k-- != 0) { bloomFilter |= 1 << (c[k] & 0x1F); if (s[k].length() == 0) throw new IllegalArgumentException("You cannot use the empty string as a replacement"); } i = length; boolean found = false; while (i-- != 0) if ((bloomFilter & (1 << (a[i] & 0x1F))) != 0) { k = n; while (k-- != 0) if (a[i] == c[k]) break; if (k >= 0) { newLength += s[k].length() - 1; found = true; } } if (! found) return this; expand(newLength); a = array; i = newLength; // index in the new string j = length; // index in the old string while(j-- != 0) { if ((bloomFilter & (1 << (a[j] & 0x1F))) != 0) { k = n; while (k-- != 0) if (a[j] == c[k]) break; if (k >= 0) { l = s[k].length(); System.arraycopy(s[k].array, 0, array, i -= l, l); continue; } } a[--i] = a[j]; } hashLength = hashLength < 0 ? -1 : newLength; return this; } /** Replaces each occurrence of a set of characters with a corresponding string. * *
Each occurrences of the character c[i] in this mutable
* string will be replaced by s[i]. Note that
* c and s must have the same length, and that
* c must not contain duplicates. Moreover, each replacement
* string must be nonempty.
*
*
This method uses a Bloom filter to avoid repeated linear scans of the * character array for {@link #length()} times; however, this optimisation * will be most effective with arrays of less than twenty characters, * and, in fact, will very slightly slow down replacements with more than one hundred characters. * *
This method will try at most one reallocation. * * @param c an array of characters to be replaced. * @param s an array of replacement strings. * @return this mutable string. * @throws IllegalArgumentException if one of the replacement strings is empty. */ public final MutableString replace(final char[] c, final String[] s) { final int n = c.length; if (n == 0) return this; final int length = length(); char[] a = array; int i, j, k, l, newLength = length, bloomFilter = 0; k = n; while(k-- != 0) { bloomFilter |= 1 << (c[k] & 0x1F); if (s[k].length() == 0) throw new IllegalArgumentException("You cannot use the empty string as a replacement"); } i = length; boolean found = false; while (i-- != 0) if ((bloomFilter & (1 << (a[i] & 0x1F))) != 0) { k = n; while (k-- != 0) if (a[i] == c[k]) break; if (k >= 0) { newLength += s[k].length() - 1; found = true; } } if (! found) return this; expand(newLength); a = array; i = newLength; // index in the new string j = length; // index in the old string while(j-- != 0) { if ((bloomFilter & (1 << (a[j] & 0x1F))) != 0) { k = n; while (k-- != 0) if (a[j] == c[k]) break; if (k >= 0) { l = s[k].length(); getChars(s[k], 0, l, array, i -= l); continue; } } a[--i] = a[j]; } hashLength = hashLength < 0 ? -1 : newLength; return this; } /** Replaces each occurrence of a set of characters with a corresponding character sequence. * *
Each occurrences of the character c[i] in this mutable
* string will be replaced by s[i]. Note that
* c and s must have the same length, and that
* c must not contain duplicates. Moreover, each replacement
* sequence must be nonempty.
*
*
This method uses a Bloom filter to avoid repeated linear scans of the * character array for {@link #length()} times; however, this optimisation * will be most effective with arrays of less than twenty characters, * and, in fact, will very slightly slow down replacements with more than one hundred characters. * *
This method will try at most one reallocation. * * @param c an array of characters to be replaced. * @param s an array of replacement character sequences. * @return this mutable string. * @throws IllegalArgumentException if one of the replacement sequences is empty. */ public final MutableString replace(final char[] c, final CharSequence[] s) { final int n = c.length; if (n == 0) return this; final int length = length(); char[] a = array; int i, j, k, l, newLength = length, bloomFilter = 0; k = n; while(k-- != 0) { bloomFilter |= 1 << (c[k] & 0x1F); if (s[k].length() == 0) throw new IllegalArgumentException("You cannot use the empty string as a replacement"); } i = length; boolean found = false; while (i-- != 0) if ((bloomFilter & (1 << (a[i] & 0x1F))) != 0) { k = n; while (k-- != 0) if (a[i] == c[k]) break; if (k >= 0) { newLength += s[k].length() - 1; found = true; } } if (! found) return this; expand(newLength); a = array; i = newLength; // index in the new string j = length; // index in the old string while(j-- != 0) { if ((bloomFilter & (1 << (a[j] & 0x1F))) != 0) { k = n; while (k-- != 0) if (a[j] == c[k]) break; if (k >= 0) { l = s[k].length(); getChars(s[k], 0, l, array, i -= l); continue; } } a[--i] = a[j]; } hashLength = hashLength < 0 ? -1 : newLength; return this; } /** Replaces each occurrence of a set characters with a corresponding character. * *
Each occurrences of the character c[i] in this mutable
* string will be replaced by r[i]. Note that
* c and s must have the same length, and that
* c must not contain duplicates.
*
*
This method uses a Bloom filter to avoid repeated linear scans of the * character array for {@link #length()} times; however, this optimisation * will be most effective with arrays of less than twenty characters, * and, in fact, will very slightly slow down replacements with more than one hundred characters. * * @param c an array of characters to be replaced. * @param r an array of replacement characters. * @return this mutable string. */ public final MutableString replace(final char[] c, final char[] r) { final int n = c.length; if (n == 0) return this; final char[] a = array; int i = length(), k, bloomFilter = 0; k = n; while (k-- != 0) bloomFilter |= 1 << (c[k] & 0x1F); while (i-- != 0) if ((bloomFilter & (1 << (a[i] & 0x1F))) != 0) { k = n; while (k-- != 0) if (a[i] == c[k]) break; if (k >= 0) a[i] = r[k]; } if (hashLength < 0) hashLength = -1; return this; } /** Replaces characters following a replacement map. * *
Each occurrence of a key of m
* will be substituted with the corresponding value.
*
* @param m a map specifiying the character replacements.
* @return this mutable string.
*/
public final MutableString replace(final Char2CharMap m) {
final int length = length();
final char[] a = array;
boolean found = false;
for(int i = 0; i < length; i++)
if (m.containsKey(a[i])) {
a[i] = m.get(a[i]);
found = true;
}
if (found && hashLength < 0) hashLength = -1;
return this;
}
/** Replaces each occurrence of a character with a corresponding mutable string.
*
*
Each occurrences of the character c in this mutable
* string will be replaced by s. Note that s
* must be nonempty.
*
*
This method will try at most one reallocation. * * @param c a character to be replaced. * @param s a replacement mutable string. * @return this mutable string. * @throws IllegalArgumentException if the replacement string is empty. */ public final MutableString replace(final char c, final MutableString s) { final int l = length(); final int sl = s.length(); char[] a = array; int i, j, newLength = l; if (sl == 0) throw new IllegalArgumentException("You cannot use the empty string as a replacement"); i = l; boolean found = false; while (i-- != 0) if (a[i] == c) { newLength += sl - 1; found = true; } if (! found) return this; expand(newLength); a = array; i = newLength; // index in the new string j = l; // index in the old string int last = l; int d; while(j-- != 0) if (a[j] == c) { d = last - j - 1; System.arraycopy(a, j + 1, a, i -= d , d); System.arraycopy(s.array, 0, a, i -= sl, sl); last = j; } hashLength = hashLength < 0 ? -1 : newLength; return this; } /** Replaces each occurrence of a character with a corresponding string. * *
Each occurrences of the character c in this mutable
* string will be replaced by s. Note that s
* must be nonempty.
*
*
This method will try at most one reallocation. * * @param c a character to be replaced. * @param s a replacement string. * @return this mutable string. * @throws IllegalArgumentException if the replacement sequence is empty. */ public final MutableString replace(final char c, final String s) { final int l = length(); final int sl = s.length(); char[] a = array; int i, j, newLength = l; if (sl == 0) throw new IllegalArgumentException("You cannot use the empty string as a replacement"); i = l; boolean found = false; while (i-- != 0) if (a[i] == c) { newLength += sl - 1; found = true; } if (! found) return this; expand(newLength); a = array; i = newLength; // index in the new string j = l; // index in the old string int last = l; int d; while(j-- != 0) if (a[j] == c) { d = last - j - 1; System.arraycopy(a, j + 1, a, i -= d , d); s.getChars(0, sl, a, i -= sl); last = j; } hashLength = hashLength < 0 ? -1 : newLength; return this; } /** Replaces each occurrence of a character with a corresponding character sequence. * *
Each occurrences of the character c in this mutable
* string will be replaced by s. Note that s
* must be nonempty.
*
*
This method will try at most one reallocation. * * @param c a character to be replaced. * @param s a replacement character sequence. * @return this mutable string. * @throws IllegalArgumentException if the replacement sequence is empty. */ public final MutableString replace(final char c, final CharSequence s) { final int l = length(); final int sl = s.length(); char[] a = array; int i, j, newLength = l; if (sl == 0) throw new IllegalArgumentException("You cannot use the empty string as a replacement"); i = l; boolean found = false; while (i-- != 0) if (a[i] == c) { newLength += sl - 1; found = true; } if (! found) return this; expand(newLength); a = array; i = newLength; // index in the new string j = l; // index in the old string int last = l; int d; while(j-- != 0) if (a[j] == c) { d = last - j - 1; System.arraycopy(a, j + 1, a, i -= d , d); getChars(s, 0, sl, a, i -= sl); last = j; } hashLength = hashLength < 0 ? -1 : newLength; return this; } /** Replaces each occurrence of a character with a corresponding character. * * @param c a character to be replaced. * @param r a replacement character. * @return this mutable string. */ public final MutableString replace(final char c, final char r) { int i = length(); final char[] a = array; while (i-- != 0) if (a[i] == c) a[i] = r; changed(); return this; } /** Replaces each occurrence of a mutable string with a corresponding mutable string. * *
Each occurrences of the mutable string s in this mutable
* string will be replaced by r. Note that s
* must be nonempty, unless r is empty, too.
*
*
If the replacement string is longer than the search string, * occurrences of the search string are matched from the end of * this mutable string (i.e., using {@link #lastIndexOf(MutableString,int) * lastIndexOf()}). Otherwise, occurrences of the search string are matched * from the start (i.e., using {@link #indexOf(MutableString,int) * indexOf()}). This has no effect on the semantics, unless * there are overlapping occurrences. * *
This method will try at most one reallocation. * * @param s a mutable string to be replaced. * @param r a replacement mutable string. * @return this mutable string. * @throws IllegalArgumentException if you try to replace the empty string with a nonempty string. */ public final MutableString replace(final MutableString s, final MutableString r) { final int length = length(); final int ns = s.length(); final int nr = r.length(); if (ns == 0) { if (nr == 0) return this; throw new IllegalArgumentException("You cannot replace the empty string with a nonempty string"); } final char[] ar = r.array; final char[] as = s.array; final int bloomFilter = buildFilter(s, ns); final int diff = ns - nr; int i, j, l; if (diff >= 0) { // Replacement string is not longer than the search string. final char[] a = array; if ((i = indexOf(as, ns, 0, bloomFilter)) != -1) { System.arraycopy(ar, 0, a, i, nr); j = i + nr; // The current start of the hole. l = diff; // The current length of the hole. while((i = indexOf(as, ns, i + ns, bloomFilter)) != -1) { if (diff != 0) System.arraycopy(a, j + l, a, j, i - j - l); l += diff; j = i + ns - l; System.arraycopy(ar, 0, a, j - nr, nr); } if (diff != 0) System.arraycopy(a, j + l, a, j, length - l - j); l = length - l; if (hashLength < 0) { hashLength = -1; if (diff != 0) { final char[] newArray = new char[l]; System.arraycopy(a, 0, newArray, 0, l); array = newArray; } } else hashLength = l; } } else { // Replacement string is longer than the search string. j = 0; i = length; while((i = lastIndexOf(as, ns, i - ns, bloomFilter)) != -1) j++; if (j != 0) { final int m = l = length + j * - diff; expand(m); final char[] a = array; i = j = length; while((i = lastIndexOf(as, ns, i - ns, bloomFilter)) != -1) { System.arraycopy(a, i + ns, a, l -= j - i - ns, j - i - ns); System.arraycopy(ar, 0, a, l -= nr, nr); j = i; } if (hashLength < 0) hashLength = -1; else hashLength = m; } } return this; } /** Replaces each occurrence of a string with a corresponding string. * *
Each occurrences of the string s in this mutable
* string will be replaced by r. Note that s
* must be nonempty, unless r is empty, too.
*
*
This method will try at most one reallocation. * * @param s a string to be replaced. * @param r a replacement string. * @return this mutable string. * @throws IllegalArgumentException if you try to replace the empty string with a nonempty string. * @see #replace(MutableString,MutableString) */ public final MutableString replace(final String s, final String r) { final int length = length(); final int ns = s.length(); final int nr = r.length(); if (ns == 0) { if (nr == 0) return this; throw new IllegalArgumentException("You cannot replace the empty string with a nonempty string"); } final int bloomFilter = buildFilter(s, ns); final int diff = ns - nr; int i, j, l; if (diff >= 0) { // Replacement string is not longer than the search string. final char[] a = array; if ((i = indexOf(s, ns, 0, bloomFilter)) != -1) { r.getChars(0, nr, a, i); j = i + nr; // The current start of the hole. l = diff; // The current length of the hole. while((i = indexOf(s, ns, i + ns, bloomFilter)) != -1) { if (diff != 0) System.arraycopy(a, j + l, a, j, i - j - l); l += diff; j = i + ns - l; r.getChars(0, nr, a, j - nr); } if (diff != 0) System.arraycopy(a, j + l, a, j, length - l - j); l = length - l; if (hashLength < 0) { hashLength = -1; if (diff != 0) { final char[] newArray = new char[l]; System.arraycopy(a, 0, newArray, 0, l); array = newArray; } } else hashLength = l; } } else { // Replacement string is longer than the search string. j = 0; i = length; while((i = lastIndexOf(s, ns, i - ns, bloomFilter)) != -1) j++; if (j != 0) { final int m = l = length + j * - diff; expand(m); final char[] a = array; i = j = length; while((i = lastIndexOf(s, ns, i - ns, bloomFilter)) != -1) { System.arraycopy(a, i + ns, a, l -= j - i - ns, j - i - ns); r.getChars(0, nr, a, l -= nr); j = i; } if (hashLength < 0) hashLength = -1; else hashLength = m; } } return this; } /** Replaces each occurrence of a character sequence with a corresponding character sequence. * *
Each occurrences of the string s in this mutable
* string will be replaced by r. Note that s
* must be nonempty, unless r is empty, too.
*
*
This method will try at most one reallocation.
*
* @param s a character sequence to be replaced.
* @param r a replacement character sequence.
* @return this mutable string.
* @throws IllegalArgumentException if you try to replace the empty sequence with a nonempty sequence.
* @see #replace(MutableString,MutableString)
*/
public final MutableString replace(final CharSequence s, final CharSequence r) {
final int length = length();
final int ns = s.length();
final int nr = r.length();
if (ns == 0) {
if (nr == 0) return this;
throw new IllegalArgumentException("You cannot replace the empty string with a nonempty string");
}
final int bloomFilter = buildFilter(s, ns);
final int diff = ns - nr;
int i, j, l;
if (diff >= 0) { // Replacement string is not longer than the search string.
final char[] a = array;
if ((i = indexOf(s, ns, 0, bloomFilter)) != -1) {
getChars(r, 0, nr, a, i);
j = i + nr; // The current start of the hole.
l = diff; // The current length of the hole.
while((i = indexOf(s, ns, i + ns, bloomFilter)) != -1) {
if (diff != 0) System.arraycopy(a, j + l, a, j, i - j - l);
l += diff;
j = i + ns - l;
getChars(r, 0, nr, a, j - nr);
}
if (diff != 0) System.arraycopy(a, j + l, a, j, length - l - j);
l = length - l;
if (hashLength < 0) {
hashLength = -1;
if (diff != 0) {
final char[] newArray = new char[l];
System.arraycopy(a, 0, newArray, 0, l);
array = newArray;
}
}
else hashLength = l;
}
}
else { // Replacement string is longer than the search string.
j = 0;
i = length;
while((i = lastIndexOf(s, ns, i - ns, bloomFilter)) != -1) j++;
if (j != 0) {
final int m = l = length + j * - diff;
expand(m);
final char[] a = array;
i = j = length;
while((i = lastIndexOf(s, ns, i - ns, bloomFilter)) != -1) {
System.arraycopy(a, i + ns, a, l -= j - i - ns, j - i - ns);
getChars(r, 0, nr, a, l -= nr);
j = i;
}
if (hashLength < 0) hashLength = -1;
else hashLength = m;
}
}
return this;
}
/** Returns the index of the first occurrence of the
* specified character.
*
* @param c the character to look for.
* @return the index of the first occurrence of c, or
* -1, if the character never appears.
*/
public final int indexOf(final char c) {
return indexOf(c, 0);
}
/** Returns the index of the first occurrence of the
* specified character, starting at the specified index.
*
* @param c the character to look for.
* @param from the index from which the search must start.
* @return the index of the first occurrence of c, or
* -1, if the character never appears with index greater than
* or equal to from.
*/
public final int indexOf(final char c, final int from) {
final int length = length();
final char[] a = array;
int i = from < 0 ? -1 : from - 1;
while(++i < length) if (a[i] == c) return i;
return -1;
}
/** Computes a Bloom filter for the given character array using the given number of characters.
*
* @param s a character array.
* @param n the length of the prefix of s to use in building the filter.
* @return the Bloom filter for the specified characters.
*/
static private int buildFilter(final char[] s, final int n) {
int i = n, bloomFilter = 0;
while (i-- != 0) bloomFilter |= 1 << (s[i] & 0x1f);
return bloomFilter;
}
/** Returns the index of the first occurrence of the specified pattern, starting at the specified index.
*
*
This method is used internally by {@link MutableString} for different methods, such as
* {@link #indexOf(MutableString)} and {@link #replace(MutableString,MutableString)}.
*
* @param pattern the string to look for.
* @param n the number of valid characters in pattern.
* @param from the index from which the search must start.
* @param bloomFilter the Bloom filter for pattern.
* @return the index of the first occurrence of pattern after
* the first from characters, or -1, if the string never
* appears.
*/
private int indexOf(final char[] pattern, final int n, final int from, final int bloomFilter) {
final int m1 = length() - 1;
final char[] a = array, p = pattern;
final char last = p[n - 1];
int i, j, k;
i = (from < 0 ? 0 : from) + n - 1;
while (i < m1) {
if (a[i] == last) {
j = n - 1;
k = i;
while(j-- != 0 && a[--k] == p[j]);
if (j < 0) return k;
}
if ((bloomFilter & 1 << (a[++i] & 0x1f)) == 0) i += n;
}
// We unroll the last iteration because we cannot access a[++i].
if (i == m1) {
j = n;
while(j-- != 0) if (a[i--] != p[j]) return -1;
return i + 1;
}
return -1;
}
/**
* Returns the index of the first occurrence of the specified mutable string, starting at the
* specified index.
*
*
* This method uses a lightweight combination of Boyer–Moore's last-character heuristics and * Bloom filters. More precisely, instead of recording the last occurrence of all characters in the * pattern, we simply record in a small Bloom filter which characters belong to the pattern. Every * time there is a mismatch, we look at the character immediately following the pattern: if * it is not in the Bloom filter, besides moving to the next position we can additionally skip a * number of characters equal to the length of the pattern. * *
* Unless called with a pattern that saturates the filter, this method will usually outperform that
* of String.
*
* @param pattern the mutable string to look for.
* @param from the index from which the search must start.
* @return the index of the first occurrence of pattern after the first
* from characters, or -1, if the string never appears.
*/
public final int indexOf(final MutableString pattern, final int from) {
final int n = pattern.length();
if (n == 0) return from > length() ? length() : (from < 0 ? 0 : from);
if (n == 1) return indexOf(pattern.array[n - 1], from);
return indexOf(pattern.array, n, from, buildFilter(pattern.array, n));
}
/** Returns the index of the first occurrence of the specified mutable string.
*
* @param pattern the mutable string to look for.
* @return the index of the first occurrence of pattern, or
* -1, if the string never appears.
* @see #indexOf(MutableString,int)
*/
public final int indexOf(final MutableString pattern) {
return indexOf(pattern, 0);
}
/** Computes a Bloom filter for the given character sequence using the given number of characters.
*
* @param s a character sequence.
* @param n the length of the prefix of s to use in building the filter.
* @return the Bloom filter for the specified characters.
*/
static private int buildFilter(final CharSequence s, final int n) {
int i = n, bloomFilter = 0;
while (i-- != 0) bloomFilter |= 1 << (s.charAt(i) & 0x1f);
return bloomFilter;
}
/** Returns the index of the first occurrence of the specified character sequence, starting at the specified index.
*
*
This method is used internally by {@link MutableString} for different methods, such as
* {@link #indexOf(CharSequence)} and {@link #replace(CharSequence,CharSequence)}.
*
* @param pattern the character sequence to look for.
* @param n the number of valid characters in pattern.
* @param from the index from which the search must start.
* @param bloomFilter the Bloom filter for pattern.
* @return the index of the first occurrence of pattern after
* the first from characters, or -1, if the string never
* appears.
*/
private int indexOf(final CharSequence pattern, final int n, final int from, final int bloomFilter) {
final int m1 = length() - 1;
final char[] a = array;
final char last = pattern.charAt(n - 1);
int i, j, k;
i = (from < 0 ? 0 : from) + n - 1;
while (i < m1) {
if (a[i] == last) {
j = n - 1;
k = i;
while(j-- != 0 && a[--k] == pattern.charAt(j));
if (j < 0) return k;
}
if ((bloomFilter & 1 << (a[++i] & 0x1f)) == 0) i += n;
}
// We unroll the last iteration because we cannot access a[++i].
if (i == m1) {
j = n;
while(j-- != 0) if (a[i--] != pattern.charAt(j)) return -1;
return i + 1;
}
return -1;
}
/** Returns the index of the first occurrence of the specified character sequence, starting at the specified index.
*
*
Searching for a character sequence is slightly slower than {@link
* #indexOf(MutableString,int) searching for a mutable string}, as every
* character of the pattern must be accessed through a method
* call. Please consider wrapping pattern in a mutable string, or use
* {@link TextPattern} for repeated searches.
*
* @param pattern the character sequence to look for.
* @param from the index from which the search must start.
* @return the index of the first occurrence of pattern after
* the first from characters, or -1, if the string never
* appears.
* @see #indexOf(MutableString,int)
*/
public final int indexOf(final CharSequence pattern, final int from) {
final int n = pattern.length();
if (n == 0) return from > length() ? length() : (from < 0 ? 0 : from);
if (n == 1) return indexOf(pattern.charAt(n - 1), from);
return indexOf(pattern, n, from, buildFilter(pattern, n));
}
/** Returns the index of the first occurrence of the specified character sequence.
*
* @param pattern the character sequence to look for.
* @return the index of the first occurrence of pattern, or
* -1, if the string never appears.
* @see #indexOf(CharSequence,int)
*/
public final int indexOf(final CharSequence pattern) {
return indexOf(pattern, 0);
}
/** Returns the index of the first occurrence of the
* specified text pattern, starting at the specified index.
*
*
To use this method, you have to {@linkplain TextPattern#TextPattern(CharSequence, int) create
* a text pattern first}.
*
* @param pattern a compiled text pattern to be searched for.
* @param from the index from which the search must start.
* @return the index of the first occurrence of pattern, or
* -1, if no such character ever appears.
*/
public int indexOf(final TextPattern pattern, final int from) {
return pattern.search(array(), from);
}
/** Returns the index of the first occurrence of the specified text pattern, starting at the specified index.
*
* @param pattern a compiled text pattern to be searched for.
* @return the index of the first occurrence of pattern, or
* -1, if no such character ever appears.
* @see #indexOf(TextPattern, int)
*/
public int indexOf(final TextPattern pattern) {
return indexOf(pattern, 0);
}
/** Returns the index of the first occurrence of any of the specified characters, starting at the specified index.
*
* @param s a set of characters to be searched for.
* @param from the index from which the search must start.
* @return the index of the first occurrence of any of the characters in s, or
* -1, if no such character ever appears.
*/
public int indexOfAnyOf(final CharSet s, final int from) {
final int n = s.size();
if (n == 0) return -1;
if (n == 1) return indexOf(s.iterator().nextChar(), from);
final char[] a = array;
final int length = length();
int i = (from < 0 ? 0 : from) - 1;
while (++i < length) if (s.contains(a[i])) return i;
return -1;
}
/** Returns the index of the first occurrence of any of the specified characters.
*
* @param s a set of characters to be searched for.
* @return the index of the first occurrence of any of the characters in s, or
* -1, if no such character ever appears.
* @see #indexOfAnyOf(CharSet,int)
*/
public int indexOfAnyOf(final CharSet s) {
return indexOfAnyOf(s, 0);
}
/** Returns the index of the first occurrence of any of the specified characters, starting at the specified index.
*
*
This method uses a Bloom filter to avoid repeated linear scans of the
* array c; however, this optimisation
* will be most effective with arrays of less than twenty characters,
* and, in fact, will very slightly slow down searches for more than one hundred characters.
*
* @param n the number of valid characters in c.
* @param c an array of characters to be searched for.
* @param from the index from which the search must start.
* @param bloomFilter the Bloom filter for the characters in c.
* @return the index of the first occurrence of any of the characters in c, or
* -1, if no such character ever appears.
*/
private int indexOfAnyOf(final char[] c, final int n, final int from, final int bloomFilter) {
final int m = length();
if (n == 0) return -1;
final char[] a = array;
int i = (from < 0 ? 0 : from) - 1, k;
while (++i < m)
if ((bloomFilter & (1 << (a[i] & 0x1F))) != 0) {
k = n;
while (k-- != 0) if (a[i] == c[k]) return i;
}
return -1;
}
/** Returns the index of the first occurrence of any of the specified characters, starting at the specified index.
*
*
This method uses a Bloom filter to avoid repeated linear scans of the
* array c; however, this optimisation
* will be most effective with arrays of less than twenty characters,
* and, in fact, will very slightly slow down searches for more than one hundred characters.
*
* @param c an array of characters to be searched for.
* @param from the index from which the search must start.
* @return the index of the first occurrence of any of the characters in c, or
* -1, if no such character ever appears.
*/
public int indexOfAnyOf(final char[] c, final int from) {
final int n = c.length;
if (n == 0) return -1;
if (n == 1) return indexOf(c[0], from);
return indexOfAnyOf(c, n, from, buildFilter(c, n));
}
/** Returns the index of the first occurrence of any of the specified characters.
*
* @param c an array of characters to be searched for.
* @return the index of the first occurrence of any of the characters in c, or
* -1, if no such character ever appears.
* @see #indexOfAnyOf(char[],int)
*/
public int indexOfAnyOf(final char[] c) {
return indexOfAnyOf(c, 0);
}
/** Returns the index of the first occurrence of any character, except those specified, starting at the specified index.
*
* @param s a set of characters to be searched for.
* @param from the index from which the search must start.
* @return the index of the first occurrence of any of the characters not in s, or
* -1, if no such character ever appears.
*/
public int indexOfAnyBut(final CharSet s, final int from) {
final char[] a = array;
final int length = length();
int i = (from < 0 ? 0 : from) - 1;
while (++i < length) if (! s.contains(a[i])) return i;
return -1;
}
/** Returns the index of the first occurrence of any character, except those specified.
*
* @param s a set of characters to be searched for.
* @return the index of the first occurrence of any of the characters not in s, or
* -1, if no such character ever appears.
* @see #indexOfAnyBut(CharSet,int)
*/
public int indexOfAnyBut(final CharSet s) {
return indexOfAnyOf(s, 0);
}
/** Returns the index of the first occurrence of any character, except those specified, starting at the specified index.
*
*
This method uses a Bloom filter to avoid repeated linear scans of the
* array c; however, this optimisation
* will be most effective with arrays of less than twenty characters,
* and, in fact, will very slightly slow down searches for more than one hundred characters.
*
* @param n the number of valid characters in c.
* @param c an array of characters to be searched for.
* @param from the index from which the search must start (must be nonnegative).
* @param bloomFilter the Bloom filter for the characters in c.
* @return the index of the first occurrence of any of the characters not in c, or
* -1, if no such character ever appears.
*/
private int indexOfAnyBut(final char[] c, final int n, final int from, final int bloomFilter) {
final int m = length();
if (n == 0) return from < m ? from : -1;
final char[] a = array;
int i = (from < 0 ? 0 : from) - 1, k;
while (++i < m)
if ((bloomFilter & (1 << (a[i] & 0x1F))) != 0) {
k = n;
while (k-- != 0) if (a[i] == c[k]) break;
if (k == -1) return i;
}
else return i;
return -1;
}
/** Returns the index of the first occurrence of any character, except those specified, starting at the specified index.
*
*
This method uses a Bloom filter to avoid repeated linear scans of the
* array c; however, this optimisation
* will be most effective with arrays of less than twenty characters,
* and, in fact, will very slightly slow down searches for more than one hundred characters.
*
* @param c an array of characters to be searched for.
* @param from the index from which the search must start.
* @return the index of the first occurrence of any of the characters not in c, or
* -1, if no such character ever appears.
*/
public int indexOfAnyBut(final char[] c, final int from) {
final int n = c.length;
return indexOfAnyBut(c, n, from < 0 ? 0 : from, buildFilter(c, n));
}
/** Returns the index of the first occurrence of any character, except those specified.
*
* @param c an array of characters to be searched for.
* @return the index of the first occurrence of any of the characters not in c, or
* -1, if no such character ever appears.
* @see #indexOfAnyBut(char[],int)
*/
public int indexOfAnyBut(final char[] c) {
return indexOfAnyOf(c, 0);
}
/** Returns the index of the last occurrence of the
* specified character.
*
* @param c the character to look for.
* @return the index of the last occurrence of c, or
* -1, if the character never appears.
*/
public final int lastIndexOf(final char c) {
final char[] a = array;
int i = length();
while(i-- != 0) if (a[i] == c) return i;
return -1;
}
/** Returns the index of the last occurrence of the specified character, searching backward starting at the specified index.
*
* @param c the character to look for.
* @param from the index from which the search must start.
* @return the index of the last occurrence of c, or
* -1, if the character never appears with index smaller than
* or equal to from.
*/
public final int lastIndexOf(final char c, final int from) {
final char[] a = array;
if (from < 0) return -1;
int i = length();
if (from < i) i = from + 1;
if (i < 0) return -1;
while(i-- != 0) if (a[i] == c) return i;
return -1;
}
/** Returns the index of the last occurrence of the specified pattern, searching backward starting at the specified index.
*
*
This method is used internally by {@link MutableString} for different methods, such as
* {@link #lastIndexOf(MutableString)} and {@link #replace(MutableString,MutableString)}.
*
* @param pattern the string to look for.
* @param n the number of valid characters in pattern.
* @param from the index from which the search must start.
* @param bloomFilter the Bloom filter for pattern.
* @return the index of the last occurrence of pattern, or
* -1, if the pattern never appears with index
* smaller than or equal to from.
*/
private int lastIndexOf(final char[] pattern, final int n, final int from, final int bloomFilter) {
final char[] a = array, p = pattern;
final char first = p[0];
int i, j, k;
i = length() - n;
if (from < i) i = from;
while (i > 0) {
if (a[i] == first) {
j = n - 1;
k = 0;
while(j-- != 0 && a[++i] == p[++k]);
if (j < 0) return i - k;
i -= k;
}
if ((bloomFilter & 1 << (a[--i] & 0x1f)) == 0) i -= n;
}
// We unroll the last iteration because we cannot access a[++i].
if (i == 0) {
j = n;
while(j-- != 0) if (a[j] != p[j]) return -1;
return 0;
}
return -1;
}
/** Returns the index of the last occurrence of the specified mutable string, searching backward starting at the specified index.
*
* @param pattern the mutable string to look for.
* @param from the index from which the search must start.
* @return the index of the last occurrence of pattern, or
* -1, if the pattern never appears with index
* smaller than or equal to from.
*/
public final int lastIndexOf(final MutableString pattern, final int from) {
final int n = pattern.length();
if (from < 0) return -1;
if (n == 0) return from > length() ? length() : from;
if (n == 1) return lastIndexOf(pattern.array[0], from);
return lastIndexOf(pattern.array, n, from, buildFilter(pattern.array, n));
}
/** Returns the index of the last occurrence of the specified mutable string.
*
* @param pattern the mutable string to look for.
* @return the index of the last occurrence of pattern, or
* -1, if the pattern never appears.
* @see #lastIndexOf(MutableString,int)
*/
public final int lastIndexOf(final MutableString pattern) {
return lastIndexOf(pattern, length());
}
/** Returns the index of the last occurrence of the specified pattern, searching backward starting at the specified index.
*
*
This method is used internally by {@link MutableString} for different methods, such as
* {@link #lastIndexOf(MutableString)} and {@link #replace(MutableString,MutableString)}.
*
* @param pattern the string to look for.
* @param n the number of valid characters in pattern.
* @param from the index from which the search must start.
* @param bloomFilter the Bloom filter for pattern.
* @return the index of the last occurrence of pattern, or
* -1, if the pattern never appears with index
* smaller than or equal to from.
*/
private int lastIndexOf(final CharSequence pattern, final int n, final int from, final int bloomFilter) {
final char[] a = array;
final char first = pattern.charAt(0);
int i, j, k;
i = length() - n;
if (from < i) i = from;
while (i > 0) {
if (a[i] == first) {
j = n - 1;
k = 0;
while(j-- != 0 && a[++i] == pattern.charAt(++k));
if (j < 0) return i - k;
i -= k;
}
if ((bloomFilter & 1 << (a[--i] & 0x1f)) == 0) i -= n;
}
// We unroll the last iteration because we cannot access a[++i].
if (i == 0) {
j = n;
while(j-- != 0) if (a[j] != pattern.charAt(j)) return -1;
return 0;
}
return -1;
}
/** Returns the index of the last occurrence of the specified character sequence, searching backward starting at the specified index.
*
* @param pattern the character sequence to look for.
* @param from the index from which the search must start.
* @return the index of the last occurrence of pattern, or
* -1, if the pattern never appears with index
* smaller than or equal to from.
* @see #lastIndexOf(MutableString,int)
*/
public final int lastIndexOf(final CharSequence pattern, final int from) {
final int n = pattern.length();
if (from < 0) return -1;
if (n == 0) return from > length() ? length() : from;
if (n == 1) return lastIndexOf(pattern.charAt(0), from);
return lastIndexOf(pattern, n, from, buildFilter(pattern, n));
}
/** Returns the index of the last occurrence of the specified character sequence.
*
* @param pattern the character sequence to look for.
* @return the index of the last occurrence of pattern, or
* -1, if the pattern never appears.
* @see #lastIndexOf(CharSequence,int)
*/
public final int lastIndexOf(final CharSequence pattern) {
return lastIndexOf(pattern, length());
}
/** Returns the index of the last occurrence of any of the specified characters, searching backwards starting at the specified index.
*
*
This method uses a Bloom filter to avoid repeated linear scans of the
* array c; however, this optimisation
* will be most effective with arrays of less than twenty characters,
* and, in fact, will very slightly slow down searches for more than one hundred characters.
*
* @param s a set of characters to be searched for.
* @param from the index from which the search must start.
* @return the index of the last occurrence of any character in s, or
* -1, if no character in s ever appears with index
* smaller than or equal to from.
*/
public int lastIndexOfAnyOf(final CharSet s, final int from) {
if (from < 0) return -1;
final int n = s.size();
if (n == 0) return -1;
if (n == 1) return lastIndexOf(s.iterator().nextChar(), from);
final char[] a = array;
int i = length();
if (from < i) i = from + 1;
while (i-- > 0) if (s.contains(a[i])) return i;
return -1;
}
/** Returns the index of the last occurrence of any of the specified characters.
*
* @param s a set of characters to be searched for.
* @return the index of the last occurrence of any of the characters in s, or
* -1, if no such character ever appears.
* @see #lastIndexOfAnyOf(CharSet, int)
*/
public int lastIndexOfAnyOf(final CharSet s) {
return lastIndexOfAnyOf(s, length());
}
/** Returns the index of the last occurrence of any of the specified characters, searching backwards starting at the specified index.
*
*
This method uses a Bloom filter to avoid repeated linear scans of the
* array c; however, this optimisation
* will be most effective with arrays of less than twenty characters,
* and, in fact, will very slightly slow down searches for more than one hundred characters.
*
* @param c an array of characters to be searched for.
* @param n the length of c.
* @param from the index from which the search must start.
* @param bloomFilter the Bloom filter for the characters in c.
* @return the index of the last occurrence of any character in c, or
* -1, if no character in c ever appears with index
* smaller than or equal to from.
*/
private int lastIndexOfAnyOf(final char[] c, final int n, final int from, final int bloomFilter) {
if (n == 0) return -1;
final char[] a = array;
int i = length(), k;
if (from < i) i = from + 1;
while (i-- > 0)
if ((bloomFilter & (1 << (a[i] & 0x1F))) != 0) {
k = n;
while (k-- != 0) if (a[i] == c[k]) return i;
}
return -1;
}
/** Returns the index of the last occurrence of any of the specified characters, searching backwards starting at the specified index.
*
*
This method uses a Bloom filter to avoid repeated linear scans of the
* array c; however, this optimisation
* will be most effective with arrays of less than twenty characters,
* and, in fact, will very slightly slow down searches for more than one hundred characters.
*
* @param c an array of characters to be searched for.
* @param from the index from which the search must start.
* @return the index of the last occurrence of any character in c, or
* -1, if no character in c ever appears with index
* smaller than or equal to from.
*/
public int lastIndexOfAnyOf(final char[] c, final int from) {
final int n = c.length;
if (from < 0) return -1;
if (n == 0) return -1;
if (n == 1) return lastIndexOf(c[0], from);
return lastIndexOfAnyOf(c, n, from, buildFilter(c, n));
}
/** Returns the index of the last occurrence of any of the specified characters.
*
* @param c an array of characters to be searched for.
* @return the index of the last occurrence of any of the characters in c, or
* -1, if no such character ever appears.
* @see #lastIndexOfAnyOf(char[], int)
* */
public int lastIndexOfAnyOf(final char[] c) {
return lastIndexOfAnyOf(c, length());
}
/** Returns the index of the last occurrence of any character, except those specified, starting at the specified index.
*
*
This method uses a Bloom filter to avoid repeated linear scans of the
* array c; however, this optimisation
* will be most effective with arrays of less than twenty characters,
* and, in fact, will very slightly slow down searches for more than one hundred characters.
*
* @param s a set of characters to be searched for.
* @param from the index from which the search must start.
* @return the index of the last occurrence of any of the characters not in c, or
* -1, if no such character ever appears.
*/
public int lastIndexOfAnyBut(final CharSet s, final int from) {
if (from < 0) return -1;
final char[] a = array;
int i = length();
if (s.size() == 0) return from < i ? from : i - 1;
if (from < i) i = from + 1;
while (i-- > 0) if (! s.contains(a[i])) return i;
return -1;
}
/** Returns the index of the last occurrence of any character, except those specified.
*
* @param s a set of characters to be searched for.
* @return the index of the last occurrence of any of the characters not in s, or
* -1, if no such character ever appears.
* @see #lastIndexOfAnyBut(CharSet,int)
*/
public int lastIndexOfAnyBut(final CharSet s) {
return lastIndexOfAnyBut(s, length());
}
/** Returns the index of the last occurrence of any character, except those specified, starting at the specified index.
*
*
This method uses a Bloom filter to avoid repeated linear scans of the
* array c; however, this optimisation
* will be most effective with arrays of less than twenty characters,
* and, in fact, will very slightly slow down searches for more than one hundred characters.
*
* @param n the number of valid characters in c.
* @param c an array of characters to be searched for.
* @param from the index from which the search must start (must be nonnegative).
* @param bloomFilter the Bloom filter for the characters in c.
* @return the index of the last occurrence of any of the characters not in c, or
* -1, if no such character ever appears.
*/
private int lastIndexOfAnyBut(final char[] c, final int n, final int from, final int bloomFilter) {
final char[] a = array;
int i = length(), k;
if (i == 0) return -1;
if (n == 0) return from < i ? from : i - 1;
if (from < i) i = from + 1;
while (i-- != 0)
if ((bloomFilter & (1 << (a[i] & 0x1F))) != 0) {
k = n;
while (k-- != 0) if (a[i] == c[k]) break;
if (k == - 1) return i;
}
else return i;
return -1;
}
/** Returns the index of the last occurrence of any character, except those specified, starting at the specified index.
*
*
This method uses a Bloom filter to avoid repeated linear scans of the
* array c; however, this optimisation
* will be most effective with arrays of less than twenty characters,
* and, in fact, will very slightly slow down searches for more than one hundred characters.
*
* @param c an array of characters to be searched for.
* @param from the index from which the search must start.
* @return the index of the last occurrence of any of the characters not in c, or
* -1, if no such character ever appears.
*/
public int lastIndexOfAnyBut(final char[] c, final int from) {
if (from < 0) return -1;
final int n = c.length;
return lastIndexOfAnyBut(c, n, from, buildFilter(c, n));
}
/** Returns the index of the last occurrence of any character, except those specified.
*
* @param c an array of characters to be searched for.
* @return the index of the last occurrence of any of the characters not in c, or
* -1, if no such character ever appears.
* @see #lastIndexOfAnyBut(char[],int)
*/
public int lastIndexOfAnyBut(final char[] c) {
return lastIndexOfAnyBut(c, 0);
}
/** Spans a segment of this mutable string made of the specified characters.
*
* @param s a set of characters.
* @param from the index from which to span.
* @return the length of the maximal subsequence of this mutable string made of
* characters in s starting at from.
* @see #cospan(CharSet, int)
*/
public int span(final CharSet s, int from) {
final int length = length();
if (s.size() == 0) return 0;
final char[] a = array;
if (from < 0) from = 0;
int i = from - 1;
while (++i < length) if (! s.contains(a[i])) break;
return i - from;
}
/** Spans the initial segment of this mutable string made of the specified characters.
*
* @param s a set of characters.
* @return the length of the maximal initial subsequence of this mutable string made of
* characters in s.
* @see #span(CharSet, int)
*/
public int span(final CharSet s) {
return span(s, 0);
}
/** Spans a segment of this mutable string made of the specified characters.
*
*
This method uses a Bloom filter to avoid repeated linear scans of the
* array c; however, this optimisation
* will be most effective with arrays of less than twenty characters,
* and, in fact, will very slightly slow down searches for more than one hundred characters.
*
* @param c an array of characters.
* @param from the index from which to span.
* @return the length of the maximal subsequence of this mutable string made of
* characters in c starting at from.
* @see #cospan(char[], int)
*/
public int span(final char[] c, int from) {
final int length = length(), n = c.length;
if (n == 0) return 0;
final int bloomFilter = buildFilter(c, n);
final char[] a = array;
if (from < 0) from = 0;
int i = from - 1, k;
while (++i < length)
if ((bloomFilter & (1 << (a[i] & 0x1F))) != 0) {
k = n;
while (k-- != 0) if (a[i] == c[k]) break;
if (k == -1) return i - from;
}
else return i - from;
return i - from;
}
/** Spans the initial segment of this mutable string made of the specified characters.
*
* @param c an array of characters.
* @return the length of the maximal initial subsequence of this mutable string made of
* characters in c.
* @see #span(char[], int)
*/
public int span(final char[] c) {
return span(c, 0);
}
/** Spans a segment of this mutable string made of the complement of the specified characters.
*
* @param s a set of characters.
* @param from the index from which to span.
* @return the length of the maximal subsequence of this mutable string made of
* characters not in s starting at from.
* @see #span(CharSet, int)
*/
public int cospan(final CharSet s, int from) {
final int length = length();
if (s.size() == 0) return from < 0 ? length : (from < length ? length - from : 0);
final char[] a = array;
if (from < 0) from = 0;
int i = from - 1;
while (++i < length) if (s.contains(a[i])) break;
return i - from;
}
/** Spans the initial segment of this mutable string made of the complement of the specified characters.
*
* @param s a set of characters.
* @return the length of the maximal initial subsequence of this mutable string made of
* characters not in s.
* @see #cospan(CharSet, int)
*/
public int cospan(final CharSet s) {
return cospan(s, 0);
}
/** Spans a segment of this mutable string made of the complement of the specified characters.
*
*
This method uses a Bloom filter to avoid repeated linear scans of the
* array c; however, this optimisation
* will be most effective with arrays of less than twenty characters,
* and, in fact, will very slightly slow down searches for more than one hundred characters.
*
* @param c an array of characters.
* @param from the index from which to span.
* @return the length of the maximal subsequence of this mutable string made of
* characters not in c starting at from.
* @see #span(char[], int)
*/
public int cospan(final char[] c, int from) {
final int length = length(), n = c.length;
if (n == 0) return from < 0 ? length : (from < length ? length - from : 0);
final int bloomFilter = buildFilter(c, n);
final char[] a = array;
if (from < 0) from = 0;
int i = from - 1, k;
while (++i < length)
if ((bloomFilter & (1 << (a[i] & 0x1F))) != 0) {
k = n;
while (k-- != 0) if (a[i] == c[k]) break;
if (k != -1) return i - from;
}
return i - from;
}
/** Spans the initial segment of this mutable string made of the complement of the specified characters.
*
* @param c an array of characters.
* @return the length of the maximal initial subsequence of this mutable string made of
* characters not in c.
* @see #cospan(char[], int)
*/
public int cospan(final char[] c) {
return cospan(c, 0);
}
/** Returns whether this mutable string starts with the given mutable string.
*
* @param prefix a mutable string.
* @return true if this mutable string starts with prefix.
*/
public final boolean startsWith(final MutableString prefix) {
final int l = prefix.length();
if (l > length()) return false;
int i = l;
final char[] a1 = prefix.array;
final char[] a2 = array;
while(i-- != 0) if (a1[i] != a2[i]) return false;
return true;
}
/** Returns whether this mutable string starts with the given string.
*
* @param prefix a string.
* @return true if this mutable string starts with prefix.
*/
public final boolean startsWith(final String prefix) {
final int l = prefix.length();
if (l > length()) return false;
int i = l;
final char[] a = array;
while(i-- != 0) if (prefix.charAt(i) != a[i]) return false;
return true;
}
/** Returns whether this mutable string starts with the given character sequence.
*
* @param prefix a character sequence.
* @return true if this mutable string starts with prefix.
*/
public final boolean startsWith(final CharSequence prefix) {
final int l = prefix.length();
if (l > length()) return false;
int i = l;
final char[] a = array;
while(i-- != 0) if (prefix.charAt(i) != a[i]) return false;
return true;
}
/** Returns whether this mutable string starts with the given mutable string disregarding case.
*
*
* @param prefix a mutable string.
* @return true if this mutable string starts with prefix up to case.
*/
public final boolean startsWithIgnoreCase(final MutableString prefix) {
final int l = prefix.length();
if (l > length()) return false;
int i = l;
final char[] a1 = prefix.array;
final char[] a2 = array;
char c, d;
while(i-- != 0) {
c = Character.toLowerCase(Character.toUpperCase(a1[i]));
d = Character.toLowerCase(Character.toUpperCase(a2[i]));
if (c != d) return false;
}
return true;
}
/** Returns whether this mutable string starts with the given string disregarding case.
*
* @param prefix a string.
* @return true if this mutable string starts with prefix up to case.
*/
public final boolean startsWithIgnoreCase(final String prefix) {
final int l = prefix.length();
if (l > length()) return false;
int i = l;
final char[] a = array;
char c, d;
while(i-- != 0) {
c = Character.toLowerCase(Character.toUpperCase(a[i]));
d = Character.toLowerCase(Character.toUpperCase(prefix.charAt(i)));
if (c != d) return false;
}
return true;
}
/** Returns whether this mutable string starts with the given character sequence disregarding case.
*
* @param prefix a character sequence.
* @return true if this mutable string starts with prefix up to case.
*/
public final boolean startsWithIgnoreCase(final CharSequence prefix) {
final int l = prefix.length();
if (l > length()) return false;
int i = l;
final char[] a = array;
char c, d;
while(i-- != 0) {
c = Character.toLowerCase(Character.toUpperCase(a[i]));
d = Character.toLowerCase(Character.toUpperCase(prefix.charAt(i)));
if (c != d) return false;
}
return true;
}
/** Returns whether this mutable string ends with the given mutable string.
*
* @param suffix a mutable string.
* @return true if this mutable string ends with suffix.
*/
public final boolean endsWith(final MutableString suffix) {
final int l = suffix.length();
int length = length();
if (l > length) return false;
int i = l;
final char[] a1 = suffix.array;
final char[] a2 = array;
while(i-- != 0) if (a1[i] != a2[--length]) return false;
return true;
}
/** Returns whether this mutable string ends with the given string.
*
* @param suffix a string.
* @return true if this mutable string ends with suffix.
*/
public final boolean endsWith(final String suffix) {
final int l = suffix.length();
int length = length();
if (l > length) return false;
int i = l;
final char[] a = array;
while(i-- != 0) if (suffix.charAt(i) != a[--length]) return false;
return true;
}
/** Returns whether this mutable string ends with the given character sequence.
*
* @param suffix a character sequence.
* @return true if this mutable string ends with prefix.
*/
public final boolean endsWith(final CharSequence suffix) {
final int l = suffix.length();
int length = length();
if (l > length) return false;
int i = l;
final char[] a = array;
while(i-- != 0) if (suffix.charAt(i) != a[--length]) return false;
return true;
}
/** Returns whether this mutable string ends with the given mutable string disregarding case.
*
* @param suffix a mutable string.
* @return true if this mutable string ends with suffix up to case.
*/
public final boolean endsWithIgnoreCase(final MutableString suffix) {
final int l = suffix.length();
int length = length();
if (l > length) return false;
int i = l;
final char[] a1 = suffix.array;
final char[] a2 = array;
char c, d;
while(i-- != 0) {
c = Character.toLowerCase(Character.toUpperCase(a1[i]));
d = Character.toLowerCase(Character.toUpperCase(a2[--length]));
if (c != d) return false;
}
return true;
}
/** Returns whether this mutable string ends with the given string disregarding case.
*
* @param suffix a string.
* @return true if this mutable string ends with suffix up to case.
*/
public final boolean endsWithIgnoreCase(final String suffix) {
final int l = suffix.length();
int length = length();
if (l > length) return false;
int i = l;
final char[] a = array;
char c, d;
while(i-- != 0) {
c = Character.toLowerCase(Character.toUpperCase(suffix.charAt(i)));
d = Character.toLowerCase(Character.toUpperCase(a[--length]));
if (c != d) return false;
}
return true;
}
/** Returns whether this mutable string ends with the given character sequence disregarding case.
*
* @param suffix a character sequence.
* @return true if this mutable string ends with prefix up to case.
*/
public final boolean endsWithIgnoreCase(final CharSequence suffix) {
final int l = suffix.length();
int length = length();
if (l > length) return false;
int i = l;
final char[] a = array;
char c, d;
while(i-- != 0) {
c = Character.toLowerCase(Character.toUpperCase(suffix.charAt(i)));
d = Character.toLowerCase(Character.toUpperCase(a[--length]));
if (c != d) return false;
}
return true;
}
/** Converts all of the characters in this mutable string to lower
* case using the rules of the default locale.
* @return this mutable string.
*/
public final MutableString toLowerCase() {
int n = length();
final char[] a = array;
while(n-- != 0) a[n] = Character.toLowerCase(a[n]);
changed();
return this;
}
/** Converts all of the characters in this mutable string to upper
* case using the rules of the default locale.
* @return this mutable string.
*/
public final MutableString toUpperCase() {
int n = length();
final char[] a = array;
while(n-- != 0) a[n] = Character.toUpperCase(a[n]);
changed();
return this;
}
/** Trims all leading and trailing whitespace from this string.
* Whitespace here is any character smaller than '\u0020' (the ASCII space).
*
* @return this mutable string.
*/
public final MutableString trim() {
final int length = length();
final char[] a = array;
int i = 0;
if (length == 0) return this;
while (i < length && a[i] <= ' ') i++;
if (i == length) {
if (hashLength < 0) {
hashLength = -1;
array = CharArrays.EMPTY_ARRAY;
return this;
}
hashLength = 0;
return this;
}
int j = length;
while (a[--j] <= ' ');
final int newLength = j - i + 1;
if (length == newLength) return this;
System.arraycopy(array, i, array, 0, newLength);
if (hashLength < 0) {
setCapacity(newLength);
hashLength = -1;
}
else hashLength = newLength;
return this;
}
/** Trims all leading whitespace from this string.
* Whitespace here is any character smaller than '\u0020' (the ASCII space).
*
* @return this mutable string.
*/
public final MutableString trimLeft() {
final int length = length();
final char[] a = array;
int i = 0;
if (length == 0) return this;
while (i < length && a[i] <= ' ') i++;
if (i == length) {
if (hashLength < 0) {
hashLength = -1;
array = CharArrays.EMPTY_ARRAY;
return this;
}
hashLength = 0;
}
final int newLength = length - i;
if (length == newLength) return this;
System.arraycopy(array, i, array, 0, newLength);
if (hashLength < 0) {
setCapacity(newLength);
hashLength = -1;
}
else hashLength = newLength;
return this;
}
/** Trims all trailing whitespace from this string.
* Whitespace here is any character smaller than '\u0020' (the ASCII space).
*
* @return this mutable string.
*/
public final MutableString trimRight() {
final int length = length();
final char[] a = array;
if (length == 0) return this;
int j = length;
while (j-- != 0) if (a[j] > ' ') break;
final int newLength = j + 1;
if (length == newLength) return this;
if (hashLength < 0) {
setCapacity(newLength);
hashLength = -1;
}
else hashLength = newLength;
return this;
}
/**
* Squeezes and normalizes spaces in this mutable string. All subsequences of consecutive characters
* satisfying {@link Character#isSpaceChar(char)} (or {@link Character#isWhitespace(char)} if
* squeezeOnlyWhitespace is true) will be transformed into a single space.
*
* @param squeezeOnlyWhitespace if true, a space is defined by {@link Character#isWhitespace(char)};
* otherwise, a space is defined by {@link Character#isSpaceChar(char)}.
* @return this mutable string.
*/
public final MutableString squeezeSpaces(final boolean squeezeOnlyWhitespace) {
final int length = length();
final char[] a = array;
int i = 0, j = 0;
while(i < length)
if (! (squeezeOnlyWhitespace ? Character.isWhitespace(a[i]) : Character.isSpaceChar(a[i]))) a[j++] = a[i++];
else {
a[j++] = ' ';
while (i < length && (squeezeOnlyWhitespace ? Character.isWhitespace(a[i]) : Character.isSpaceChar(a[i]))) i++;
}
if (length == j) return this;
if (hashLength < 0) {
setCapacity(j);
hashLength = -1;
}
else hashLength = j;
return this;
}
/**
* Squeezes and normalizes whitespace in this mutable string. All subsequences of consecutive
* characters satisfying {@link Character#isWhitespace(char)} will be transformed into a single
* space.
*
* @return this mutable string.
* @see #squeezeSpace()
*/
public final MutableString squeezeWhitespace() {
return squeezeSpaces(true);
}
/**
* Squeezes and normalizes spaces in this mutable string. All subsequences of consecutive characters
* satisfying {@link Character#isSpaceChar(char)} will be transformed into a single space.
*
* @return this mutable string.
* @see #squeezeWhitespace()
*/
public final MutableString squeezeSpace() {
return squeezeSpaces(false);
}
/** The characters in this mutable string get reversed.
*
* @return this mutable string.
*/
public final MutableString reverse() {
final int k = length() - 1;
final char[] a = array;
char c;
int i = ((k - 1) >>> 1) + 1;
while(i-- != 0) {
c = a[i]; a[i] = a[k - i]; a[k - i] = c;
}
changed();
return this;
}
/** Writes this mutable string to a {@link Writer}.
*
* @param w a {@link Writer}.
* @throws IOException if thrown by the provided {@link Writer}.
*/
public final void write(final Writer w) throws IOException {
if (hashLength < 0) w.write(array);
else w.write(array, 0, hashLength);
}
/** Reads a mutable string that has been written by {@link #write(Writer)} from a {@link Reader}.
*
*
If length is smaller than the current capacity or the
* number of characters actually read is smaller than length
* the string will become loose.
*
* @param r a {@link Reader}.
* @param length the number of characters to read.
* @return The number of characters read, or -1 if the end of the stream has been reached.
* @throws IOException if thrown by the provided {@link Reader}.
*/
public final int read(final Reader r, final int length) throws IOException {
final boolean compact = hashLength < 0;
expand(length);
hashLength = 0; // In case of an exception, we empty the string.
final int result = r.read(array, 0, length);
// If the string was compact and we can make it again compact, we do it.
if (result < length) hashLength = result == -1 ? 0 : result;
else hashLength = compact && length == array.length ? -1 : length;
return result;
}
/** Prints this mutable string to a {@link PrintWriter}.
* @param w a {@link PrintWriter}.
*/
public final void print(final PrintWriter w) {
if (hashLength < 0) w.write(array);
else w.write(array, 0, hashLength);
}
/** Prints this mutable string to a {@link PrintWriter} and then terminates the line.
* @param w a {@link PrintWriter}.
*/
public final void println(final PrintWriter w) {
print(w);
w.println();
}
/** Prints this mutable string to a {@link PrintStream}.
* @param s a {@link PrintStream}.
*/
public final void print(final PrintStream s) {
if (hashLength < 0) s.print(array);
else s.print(toString());
}
/** Prints this mutable string to a {@link PrintStream} and then terminates the line.
* @param s a {@link PrintStream}.
*/
public final void println(final PrintStream s) {
print(s);
s.println();
}
/** Writes this mutable string in UTF-8 encoding.
*
*
The string is coded in UTF-8, not in * the {@linkplain DataOutput#writeUTF(String) Java modified UTF * representation}. Thus, an ASCII NUL is represented by a single zero. Watch out! * *
This method does not try to do any caching (in particular, it does
* not create any object). On non-buffered data outputs it might be very
* slow.
*
* @param s a data output.
* @throws IOException if s does.
* @deprecated This method will not process surrogate pairs correctly.
*/
@Deprecated
public final void writeUTF8(final DataOutput s) throws IOException {
writeUTF8(s, length());
}
/** Writes this mutable string in UTF-8 encoding.
*
*
This method is not particularly efficient; in particular, it does not
* do any buffering (it will call {@link DataOutput#write(int)} for each
* byte). Have a look at {@link java.nio.charset.Charset} for more efficient ways of
* encoding strings.
*
* @param s a data output.
* @param length the length of this string (i.e., {@link #length()}).
* @throws IOException if s does.
* @deprecated This method will not process surrogate pairs correctly.
*/
@Deprecated
private void writeUTF8(final DataOutput s, final int length) throws IOException {
final char[] a = array;
char c;
for (int i = 0; i < length; i++) {
c = a[i];
if (c <= 127) s.write(c); // ASCII
else if (c >= 0x800) {
s.write((byte) (0xE0 | ((c >> 12) & 0x0F)));
s.write((byte) (0x80 | ((c >> 6) & 0x3F)));
s.write((byte) (0x80 | ((c >> 0) & 0x3F)));
}
else {
s.write((byte) (0xC0 | ((c >> 6) & 0x1F)));
s.write((byte) (0x80 | ((c >> 0) & 0x3F)));
}
}
}
/** Reads a mutable string in UTF-8 encoding.
*
*
This method does not try to do any read-ahead (in particular, it does * not create any object). On non-buffered data inputs it might be very * slow. * *
This method is able to read only strings containing UTF-8 sequences * of at most 3 bytes. Longer sequences will cause a {@link UTFDataFormatException}. * *
If length is smaller than the current capacity,
* the string will become loose.
*
* @param s a data input.
* @param length the number of characters to read.
* @return this mutable string.
* @throws UTFDataFormatException on UTF-8 sequences longer than three octects.
* @throws IOException if s does.
* @deprecated This method will not process surrogate pairs correctly.
*/
@Deprecated
public final MutableString readUTF8(final DataInput s, final int length) throws IOException {
final boolean compact = hashLength < 0;
expand(length);
final char[] a = array;
int b, c, d;
for(int i = 0; i < length; i++) {
b = s.readByte() & 0xFF;
switch (b >> 4) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
a[i] = (char)b; // ASCII
break;
case 12:
case 13:
c = s.readByte() & 0xFF;
if ((c & 0xC0) != 0x80) throw new UTFDataFormatException();
a[i] = (char)(((b & 0x1F) << 6) | (c & 0x3F));
break;
case 14:
c = s.readByte() & 0xFF;
d = s.readByte() & 0xFF;
if ((c & 0xC0) != 0x80 || (d & 0xC0) != 0x80) throw new UTFDataFormatException();
a[i] = (char)(((b & 0x0F) << 12) | ((c & 0x3F) << 6) | ((d & 0x3F) << 0));
break;
default:
throw new UTFDataFormatException();
}
}
// If the string was compact and we can make it again compact, we do it.
hashLength = compact && length == a.length ? -1 : length;
return this;
}
/** Writes this mutable string to a {@link DataOutput} as a
* length followed by a UTF-8 encoding.
*
*
The purpose of this method and of {@link * #readSelfDelimUTF8(DataInput)} is to provide a simple, ready-to-use * (even if not particularly efficient) method for storing arbitrary * mutable strings in a self-delimiting way. * *
You can save any mutable string using this method. The length * will be written in packed 7-bit format, that is, as a list of blocks * of 7 bits (from higher to lower) in which the seventh bit determines * whether there is another block in the list. For strings shorter than * 27 characters, the length will be packed in one byte, for strings * shorter than 214 in 2 bytes and so on. * *
The string following the length is coded in UTF-8, not in
* the {@linkplain DataOutput#writeUTF(String) Java modified UTF
* representation}. Thus, an ASCII NUL is represented by a single zero. Note also
* that surrogate pairs will be written as such, that is, without coalescing
* them into a single codepoint. Watch out!
*
* @param s a data output.
* @throws IOException if s does.
*/
public final void writeSelfDelimUTF8(final DataOutput s) throws IOException {
final int length = length();
if (length < 1 << 7) s.writeByte(length);
else if (length < 1 << 14) {
s.writeByte(length >>> 7 & 0x7F | 0x80);
s.writeByte(length & 0x7F);
}
else if (length < 1 << 21) {
s.writeByte(length >>> 14 & 0x7F | 0x80);
s.writeByte(length >>> 7 & 0x7F | 0x80);
s.writeByte(length & 0x7F);
}
else if (length < 1 << 28) {
s.writeByte(length >>> 21 & 0x7F | 0x80);
s.writeByte(length >>> 14 & 0x7F | 0x80);
s.writeByte(length >>> 7 & 0x7F | 0x80);
s.writeByte(length & 0x7F);
}
else {
s.writeByte(length >>> 28 & 0x7F | 0x80);
s.writeByte(length >>> 21 & 0x7F | 0x80);
s.writeByte(length >>> 14 & 0x7F | 0x80);
s.writeByte(length >>> 7 & 0x7F | 0x80);
s.writeByte(length & 0x7F);
}
writeUTF8(s, length);
}
/** Reads a mutable string that has been written by {@link #writeSelfDelimUTF8(DataOutput) writeSelfDelimUTF8()}
* from a {@link DataInput}.
*
* @param s a data input.
* @see #readUTF8(DataInput,int)
* @return this mutable string.
* @throws IOException if s does.
*/
public final MutableString readSelfDelimUTF8(final DataInput s) throws IOException {
int length = 0, b;
while((b = s.readByte()) < 0) {
length |= b & 0x7F;
length <<= 7;
}
length |= b;
readUTF8(s, length);
return this;
}
/** Writes this mutable string in UTF-8 encoding.
* @param s an output stream.
* @throws IOException if s does.
* @see #writeUTF8(DataOutput)
* @deprecated This method will not process surrogate pairs correctly.
*/
@Deprecated
public final void writeUTF8(final OutputStream s) throws IOException {
writeUTF8(s, length());
}
/** Writes this mutable string in UTF-8 encoding.
*
* @param s an output stream.
* @param length the length of this string (i.e., {@link #length()}).
* @throws IOException if s does.
* @see #writeUTF8(DataOutput, int)
* @deprecated This method will not process surrogate pairs correctly.
*/
@Deprecated
private void writeUTF8(final OutputStream s, final int length) throws IOException {
final char[] a = array;
char c;
for (int i = 0; i < length; i++) {
c = a[i];
if (c <= 127) s.write(c); // ASCII
else if (c >= 0x800) {
s.write((byte) (0xE0 | ((c >> 12) & 0x0F)));
s.write((byte) (0x80 | ((c >> 6) & 0x3F)));
s.write((byte) (0x80 | ((c >> 0) & 0x3F)));
}
else {
s.write((byte) (0xC0 | ((c >> 6) & 0x1F)));
s.write((byte) (0x80 | ((c >> 0) & 0x3F)));
}
}
}
/** Skips a string encoded by {@link #writeSelfDelimUTF8(OutputStream)}.
*
* @param s an input stream.
* @throws UTFDataFormatException on UTF-8 sequences longer than three octects.
* @throws IOException if s does, or we try to read beyond end-of-file.
* @return the length of the skipped string.
* @see #writeSelfDelimUTF8(OutputStream)
*/
public static int skipSelfDelimUTF8(final InputStream s) throws IOException {
int length = 0, b, c, d;
for(;;) {
if ((b = s.read()) < 0) throw new EOFException();
if ((b & 0x80) == 0) break;
length |= b & 0x7F;
length <<= 7;
}
length |= b;
for(int i = 0; i < length; i++) {
if ((b = s.read()) == -1) throw new EOFException();
b &= 0xFF;
switch (b >> 4) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
// ASCII
break;
case 12:
case 13:
if ((c = s.read()) == -1) throw new EOFException();
c &= 0xFF;
if ((c & 0xC0) != 0x80) throw new UTFDataFormatException();
break;
case 14:
if ((c = s.read()) == -1) throw new EOFException();
c &= 0xFF;
if ((d = s.read()) == -1) throw new EOFException();
d &= 0xFF;
if ((c & 0xC0) != 0x80 || (d & 0xC0) != 0x80) throw new UTFDataFormatException();
break;
default:
throw new UTFDataFormatException();
}
}
return length;
}
/** Reads a mutable string in UTF-8 encoding.
*
* @param s an input stream.
* @param length the number of characters to read.
* @return this mutable string.
* @throws UTFDataFormatException on UTF-8 sequences longer than three octects.
* @throws IOException if s does, or we try to read beyond end-of-file.
* @see #readUTF8(DataInput, int)
* @deprecated This method will not process surrogate pairs correctly.
*/
@Deprecated
public final MutableString readUTF8(final InputStream s, final int length) throws IOException {
final boolean compact = hashLength < 0;
expand(length);
final char[] a = array;
int b, c, d;
for(int i = 0; i < length; i++) {
if ((b = s.read()) == -1) throw new EOFException();
b &= 0xFF;
switch (b >> 4) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
a[i] = (char)b; // ASCII
break;
case 12:
case 13:
if ((c = s.read()) == -1) throw new EOFException();
c &= 0xFF;
if ((c & 0xC0) != 0x80) throw new UTFDataFormatException();
a[i] = (char)(((b & 0x1F) << 6) | (c & 0x3F));
break;
case 14:
if ((c = s.read()) == -1) throw new EOFException();
c &= 0xFF;
if ((d = s.read()) == -1) throw new EOFException();
d &= 0xFF;
if ((c & 0xC0) != 0x80 || (d & 0xC0) != 0x80) throw new UTFDataFormatException();
a[i] = (char)(((b & 0x0F) << 12) | ((c & 0x3F) << 6) | ((d & 0x3F) << 0));
break;
default:
throw new UTFDataFormatException();
}
}
// If the string was compact and we can make it again compact, we do it.
hashLength = compact && length == a.length ? -1 : length;
return this;
}
/** Writes this mutable string to an {@link OutputStream} as a
* length followed by a UTF-8 encoding.
*
* @param s an output stream.
* @see #writeUTF8(DataOutput)
* @throws IOException if s does.
* @see #writeSelfDelimUTF8(DataOutput)
*/
public final void writeSelfDelimUTF8(final OutputStream s) throws IOException {
final int length = length();
if (length < 1 << 7) s.write(length);
else if (length < 1 << 14) {
s.write(length >>> 7 & 0x7F | 0x80);
s.write(length & 0x7F);
}
else if (length < 1 << 21) {
s.write(length >>> 14 & 0x7F | 0x80);
s.write(length >>> 7 & 0x7F | 0x80);
s.write(length & 0x7F);
}
else if (length < 1 << 28) {
s.write(length >>> 21 & 0x7F | 0x80);
s.write(length >>> 14 & 0x7F | 0x80);
s.write(length >>> 7 & 0x7F | 0x80);
s.write(length & 0x7F);
}
else {
s.write(length >>> 28 & 0x7F | 0x80);
s.write(length >>> 21 & 0x7F | 0x80);
s.write(length >>> 14 & 0x7F | 0x80);
s.write(length >>> 7 & 0x7F | 0x80);
s.write(length & 0x7F);
}
writeUTF8(s, length);
}
/** Reads a mutable string that has been written by {@link #writeSelfDelimUTF8(OutputStream) writeSelfDelimUTF8()}
* from an {@link InputStream}.
*
* @param s an input stream.
* @see #readUTF8(DataInput,int)
* @return this mutable string.
* @throws UTFDataFormatException on UTF-8 sequences longer than three octects.
* @throws IOException if s does.
* @throws IOException if s does, or we try to read beyond end-of-file.
* @see #readSelfDelimUTF8(DataInput)
*/
public final MutableString readSelfDelimUTF8(final InputStream s) throws IOException {
int length = 0, b;
for(;;) {
if ((b = s.read()) < 0) throw new EOFException();
if ((b & 0x80) == 0) break;
length |= b & 0x7F;
length <<= 7;
}
length |= b;
readUTF8(s, length);
return this;
}
/** Compares this mutable string to another object.
*
*
This method will return true iff its argument
* is a CharSequence containing the same characters of this
* mutable string.
*
*
A potentially nasty consequence is that equality is not symmetric.
* See the discussion in the {@linkplain MutableString class description}.
*
* @param o an {@link java.lang.Object}.
* @return true if the argument is a CharSequences that contains the same characters of this mutable string.
*/
@Override
public final boolean equals(final Object o) {
if (o == null) return false;
if (o instanceof MutableString) return equals((MutableString)o);
if (o instanceof String) return equals((String)o);
if (o instanceof CharSequence) return equals((CharSequence)o);
return false;
}
/** Type-specific version of {@link #equals(Object) equals()}.
* This version of the {@link #equals(Object)} method will be called
* on mutable strings.
*
* @param s a mutable string.
* @return true if the two mutable strings contain the same characters.
* @see #equals(Object)
*/
public final boolean equals(final MutableString s) {
if (s == this) return true;
int n = length();
if (n == s.length()) {
final char[] a1 = array, a2 = s.array;
while(n-- != 0) if (a1[n] != a2[n]) return false;
return true;
}
return false;
}
/** Type-specific version of {@link #equals(Object) equals()}.
*
* This version of the {@link #equals(Object)} method will be called
* on Strings. It is guaranteed that it will return true
* iff the mutable string and the String contain the same characters.
* Thus, you can use expressions like
*
* mutableString.equals("Hello")
*
* to check against string contants.
*
* @param s a String.
* @return true if the String contain the same characters of this mutable string.
* @see #equals(Object)
*/
public final boolean equals(final String s) {
int n = length();
if (n == s.length()) {
final char[] a = array;
while(n-- != 0) if (a[n] != s.charAt(n)) return false;
return true;
}
return false;
}
/** Type-specific version of {@link #equals(Object) equals()}.
*
* This version of the {@link #equals(Object)} method will be called
* on character sequences. It is guaranteed that it will return true
* iff this mutable string and the character sequence contain the same characters.
*
* @param s a character sequence.
* @return true if the character sequence contains the same characters of this mutable string.
* @see #equals(Object)
*/
public final boolean equals(final CharSequence s) {
int n = length();
if (n == s.length()) {
final char[] a = array;
while(n-- != 0) if (a[n] != s.charAt(n)) return false;
return true;
}
return false;
}
/** Checks two mutable strings for equality ignoring case.
*
* @param s a mutable string.
* @return true if the two mutable strings contain the same characters up to case.
* @see java.lang.String#equalsIgnoreCase(String)
*/
public final boolean equalsIgnoreCase(final MutableString s) {
if (this == s) return true;
if (s == null) return false;
final int n = length();
if (n == s.length()) {
final char[] a1 = array;
final char[] a2 = s.array;
for(int i = 0; i < n; i++)
if (a1[i] != a2[i]
&& Character.toLowerCase(a1[i]) != Character.toLowerCase(a2[i])
&& Character.toUpperCase(a1[i]) != Character.toUpperCase(a2[i]))
return false;
return true;
}
return false;
}
/** Type-specific version of {@link #equalsIgnoreCase(MutableString) equalsIgnoreCase()}.
*
* @param s a string.
* @return true if the string contains the same characters of this mutable string up to case.
* @see #equalsIgnoreCase(MutableString)
*/
public final boolean equalsIgnoreCase(final String s) {
if (s == null) return false;
final int n = length();
if (n == s.length()) {
final char[] a = array;
char c;
for(int i = 0; i < n; i++)
if (a[i] != (c = s.charAt(i))
&& Character.toLowerCase(a[i]) != Character.toLowerCase(c)
&& Character.toUpperCase(a[i]) != Character.toUpperCase(c))
return false;
return true;
}
return false;
}
/** Type-specific version of {@link #equalsIgnoreCase(MutableString) equalsIgnoreCase()}.
*
* @param s a character sequence.
* @return true if the character sequence contains the same characters of this mutable string up to case.
* @see #equalsIgnoreCase(MutableString)
*/
public final boolean equalsIgnoreCase(final CharSequence s) {
if (s == null) return false;
final int n = length();
if (n == s.length()) {
final char[] a = array;
char c;
for(int i = 0; i < n; i++)
if (a[i] != (c = s.charAt(i))
&& Character.toLowerCase(a[i]) != Character.toLowerCase(c)
&& Character.toUpperCase(a[i]) != Character.toUpperCase(c))
return false;
return true;
}
return false;
}
/** Compares this mutable string to another mutable string performing a lexicographical comparison.
*
* @param s a mutable string.
* @return a negative integer, zero, or a positive integer as this mutable
* string is less than, equal to, or greater than the specified mutable
* string.
*/
@Override
public final int compareTo(final MutableString s) {
final int l1 = length();
final int l2 = s.length();
final int n = l1 < l2 ? l1 : l2;
final char[] a1 = array;
final char[] a2 = s.array;
for(int i = 0; i < n; i++) if (a1[i] != a2[i]) return a1[i] - a2[i];
return l1 - l2;
}
/** Compares this mutable string to a string performing a lexicographical comparison.
*
* @param s a String.
* @return a negative integer, zero, or a positive integer as this mutable
* string is less than, equal to, or greater than the specified
* String.
*/
public final int compareTo(final String s) {
final int l1 = length();
final int l2 = s.length();
final int n = l1 < l2 ? l1 : l2;
final char[] a = array;
for(int i = 0; i < n; i++) if (a[i] != s.charAt(i)) return a[i] - s.charAt(i);
return l1 - l2;
}
/** Compares this mutable string to a character sequence performing a lexicographical comparison.
*
* @param s a character sequence.
* @return a negative integer, zero, or a positive integer as this mutable
* string is less than, equal to, or greater than the specified character sequence.
*/
public final int compareTo(final CharSequence s) {
final int l1 = length();
final int l2 = s.length();
final int n = l1 < l2 ? l1 : l2;
final char[] a = array;
for(int i = 0; i < n; i++) if (a[i] != s.charAt(i)) return a[i] - s.charAt(i);
return l1 - l2;
}
/** Compares this mutable string to another object disregarding case. If the
* argument is a character sequence, this method performs a lexicographical comparison; otherwise,
* it throws a ClassCastException.
*
* A potentially nasty consequence is that comparisons are not symmetric. * See the discussion in the {@linkplain MutableString class description}. * * * @param s a mutable string. * @return a negative integer, zero, or a positive integer as this mutable * string is less than, equal to, or greater than the specified mutable * string once case differences have been eliminated. * @see java.lang.String#compareToIgnoreCase(String) */ public final int compareToIgnoreCase(final MutableString s) { final int l1 = length(); final int l2 = s.length(); final int n = l1 < l2 ? l1 : l2; final char[] a1 = array; final char[] a2 = s.array; char c, d; for(int i = 0; i < n; i++) { c = Character.toLowerCase(Character.toUpperCase(a1[i])); d = Character.toLowerCase(Character.toUpperCase(a2[i])); if (c != d) return c - d; } return l1 - l2; } /** Type-specific version of {@link #compareToIgnoreCase(MutableString) compareToIgnoreCase()}. * @param s a mutable string. * @return a negative integer, zero, or a positive integer as this mutable * string is less than, equal to, or greater than the specified * string once case differences have been eliminated. * @see #compareToIgnoreCase(MutableString) */ public final int compareToIgnoreCase(final String s) { final int l1 = length(); final int l2 = s.length(); final int n = l1 < l2 ? l1 : l2; final char[] a = array; char c, d; for(int i = 0; i < n; i++) { c = Character.toLowerCase(Character.toUpperCase(a[i])); d = Character.toLowerCase(Character.toUpperCase(s.charAt(i))); if (c != d) return c - d; } return l1 - l2; } /** Type-specific version of {@link #compareToIgnoreCase(MutableString) compareToIgnoreCase()}. * @param s a mutable string. * @return a negative integer, zero, or a positive integer as this mutable * string is less than, equal to, or greater than the specified * character sequence once case differences have been eliminated. * @see #compareToIgnoreCase(MutableString) */ public final int compareToIgnoreCase(final CharSequence s) { final int l1 = length(); final int l2 = s.length(); final int n = l1 < l2 ? l1 : l2; final char[] a = array; char c, d; for(int i = 0; i < n; i++) { c = Character.toLowerCase(Character.toUpperCase(a[i])); d = Character.toLowerCase(Character.toUpperCase(s.charAt(i))); if (c != d) return c - d; } return l1 - l2; } /** Returns a hash code for this mutable string. * *
The hash code of a mutable string is the same as that of a
* String with the same content, but with the leftmost bit
* set.
*
*
A compact mutable string caches its hash code, so it is * very efficient on data structures that check hash codes before invoking * {@link java.lang.Object#equals(Object) equals()}. * * @return a hash code array for this object. * @see java.lang.String#hashCode() */ @Override public final int hashCode() { int h = hashLength; if (h >= -1) { final char[] a = array; final int l = length(); for (int i = h = 0; i < l; i++) h = 31 * h + a[i]; h |= (1 << 31); if (hashLength == -1) hashLength = h; } return h; } @Override public final String toString() { return new String(array, 0, length()); } /** * Writes a mutable string in serialized form. * *
* The serialized version of a mutable string is made of its length followed by its characters (in * UTF-16 format). Note that the compactness state is forgotten. * *
* Because of limitations of {@link ObjectOutputStream}, this method must write one character at a * time, and does not try to do any caching (in particular, it does not create any object). On * non-buffered data outputs it might be very slow. * * @param s a data output. */ private void writeObject(final ObjectOutputStream s) throws IOException { s.defaultWriteObject(); final int length = length(); final char[] a = array; s.writeInt(length); for(int i = 0; i < length; i++) s.writeChar(a[i]); } /** * Reads a mutable string in serialized form. * *
* Mutable strings produced by this method are always compact; this seems reasonable, as stored * strings are unlikely going to be changed. * *
* Because of limitations of {@link ObjectInputStream}, this method must read one character at a * time, and does not try to do any read-ahead (in particular, it does not create any object). On * non-buffered data inputs it might be very slow. * * @param s a data input. */ private void readObject(final ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); final int length = s.readInt(); // The new string will be compact. hashLength = -1; expand(length); final char[] a = array; for(int i = 0; i < length; i++) a[i] = s.readChar(); } }