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

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

package it.unimi.dsi.lang;

/*
 * DSI utilities
 *
 * Copyright (C) 2002-2017 Paolo Boldi and Sebastiano Vigna
 *
 *  This library is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU Lesser General Public License as published by the Free
 *  Software Foundation; either version 3 of the License, or (at your option)
 *  any later version.
 *
 *  This library is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 *  or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 *  for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program; if not, see .
 *
 */

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

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;

/** 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(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[]) { 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) { 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(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(Object o) { if (o instanceof CharSequence) { 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 (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) { 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(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(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(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(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) { 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) { 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) { 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, 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 { 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 { 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