src.it.unimi.dsi.lang.MutableString Maven / Gradle / Ivy
Show all versions of dsiutils Show documentation
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;
/*
* DSI utilities
*
* Copyright (C) 2002-2019 Paolo Boldi and Sebastiano Vigna
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at your option)
* any later version.
*
* This library 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. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see .
*
*/
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, optimised & versatile mutable strings.
*
* Motivation
*
* The classical Java string classes, {@link java.lang.String} and {@link
* java.lang.StringBuffer}, 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 optimised 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.
*
*
Features
*
* Mutable strings come in two flavours: compact and
* loose. A mutable string created by the empty constructor or
* the constructor specifying a capacity is loose. All other
* constructors create compact mutable strings. In most cases, you can completely
* forget whether your mutable strings are loose or compact and get
* good performance.
*
*
*
* - Mutable strings occupy little space— their only attributes are a
* backing character array and an integer;
*
*
- their methods try to be as efficient as possible:for instance, if some
* limitation on a parameter is implied by limitation on array access, we do
* not check it explicitly, and Bloom filters are used to speed up {@link
* #replace(char[],String[]) multi-character substitutions};
*
*
- they let you access directly the backing array
* (at your own risk);
*
*
- they implement {@link CharSequence}, so, for instance, you can match or split a
* mutable string against a regular expression using the {@linkplain java.util.regex.Pattern standard Java API};
*
*
- they implement {@link Appendable}, so they can be used with {@link java.util.Formatter}
* and similar classes;
*
*
- {@code null}is not accepted as a string argument;
*
*
- compact mutable strings have a slow growth; loose mutable strings have a fast growth;
*
*
- hash codes of compact mutable strings are cached (for faster
* equality checks);
*
*
- typical conversions such as trimming, upper/lower casing and
* replacements are made in place, with minimal reallocations;
*
*
- all methods try, whenever it is possible, to return
this
, so
* you can chain methods as in s.length(0).append("foo").append("bar")
;
*
* - you can write or print a mutable string without creating a
*
String
by using {@link #write(Writer)}, {@link
* #print(PrintWriter)} and {@link #println(PrintWriter)}; you can read it back
* using {@link #read(Reader,int)}.
*
* - you can write any mutable string in (length-prefixed) UTF-8
* format by using {@link #writeSelfDelimUTF8(DataOutput)}—you are not
* limited to strings whose UTF-8 encoded length fits 16 bits; notice however
* that surrogate pairs will not be coalesced into a single code point, so you must re-read
* such strings using the same method;
*
*
- you can {@link #wrap(char[]) wrap} any character array into a mutable string;
*
*
- this class is not final: thus, you can add your own methods to
* specialised versions.
*
*
*
* 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.
*
*
The Reallocation Heuristic
*
* 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 maximise 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.
*
*
Disadvantages
*
* 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.
*
*
Warnings
*
* There are a few differences with standard string classes you should be aware
* of.
*
*
*
* - This class is not synchronised. If multiple threads
* access an object of this class concurrently, and at least one of the threads
* modifies it, it must be synchronised externally.
*
*
- This class implements polymorphic versions of the {@link #equals(Object)
* equals} method that compare the content of
String
s and
* CharSequence
s, so that you can easily do checks like
*
*
* mutableString.equals("Hello")
*
*
* Thus, you must not mix mutable strings with
* CharSequence
s in collections as equality between objects of
* those types is not symmetric.
*
* - When the length of a string or char array argument is zero,
* some methods may just do nothing even if other parameters are out of
* bounds.
*
*
- The output of {@link #writeSelfDelimUTF8(DataOutput) writeSelfDelimUTF8()} is
* not compatible with the usual Java {@link
* DataOutput#writeUTF(String) writeUTF()}.
*
*
- Even if this class is not final, most methods are declared
* final for efficiency, so you cannot override them (why should you ever want
* to override {@link #array()}?).
*
*
*
* @author Sebastiano Vigna
* @author Paolo Boldi
* @since 0.3
*/
public class MutableString implements Serializable, CharSequence, Appendable, Comparable, Cloneable {
/** A mutable string containing 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 CharSequence
s.
*
* @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
*
*/
public final void getChars(final int start, final int end, final char[] dest, final int destStart) {
if (end > length()) throw new IndexOutOfBoundsException();
System.arraycopy(array, start, dest, destStart, end - start);
}
/** Ensures that at least the given number of characters can be stored in this mutable string.
*
* 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 maximised 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 CharSequence
s 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 CharSequence
s
* 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.
*/
// TODO: this needs tests
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);
// TODO: optimise for the case end == start + 1
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 Sunday's QuickSearch (a
* simplified but very effective variant of the Boyer—Moore search
* algorithm) 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 normalises 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 normalises 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 normalises 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) / 2 + 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 CharSequence
s 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 String
s. 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 serialised form.
*
*
The serialised 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 serialised 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();
}
}