All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.it.unimi.dsi.lang.MutableString Maven / Gradle / Ivy

Go to download

The DSI utilities are a mishmash of classes accumulated during the last twenty years in projects developed at the DSI (Dipartimento di Scienze dell'Informazione, i.e., Information Sciences Department), now DI (Dipartimento di Informatica, i.e., Informatics Department), of the Universita` degli Studi di Milano.

There is a newer version: 2.7.3
Show newest version
/*
 * DSI utilities
 *
 * Copyright (C) 2002-2020 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 .
 *
 */

package it.unimi.dsi.lang;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.Serializable;
import java.io.UTFDataFormatException;
import java.io.Writer;

import it.unimi.dsi.fastutil.chars.Char2CharMap;
import it.unimi.dsi.fastutil.chars.CharArrays;
import it.unimi.dsi.fastutil.chars.CharList;
import it.unimi.dsi.fastutil.chars.CharSet;
import it.unimi.dsi.fastutil.objects.ObjectArrays;
import it.unimi.dsi.util.TextPattern;

/** Fast, compact, 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. * *
    * *
  1. 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. * *
  2. This class implements polymorphic versions of the {@link #equals(Object) * equals} method that compare the content of Strings and * CharSequences, so that you can easily do checks like * *
     *         mutableString.equals("Hello")
     * 
    * * Thus, you must not mix mutable strings with * CharSequences in collections as equality between objects of * those types is not symmetric. * *
  3. 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. * *
  4. The output of {@link #writeSelfDelimUTF8(DataOutput) writeSelfDelimUTF8()} is * not compatible with the usual Java {@link * DataOutput#writeUTF(String) writeUTF()}. * *
  5. 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 CharSequences. * * @param s a CharSequence. * @param start copy start from this index (inclusive). * @param end copy ends at this index (exclusive). * @param dest destination array. * @param destStart the first character will be copied in dest[destStart]. * @see java.lang.String#getChars(int,int,char[],int) */ public static void getChars(final CharSequence s, final int start, final int end, final char[] dest, final int destStart) { int j = destStart, i = start; while(i < end) dest[j++] = s.charAt(i++); } /** Returns the number of characters in this mutable string. * * @return the length of this mutable string. */ @Override public final int length() { return hashLength >= 0 ? hashLength : array.length; } /** Returns whether this mutable string is empty. * * @return whether this mutable string is empty. */ public final boolean isEmpty() { return (hashLength >= 0 ? hashLength : array.length) == 0; } /** Returns the current length of the backing array. * * @return the current length of the backing array. */ public final int capacity() { return array.length; } /** Gets the backing array. * *

For fast, repeated access to the characters of this mutable string, * you can obtain the actual backing array. Be careful, and if this mutable * string is compact do not modify its backing array without * calling {@link #changed()} immediately afterwards (or the cached hash * code will get out of sync, with unforeseeable consequences). * * @see #changed() * @return the backing array. */ public final char[] array() { return array; } /** Characters with indices from start (inclusive) to * index end (exclusive) are copied from this * mutable string into the array dest, starting * from index destStart. * * @param start copy start from this index (inclusive). * @param end copy ends at this index (exclusive). * @param dest destination array. * @param destStart the first character will be copied in dest[destStart]. * * @see String#getChars(int,int,char[],int) * @throws NullPointerException if dest is {@code null}. * @throws IndexOutOfBoundsException if any of the following is true: *

    *
  • start or end is negative *
  • start is greater than * end *
  • end is greater than * {@link #length()} *
  • end-start+destStart is greater than * dest.length *
*/ 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 CharSequences returned by this method is defined by * content equality, and hash codes are identical to mutable strings with the same * content. Thus, you can mix mutable strings and CharSequences * returned by this method in data structures, as the contracts of {@link java.lang.Object#equals(Object) equals()} and * {@link java.lang.Object#hashCode() hashCode()} are honoured. * * @param start first character of the subsequence (inclusive). * @param end last character of the subsequence (exclusive). * @return a subsequence defined as above. * @see #substring(int,int) */ @Override public final CharSequence subSequence(final int start, final int end) { if (start < 0) throw new StringIndexOutOfBoundsException(); if (start > end || end > length()) throw new StringIndexOutOfBoundsException(); return new SubSequence(start, end); } /** Appends the given mutable string to this mutable string. * * @param s the mutable string to append. * @return this mutable string. */ public final MutableString append(MutableString s) { if (s == null) s = NULL; final int l = s.length(); if (l == 0) return this; final int newLength = length() + l; expand(newLength); System.arraycopy(s.array, 0, array, newLength - l, l); hashLength = hashLength < 0 ? -1 : newLength; return this; } /** * Appends the given String to this mutable string. * * @param s a String ({@code null} is not allowed). * @return this mutable string. * @throws NullPointerException if the argument is {@code null} */ public final MutableString append(final String s) { if (s == null) return append(NULL); final int l = s.length(); if (l == 0) return this; final int newLength = length() + l; expand(newLength); s.getChars(0, l, array, newLength - l); hashLength = hashLength < 0 ? -1 : newLength; return this; } /** * Appends the given CharSequence to this mutable string. * * @param s a CharSequence or {@code null}. * @return this mutable string. * * @see Appendable#append(java.lang.CharSequence) */ @Override public final MutableString append(final CharSequence s) { if (s == null) return append(NULL); final int l = s.length(); if (l == 0) return this; final int newLength = length() + l; expand(newLength); getChars(s, 0, l, array, newLength - l); hashLength = hashLength < 0 ? -1 : newLength; return this; } /** * Appends a subsequence of the given CharSequence to this mutable string. * * Warning: the semantics of this method of that of * {@link #append(char[], int, int)} are different. * * @param s a CharSequence or {@code null}. * @param start the index of the first character of the subsequence to append. * @param end the index of the character after the last character in the subsequence. * @return this mutable string. * * @see Appendable#append(java.lang.CharSequence, int, int) */ @Override public final MutableString append(final CharSequence s, final int start, final int end) { if (s == null) return append(NULL, start, end); final int len = end - start; if (len < 0 || start < 0 || end > s.length()) throw new IndexOutOfBoundsException("start: " + start + " end: " + end + " length():" + s.length()); final int newLength = length() + len; expand(newLength); try { getChars(s, start, end, array, newLength - len); } catch (final IndexOutOfBoundsException e) { if (hashLength < 0) setCapacity(newLength - len); else hashLength = newLength - len; throw e; } if (len != 0) hashLength = hashLength < 0 ? -1 : newLength; return this; } /** * Appends the given character sequences to this mutable string using the given separator. * * @param a an array. * @param offset the index of the first character sequence to append. * @param length the number of character sequences to append. * @param separator a separator that will be appended inbetween the character sequences. * @return this mutable string. */ // 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 CharSequences that contains the same characters of this mutable string. */ @Override public final boolean equals(final Object o) { if (o == null) return false; if (o instanceof MutableString) return equals((MutableString)o); if (o instanceof String) return equals((String)o); if (o instanceof CharSequence) return equals((CharSequence)o); return false; } /** Type-specific version of {@link #equals(Object) equals()}. * This version of the {@link #equals(Object)} method will be called * on mutable strings. * * @param s a mutable string. * @return true if the two mutable strings contain the same characters. * @see #equals(Object) */ public final boolean equals(final MutableString s) { if (s == this) return true; int n = length(); if (n == s.length()) { final char[] a1 = array, a2 = s.array; while(n-- != 0) if (a1[n] != a2[n]) return false; return true; } return false; } /** Type-specific version of {@link #equals(Object) equals()}. * * This version of the {@link #equals(Object)} method will be called * on Strings. It is guaranteed that it will return true * iff the mutable string and the String contain the same characters. * Thus, you can use expressions like *

	 * mutableString.equals("Hello")
	 * 
* to check against string contants. * * @param s a String. * @return true if the String contain the same characters of this mutable string. * @see #equals(Object) */ public final boolean equals(final String s) { int n = length(); if (n == s.length()) { final char[] a = array; while(n-- != 0) if (a[n] != s.charAt(n)) return false; return true; } return false; } /** Type-specific version of {@link #equals(Object) equals()}. * * This version of the {@link #equals(Object)} method will be called * on character sequences. It is guaranteed that it will return true * iff this mutable string and the character sequence contain the same characters. * * @param s a character sequence. * @return true if the character sequence contains the same characters of this mutable string. * @see #equals(Object) */ public final boolean equals(final CharSequence s) { int n = length(); if (n == s.length()) { final char[] a = array; while(n-- != 0) if (a[n] != s.charAt(n)) return false; return true; } return false; } /** Checks two mutable strings for equality ignoring case. * * @param s a mutable string. * @return true if the two mutable strings contain the same characters up to case. * @see java.lang.String#equalsIgnoreCase(String) */ public final boolean equalsIgnoreCase(final MutableString s) { if (this == s) return true; if (s == null) return false; final int n = length(); if (n == s.length()) { final char[] a1 = array; final char[] a2 = s.array; for(int i = 0; i < n; i++) if (a1[i] != a2[i] && Character.toLowerCase(a1[i]) != Character.toLowerCase(a2[i]) && Character.toUpperCase(a1[i]) != Character.toUpperCase(a2[i])) return false; return true; } return false; } /** Type-specific version of {@link #equalsIgnoreCase(MutableString) equalsIgnoreCase()}. * * @param s a string. * @return true if the string contains the same characters of this mutable string up to case. * @see #equalsIgnoreCase(MutableString) */ public final boolean equalsIgnoreCase(final String s) { if (s == null) return false; final int n = length(); if (n == s.length()) { final char[] a = array; char c; for(int i = 0; i < n; i++) if (a[i] != (c = s.charAt(i)) && Character.toLowerCase(a[i]) != Character.toLowerCase(c) && Character.toUpperCase(a[i]) != Character.toUpperCase(c)) return false; return true; } return false; } /** Type-specific version of {@link #equalsIgnoreCase(MutableString) equalsIgnoreCase()}. * * @param s a character sequence. * @return true if the character sequence contains the same characters of this mutable string up to case. * @see #equalsIgnoreCase(MutableString) */ public final boolean equalsIgnoreCase(final CharSequence s) { if (s == null) return false; final int n = length(); if (n == s.length()) { final char[] a = array; char c; for(int i = 0; i < n; i++) if (a[i] != (c = s.charAt(i)) && Character.toLowerCase(a[i]) != Character.toLowerCase(c) && Character.toUpperCase(a[i]) != Character.toUpperCase(c)) return false; return true; } return false; } /** Compares this mutable string to another mutable string performing a lexicographical comparison. * * @param s a mutable string. * @return a negative integer, zero, or a positive integer as this mutable * string is less than, equal to, or greater than the specified mutable * string. */ @Override public final int compareTo(final MutableString s) { final int l1 = length(); final int l2 = s.length(); final int n = l1 < l2 ? l1 : l2; final char[] a1 = array; final char[] a2 = s.array; for(int i = 0; i < n; i++) if (a1[i] != a2[i]) return a1[i] - a2[i]; return l1 - l2; } /** Compares this mutable string to a string performing a lexicographical comparison. * * @param s a String. * @return a negative integer, zero, or a positive integer as this mutable * string is less than, equal to, or greater than the specified * String. */ public final int compareTo(final String s) { final int l1 = length(); final int l2 = s.length(); final int n = l1 < l2 ? l1 : l2; final char[] a = array; for(int i = 0; i < n; i++) if (a[i] != s.charAt(i)) return a[i] - s.charAt(i); return l1 - l2; } /** Compares this mutable string to a character sequence performing a lexicographical comparison. * * @param s a character sequence. * @return a negative integer, zero, or a positive integer as this mutable * string is less than, equal to, or greater than the specified character sequence. */ public final int compareTo(final CharSequence s) { final int l1 = length(); final int l2 = s.length(); final int n = l1 < l2 ? l1 : l2; final char[] a = array; for(int i = 0; i < n; i++) if (a[i] != s.charAt(i)) return a[i] - s.charAt(i); return l1 - l2; } /** Compares this mutable string to another object disregarding case. If the * argument is a character sequence, this method performs a lexicographical comparison; otherwise, * it throws a ClassCastException. * *

A potentially nasty consequence is that comparisons are not symmetric. * See the discussion in the {@linkplain MutableString class description}. * * * @param s a mutable string. * @return a negative integer, zero, or a positive integer as this mutable * string is less than, equal to, or greater than the specified mutable * string once case differences have been eliminated. * @see java.lang.String#compareToIgnoreCase(String) */ public final int compareToIgnoreCase(final MutableString s) { final int l1 = length(); final int l2 = s.length(); final int n = l1 < l2 ? l1 : l2; final char[] a1 = array; final char[] a2 = s.array; char c, d; for(int i = 0; i < n; i++) { c = Character.toLowerCase(Character.toUpperCase(a1[i])); d = Character.toLowerCase(Character.toUpperCase(a2[i])); if (c != d) return c - d; } return l1 - l2; } /** Type-specific version of {@link #compareToIgnoreCase(MutableString) compareToIgnoreCase()}. * @param s a mutable string. * @return a negative integer, zero, or a positive integer as this mutable * string is less than, equal to, or greater than the specified * string once case differences have been eliminated. * @see #compareToIgnoreCase(MutableString) */ public final int compareToIgnoreCase(final String s) { final int l1 = length(); final int l2 = s.length(); final int n = l1 < l2 ? l1 : l2; final char[] a = array; char c, d; for(int i = 0; i < n; i++) { c = Character.toLowerCase(Character.toUpperCase(a[i])); d = Character.toLowerCase(Character.toUpperCase(s.charAt(i))); if (c != d) return c - d; } return l1 - l2; } /** Type-specific version of {@link #compareToIgnoreCase(MutableString) compareToIgnoreCase()}. * @param s a mutable string. * @return a negative integer, zero, or a positive integer as this mutable * string is less than, equal to, or greater than the specified * character sequence once case differences have been eliminated. * @see #compareToIgnoreCase(MutableString) */ public final int compareToIgnoreCase(final CharSequence s) { final int l1 = length(); final int l2 = s.length(); final int n = l1 < l2 ? l1 : l2; final char[] a = array; char c, d; for(int i = 0; i < n; i++) { c = Character.toLowerCase(Character.toUpperCase(a[i])); d = Character.toLowerCase(Character.toUpperCase(s.charAt(i))); if (c != d) return c - d; } return l1 - l2; } /** Returns a hash code for this mutable string. * *

The hash code of a mutable string is the same as that of a * String with the same content, but with the leftmost bit * set. * *

A compact mutable string caches its hash code, so it is * very efficient on data structures that check hash codes before invoking * {@link java.lang.Object#equals(Object) equals()}. * * @return a hash code array for this object. * @see java.lang.String#hashCode() */ @Override public final int hashCode() { int h = hashLength; if (h >= -1) { final char[] a = array; final int l = length(); for (int i = h = 0; i < l; i++) h = 31 * h + a[i]; h |= (1 << 31); if (hashLength == -1) hashLength = h; } return h; } @Override public final String toString() { return new String(array, 0, length()); } /** Writes a mutable string in 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(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy