http://git-wip-us.apache.org/repos/asf/commons-text/blob/978e2896/src/main/java/org/apache/commons/text/TextStringBuilder.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/text/TextStringBuilder.java b/src/main/java/org/apache/commons/text/TextStringBuilder.java new file mode 100644 index 0000000..8ab9322 --- /dev/null +++ b/src/main/java/org/apache/commons/text/TextStringBuilder.java @@ -0,0 +1,3216 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.text; + +import java.io.IOException; +import java.io.Reader; +import java.io.Serializable; +import java.io.Writer; +import java.nio.CharBuffer; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; + +import org.apache.commons.text.matcher.StringMatcher; + +/** + * Builds a string from constituent parts providing a more flexible and powerful API than StringBuffer. + * <p> + * The main differences from StringBuffer/StringBuilder are: + * </p> + * <ul> + * <li>Not synchronized</li> + * <li>Not final</li> + * <li>Subclasses have direct access to character array</li> + * <li>Additional methods + * <ul> + * <li>appendWithSeparators - adds an array of values, with a separator</li> + * <li>appendPadding - adds a length padding characters</li> + * <li>appendFixedLength - adds a fixed width field to the builder</li> + * <li>toCharArray/getChars - simpler ways to get a range of the character array</li> + * <li>delete - delete char or string</li> + * <li>replace - search and replace for a char or string</li> + * <li>leftString/rightString/midString - substring without exceptions</li> + * <li>contains - whether the builder contains a char or string</li> + * <li>size/clear/isEmpty - collections style API methods</li> + * </ul> + * </li> + * <li>Views + * <ul> + * <li>asTokenizer - uses the internal buffer as the source of a StrTokenizer</li> + * <li>asReader - uses the internal buffer as the source of a Reader</li> + * <li>asWriter - allows a Writer to write directly to the internal buffer</li> + * </ul> + * </li> + * </ul> + * <p> + * The aim has been to provide an API that mimics very closely what StringBuffer provides, but with additional methods. + * It should be noted that some edge cases, with invalid indices or null input, have been altered - see individual + * methods. The biggest of these changes is that by default, null will not output the text 'null'. This can be + * controlled by a property, {@link #setNullText(String)}. + * </p> + * <p> + * This class is called {@code TextStringBuilder} instead of {@code StringBuilder} to avoid clashing with + * {@link java.lang.StringBuilder}. + * </p> + * + * @since 1.3 + */ +public class TextStringBuilder implements CharSequence, Appendable, Serializable, Builder<String> { + + /** + * The size of the string {@code "false"}. + */ + private static final int FALSE_STRING_SIZE = "false".length(); + + /** + * The size of the string {@code "true"}. + */ + private static final int TRUE_STRING_SIZE = "true".length(); + + /** + * The extra capacity for new builders. + */ + static final int CAPACITY = 32; + + /** + * Required for serialization support. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = 1L; + + /** Internal data storage. */ + char[] buffer; // package-protected for test code use only + /** Current size of the buffer. */ + private int size; + /** The new line. */ + private String newLine; + /** The null text. */ + private String nullText; + + // ----------------------------------------------------------------------- + /** + * Constructor that creates an empty builder initial capacity 32 characters. + */ + public TextStringBuilder() { + this(CAPACITY); + } + + /** + * Constructor that creates an empty builder the specified initial capacity. + * + * @param initialCapacity + * the initial capacity, zero or less will be converted to 32 + */ + public TextStringBuilder(int initialCapacity) { + super(); + if (initialCapacity <= 0) { + initialCapacity = CAPACITY; + } + buffer = new char[initialCapacity]; + } + + /** + * Constructor that creates a builder from the string, allocating 32 extra characters for growth. + * + * @param str + * the string to copy, null treated as blank string + */ + public TextStringBuilder(final String str) { + super(); + if (str == null) { + buffer = new char[CAPACITY]; + } else { + buffer = new char[str.length() + CAPACITY]; + append(str); + } + } + + // ----------------------------------------------------------------------- + /** + * Gets the text to be appended when a new line is added. + * + * @return the new line text, null means use system default + */ + public String getNewLineText() { + return newLine; + } + + /** + * Sets the text to be appended when a new line is added. + * + * @param newLine + * the new line text, null means use system default + * @return this, to enable chaining + */ + public TextStringBuilder setNewLineText(final String newLine) { + this.newLine = newLine; + return this; + } + + // ----------------------------------------------------------------------- + /** + * Gets the text to be appended when null is added. + * + * @return the null text, null means no append + */ + public String getNullText() { + return nullText; + } + + /** + * Sets the text to be appended when null is added. + * + * @param nullText + * the null text, null means no append + * @return this, to enable chaining + */ + public TextStringBuilder setNullText(String nullText) { + if (nullText != null && nullText.isEmpty()) { + nullText = null; + } + this.nullText = nullText; + return this; + } + + // ----------------------------------------------------------------------- + /** + * Gets the length of the string builder. + * + * @return the length + */ + @Override + public int length() { + return size; + } + + /** + * Updates the length of the builder by either dropping the last characters or adding filler of Unicode zero. + * + * @param length + * the length to set to, must be zero or positive + * @return this, to enable chaining + * @throws IndexOutOfBoundsException + * if the length is negative + */ + public TextStringBuilder setLength(final int length) { + if (length < 0) { + throw new StringIndexOutOfBoundsException(length); + } + if (length < size) { + size = length; + } else if (length > size) { + ensureCapacity(length); + final int oldEnd = size; + final int newEnd = length; + size = length; + for (int i = oldEnd; i < newEnd; i++) { + buffer[i] = '\0'; + } + } + return this; + } + + // ----------------------------------------------------------------------- + /** + * Gets the current size of the internal character array buffer. + * + * @return the capacity + */ + public int capacity() { + return buffer.length; + } + + /** + * Checks the capacity and ensures that it is at least the size specified. + * + * @param capacity + * the capacity to ensure + * @return this, to enable chaining + */ + public TextStringBuilder ensureCapacity(final int capacity) { + if (capacity > buffer.length) { + final char[] old = buffer; + buffer = new char[capacity * 2]; + System.arraycopy(old, 0, buffer, 0, size); + } + return this; + } + + /** + * Minimizes the capacity to the actual length of the string. + * + * @return this, to enable chaining + */ + public TextStringBuilder minimizeCapacity() { + if (buffer.length > length()) { + final char[] old = buffer; + buffer = new char[length()]; + System.arraycopy(old, 0, buffer, 0, size); + } + return this; + } + + // ----------------------------------------------------------------------- + /** + * Gets the length of the string builder. + * <p> + * This method is the same as {@link #length()} and is provided to match the API of Collections. + * + * @return the length + */ + public int size() { + return size; + } + + /** + * Checks is the string builder is empty (convenience Collections API style method). + * <p> + * This method is the same as checking {@link #length()} and is provided to match the API of Collections. + * + * @return <code>true</code> if the size is <code>0</code>. + */ + public boolean isEmpty() { + return size == 0; + } + + /** + * Clears the string builder (convenience Collections API style method). + * <p> + * This method does not reduce the size of the internal character buffer. To do that, call <code>clear()</code> + * followed by {@link #minimizeCapacity()}. + * <p> + * This method is the same as {@link #setLength(int)} called with zero and is provided to match the API of + * Collections. + * + * @return this, to enable chaining + */ + public TextStringBuilder clear() { + size = 0; + return this; + } + + // ----------------------------------------------------------------------- + /** + * Gets the character at the specified index. + * + * @see #setCharAt(int, char) + * @see #deleteCharAt(int) + * @param index + * the index to retrieve, must be valid + * @return the character at the index + * @throws IndexOutOfBoundsException + * if the index is invalid + */ + @Override + public char charAt(final int index) { + if (index < 0 || index >= length()) { + throw new StringIndexOutOfBoundsException(index); + } + return buffer[index]; + } + + /** + * Sets the character at the specified index. + * + * @see #charAt(int) + * @see #deleteCharAt(int) + * @param index + * the index to set + * @param ch + * the new character + * @return this, to enable chaining + * @throws IndexOutOfBoundsException + * if the index is invalid + */ + public TextStringBuilder setCharAt(final int index, final char ch) { + if (index < 0 || index >= length()) { + throw new StringIndexOutOfBoundsException(index); + } + buffer[index] = ch; + return this; + } + + /** + * Deletes the character at the specified index. + * + * @see #charAt(int) + * @see #setCharAt(int, char) + * @param index + * the index to delete + * @return this, to enable chaining + * @throws IndexOutOfBoundsException + * if the index is invalid + */ + public TextStringBuilder deleteCharAt(final int index) { + if (index < 0 || index >= size) { + throw new StringIndexOutOfBoundsException(index); + } + deleteImpl(index, index + 1, 1); + return this; + } + + // ----------------------------------------------------------------------- + /** + * Copies the builder's character array into a new character array. + * + * @return a new array that represents the contents of the builder + */ + public char[] toCharArray() { + if (size == 0) { + return new char[0]; + } + final char[] chars = new char[size]; + System.arraycopy(buffer, 0, chars, 0, size); + return chars; + } + + /** + * Copies part of the builder's character array into a new character array. + * + * @param startIndex + * the start index, inclusive, must be valid + * @param endIndex + * the end index, exclusive, must be valid except that if too large it is treated as end of string + * @return a new array that holds part of the contents of the builder + * @throws IndexOutOfBoundsException + * if startIndex is invalid, or if endIndex is invalid (but endIndex greater than size is valid) + */ + public char[] toCharArray(final int startIndex, int endIndex) { + endIndex = validateRange(startIndex, endIndex); + final int len = endIndex - startIndex; + if (len == 0) { + return new char[0]; + } + final char[] chars = new char[len]; + System.arraycopy(buffer, startIndex, chars, 0, len); + return chars; + } + + /** + * Copies the character array into the specified array. + * + * @param destination + * the destination array, null will cause an array to be created + * @return the input array, unless that was null or too small + */ + public char[] getChars(char[] destination) { + final int len = length(); + if (destination == null || destination.length < len) { + destination = new char[len]; + } + System.arraycopy(buffer, 0, destination, 0, len); + return destination; + } + + /** + * Copies the character array into the specified array. + * + * @param startIndex + * first index to copy, inclusive, must be valid + * @param endIndex + * last index, exclusive, must be valid + * @param destination + * the destination array, must not be null or too small + * @param destinationIndex + * the index to start copying in destination + * @throws NullPointerException + * if the array is null + * @throws IndexOutOfBoundsException + * if any index is invalid + */ + public void getChars(final int startIndex, final int endIndex, final char[] destination, + final int destinationIndex) { + if (startIndex < 0) { + throw new StringIndexOutOfBoundsException(startIndex); + } + if (endIndex < 0 || endIndex > length()) { + throw new StringIndexOutOfBoundsException(endIndex); + } + if (startIndex > endIndex) { + throw new StringIndexOutOfBoundsException("end < start"); + } + System.arraycopy(buffer, startIndex, destination, destinationIndex, endIndex - startIndex); + } + + // ----------------------------------------------------------------------- + /** + * If possible, reads chars from the provided {@link Readable} directly into underlying character buffer without + * making extra copies. + * + * @param readable + * object to read from + * @return the number of characters read + * @throws IOException + * if an I/O error occurs + * + * @see #appendTo(Appendable) + */ + public int readFrom(final Readable readable) throws IOException { + final int oldSize = size; + if (readable instanceof Reader) { + final Reader r = (Reader) readable; + ensureCapacity(size + 1); + int read; + while ((read = r.read(buffer, size, buffer.length - size)) != -1) { + size += read; + ensureCapacity(size + 1); + } + } else if (readable instanceof CharBuffer) { + final CharBuffer cb = (CharBuffer) readable; + final int remaining = cb.remaining(); + ensureCapacity(size + remaining); + cb.get(buffer, size, remaining); + size += remaining; + } else { + while (true) { + ensureCapacity(size + 1); + final CharBuffer buf = CharBuffer.wrap(buffer, size, buffer.length - size); + final int read = readable.read(buf); + if (read == -1) { + break; + } + size += read; + } + } + return size - oldSize; + } + + // ----------------------------------------------------------------------- + /** + * Appends the new line string to this string builder. + * <p> + * The new line string can be altered using {@link #setNewLineText(String)}. This might be used to force the output + * to always use Unix line endings even when on Windows. + * + * @return this, to enable chaining + */ + public TextStringBuilder appendNewLine() { + if (newLine == null) { + append(System.lineSeparator()); + return this; + } + return append(newLine); + } + + /** + * Appends the text representing <code>null</code> to this string builder. + * + * @return this, to enable chaining + */ + public TextStringBuilder appendNull() { + if (nullText == null) { + return this; + } + return append(nullText); + } + + /** + * Appends an object to this string builder. Appending null will call {@link #appendNull()}. + * + * @param obj + * the object to append + * @return this, to enable chaining + */ + public TextStringBuilder append(final Object obj) { + if (obj == null) { + return appendNull(); + } + if (obj instanceof CharSequence) { + return append((CharSequence) obj); + } + return append(obj.toString()); + } + + /** + * Appends a CharSequence to this string builder. Appending null will call {@link #appendNull()}. + * + * @param seq + * the CharSequence to append + * @return this, to enable chaining + */ + @Override + public TextStringBuilder append(final CharSequence seq) { + if (seq == null) { + return appendNull(); + } + if (seq instanceof TextStringBuilder) { + return append((TextStringBuilder) seq); + } + if (seq instanceof StringBuilder) { + return append((StringBuilder) seq); + } + if (seq instanceof StringBuffer) { + return append((StringBuffer) seq); + } + if (seq instanceof CharBuffer) { + return append((CharBuffer) seq); + } + return append(seq.toString()); + } + + /** + * Appends part of a CharSequence to this string builder. Appending null will call {@link #appendNull()}. + * + * @param seq + * the CharSequence to append + * @param startIndex + * the start index, inclusive, must be valid + * @param length + * the length to append, must be valid + * @return this, to enable chaining + */ + @Override + public TextStringBuilder append(final CharSequence seq, final int startIndex, final int length) { + if (seq == null) { + return appendNull(); + } + return append(seq.toString(), startIndex, length); + } + + /** + * Appends a string to this string builder. Appending null will call {@link #appendNull()}. + * + * @param str + * the string to append + * @return this, to enable chaining + */ + public TextStringBuilder append(final String str) { + if (str == null) { + return appendNull(); + } + final int strLen = str.length(); + if (strLen > 0) { + final int len = length(); + ensureCapacity(len + strLen); + str.getChars(0, strLen, buffer, len); + size += strLen; + } + return this; + } + + /** + * Appends part of a string to this string builder. Appending null will call {@link #appendNull()}. + * + * @param str + * the string to append + * @param startIndex + * the start index, inclusive, must be valid + * @param length + * the length to append, must be valid + * @return this, to enable chaining + */ + public TextStringBuilder append(final String str, final int startIndex, final int length) { + if (str == null) { + return appendNull(); + } + if (startIndex < 0 || startIndex > str.length()) { + throw new StringIndexOutOfBoundsException("startIndex must be valid"); + } + if (length < 0 || (startIndex + length) > str.length()) { + throw new StringIndexOutOfBoundsException("length must be valid"); + } + if (length > 0) { + final int len = length(); + ensureCapacity(len + length); + str.getChars(startIndex, startIndex + length, buffer, len); + size += length; + } + return this; + } + + /** + * Calls {@link String#format(String, Object...)} and appends the result. + * + * @param format + * the format string + * @param objs + * the objects to use in the format string + * @return {@code this} to enable chaining + * @see String#format(String, Object...) + */ + public TextStringBuilder append(final String format, final Object... objs) { + return append(String.format(format, objs)); + } + + /** + * Appends the contents of a char buffer to this string builder. Appending null will call {@link #appendNull()}. + * + * @param buf + * the char buffer to append + * @return this, to enable chaining + */ + public TextStringBuilder append(final CharBuffer buf) { + if (buf == null) { + return appendNull(); + } + if (buf.hasArray()) { + final int length = buf.remaining(); + final int len = length(); + ensureCapacity(len + length); + System.arraycopy(buf.array(), buf.arrayOffset() + buf.position(), buffer, len, length); + size += length; + } else { + append(buf.toString()); + } + return this; + } + + /** + * Appends the contents of a char buffer to this string builder. Appending null will call {@link #appendNull()}. + * + * @param buf + * the char buffer to append + * @param startIndex + * the start index, inclusive, must be valid + * @param length + * the length to append, must be valid + * @return this, to enable chaining + */ + public TextStringBuilder append(final CharBuffer buf, final int startIndex, final int length) { + if (buf == null) { + return appendNull(); + } + if (buf.hasArray()) { + final int totalLength = buf.remaining(); + if (startIndex < 0 || startIndex > totalLength) { + throw new StringIndexOutOfBoundsException("startIndex must be valid"); + } + if (length < 0 || (startIndex + length) > totalLength) { + throw new StringIndexOutOfBoundsException("length must be valid"); + } + final int len = length(); + ensureCapacity(len + length); + System.arraycopy(buf.array(), buf.arrayOffset() + buf.position() + startIndex, buffer, len, length); + size += length; + } else { + append(buf.toString(), startIndex, length); + } + return this; + } + + /** + * Appends a string buffer to this string builder. Appending null will call {@link #appendNull()}. + * + * @param str + * the string buffer to append + * @return this, to enable chaining + */ + public TextStringBuilder append(final StringBuffer str) { + if (str == null) { + return appendNull(); + } + final int strLen = str.length(); + if (strLen > 0) { + final int len = length(); + ensureCapacity(len + strLen); + str.getChars(0, strLen, buffer, len); + size += strLen; + } + return this; + } + + /** + * Appends part of a string buffer to this string builder. Appending null will call {@link #appendNull()}. + * + * @param str + * the string to append + * @param startIndex + * the start index, inclusive, must be valid + * @param length + * the length to append, must be valid + * @return this, to enable chaining + */ + public TextStringBuilder append(final StringBuffer str, final int startIndex, final int length) { + if (str == null) { + return appendNull(); + } + if (startIndex < 0 || startIndex > str.length()) { + throw new StringIndexOutOfBoundsException("startIndex must be valid"); + } + if (length < 0 || (startIndex + length) > str.length()) { + throw new StringIndexOutOfBoundsException("length must be valid"); + } + if (length > 0) { + final int len = length(); + ensureCapacity(len + length); + str.getChars(startIndex, startIndex + length, buffer, len); + size += length; + } + return this; + } + + /** + * Appends a StringBuilder to this string builder. Appending null will call {@link #appendNull()}. + * + * @param str + * the StringBuilder to append + * @return this, to enable chaining + */ + public TextStringBuilder append(final StringBuilder str) { + if (str == null) { + return appendNull(); + } + final int strLen = str.length(); + if (strLen > 0) { + final int len = length(); + ensureCapacity(len + strLen); + str.getChars(0, strLen, buffer, len); + size += strLen; + } + return this; + } + + /** + * Appends part of a StringBuilder to this string builder. Appending null will call {@link #appendNull()}. + * + * @param str + * the StringBuilder to append + * @param startIndex + * the start index, inclusive, must be valid + * @param length + * the length to append, must be valid + * @return this, to enable chaining + */ + public TextStringBuilder append(final StringBuilder str, final int startIndex, final int length) { + if (str == null) { + return appendNull(); + } + if (startIndex < 0 || startIndex > str.length()) { + throw new StringIndexOutOfBoundsException("startIndex must be valid"); + } + if (length < 0 || (startIndex + length) > str.length()) { + throw new StringIndexOutOfBoundsException("length must be valid"); + } + if (length > 0) { + final int len = length(); + ensureCapacity(len + length); + str.getChars(startIndex, startIndex + length, buffer, len); + size += length; + } + return this; + } + + /** + * Appends another string builder to this string builder. Appending null will call {@link #appendNull()}. + * + * @param str + * the string builder to append + * @return this, to enable chaining + */ + public TextStringBuilder append(final TextStringBuilder str) { + if (str == null) { + return appendNull(); + } + final int strLen = str.length(); + if (strLen > 0) { + final int len = length(); + ensureCapacity(len + strLen); + System.arraycopy(str.buffer, 0, buffer, len, strLen); + size += strLen; + } + return this; + } + + /** + * Appends part of a string builder to this string builder. Appending null will call {@link #appendNull()}. + * + * @param str + * the string to append + * @param startIndex + * the start index, inclusive, must be valid + * @param length + * the length to append, must be valid + * @return this, to enable chaining + */ + public TextStringBuilder append(final TextStringBuilder str, final int startIndex, final int length) { + if (str == null) { + return appendNull(); + } + if (startIndex < 0 || startIndex > str.length()) { + throw new StringIndexOutOfBoundsException("startIndex must be valid"); + } + if (length < 0 || (startIndex + length) > str.length()) { + throw new StringIndexOutOfBoundsException("length must be valid"); + } + if (length > 0) { + final int len = length(); + ensureCapacity(len + length); + str.getChars(startIndex, startIndex + length, buffer, len); + size += length; + } + return this; + } + + /** + * Appends a char array to the string builder. Appending null will call {@link #appendNull()}. + * + * @param chars + * the char array to append + * @return this, to enable chaining + */ + public TextStringBuilder append(final char[] chars) { + if (chars == null) { + return appendNull(); + } + final int strLen = chars.length; + if (strLen > 0) { + final int len = length(); + ensureCapacity(len + strLen); + System.arraycopy(chars, 0, buffer, len, strLen); + size += strLen; + } + return this; + } + + /** + * Appends a char array to the string builder. Appending null will call {@link #appendNull()}. + * + * @param chars + * the char array to append + * @param startIndex + * the start index, inclusive, must be valid + * @param length + * the length to append, must be valid + * @return this, to enable chaining + */ + public TextStringBuilder append(final char[] chars, final int startIndex, final int length) { + if (chars == null) { + return appendNull(); + } + if (startIndex < 0 || startIndex > chars.length) { + throw new StringIndexOutOfBoundsException("Invalid startIndex: " + length); + } + if (length < 0 || (startIndex + length) > chars.length) { + throw new StringIndexOutOfBoundsException("Invalid length: " + length); + } + if (length > 0) { + final int len = length(); + ensureCapacity(len + length); + System.arraycopy(chars, startIndex, buffer, len, length); + size += length; + } + return this; + } + + /** + * Appends a boolean value to the string builder. + * + * @param value + * the value to append + * @return this, to enable chaining + */ + public TextStringBuilder append(final boolean value) { + if (value) { + ensureCapacity(size + TRUE_STRING_SIZE); + buffer[size++] = 't'; + buffer[size++] = 'r'; + buffer[size++] = 'u'; + buffer[size++] = 'e'; + } else { + ensureCapacity(size + FALSE_STRING_SIZE); + buffer[size++] = 'f'; + buffer[size++] = 'a'; + buffer[size++] = 'l'; + buffer[size++] = 's'; + buffer[size++] = 'e'; + } + return this; + } + + /** + * Appends a char value to the string builder. + * + * @param ch + * the value to append + * @return this, to enable chaining + */ + @Override + public TextStringBuilder append(final char ch) { + final int len = length(); + ensureCapacity(len + 1); + buffer[size++] = ch; + return this; + } + + /** + * Appends an int value to the string builder using <code>String.valueOf</code>. + * + * @param value + * the value to append + * @return this, to enable chaining + */ + public TextStringBuilder append(final int value) { + return append(String.valueOf(value)); + } + + /** + * Appends a long value to the string builder using <code>String.valueOf</code>. + * + * @param value + * the value to append + * @return this, to enable chaining + */ + public TextStringBuilder append(final long value) { + return append(String.valueOf(value)); + } + + /** + * Appends a float value to the string builder using <code>String.valueOf</code>. + * + * @param value + * the value to append + * @return this, to enable chaining + */ + public TextStringBuilder append(final float value) { + return append(String.valueOf(value)); + } + + /** + * Appends a double value to the string builder using <code>String.valueOf</code>. + * + * @param value + * the value to append + * @return this, to enable chaining + */ + public TextStringBuilder append(final double value) { + return append(String.valueOf(value)); + } + + // ----------------------------------------------------------------------- + /** + * Appends an object followed by a new line to this string builder. Appending null will call {@link #appendNull()}. + * + * @param obj + * the object to append + * @return this, to enable chaining + */ + public TextStringBuilder appendln(final Object obj) { + return append(obj).appendNewLine(); + } + + /** + * Appends a string followed by a new line to this string builder. Appending null will call {@link #appendNull()}. + * + * @param str + * the string to append + * @return this, to enable chaining + */ + public TextStringBuilder appendln(final String str) { + return append(str).appendNewLine(); + } + + /** + * Appends part of a string followed by a new line to this string builder. Appending null will call + * {@link #appendNull()}. + * + * @param str + * the string to append + * @param startIndex + * the start index, inclusive, must be valid + * @param length + * the length to append, must be valid + * @return this, to enable chaining + */ + public TextStringBuilder appendln(final String str, final int startIndex, final int length) { + return append(str, startIndex, length).appendNewLine(); + } + + /** + * Calls {@link String#format(String, Object...)} and appends the result. + * + * @param format + * the format string + * @param objs + * the objects to use in the format string + * @return {@code this} to enable chaining + * @see String#format(String, Object...) + */ + public TextStringBuilder appendln(final String format, final Object... objs) { + return append(format, objs).appendNewLine(); + } + + /** + * Appends a string buffer followed by a new line to this string builder. Appending null will call + * {@link #appendNull()}. + * + * @param str + * the string buffer to append + * @return this, to enable chaining + */ + public TextStringBuilder appendln(final StringBuffer str) { + return append(str).appendNewLine(); + } + + /** + * Appends a string builder followed by a new line to this string builder. Appending null will call + * {@link #appendNull()}. + * + * @param str + * the string builder to append + * @return this, to enable chaining + */ + public TextStringBuilder appendln(final StringBuilder str) { + return append(str).appendNewLine(); + } + + /** + * Appends part of a string builder followed by a new line to this string builder. Appending null will call + * {@link #appendNull()}. + * + * @param str + * the string builder to append + * @param startIndex + * the start index, inclusive, must be valid + * @param length + * the length to append, must be valid + * @return this, to enable chaining + */ + public TextStringBuilder appendln(final StringBuilder str, final int startIndex, final int length) { + return append(str, startIndex, length).appendNewLine(); + } + + /** + * Appends part of a string buffer followed by a new line to this string builder. Appending null will call + * {@link #appendNull()}. + * + * @param str + * the string to append + * @param startIndex + * the start index, inclusive, must be valid + * @param length + * the length to append, must be valid + * @return this, to enable chaining + */ + public TextStringBuilder appendln(final StringBuffer str, final int startIndex, final int length) { + return append(str, startIndex, length).appendNewLine(); + } + + /** + * Appends another string builder followed by a new line to this string builder. Appending null will call + * {@link #appendNull()}. + * + * @param str + * the string builder to append + * @return this, to enable chaining + */ + public TextStringBuilder appendln(final TextStringBuilder str) { + return append(str).appendNewLine(); + } + + /** + * Appends part of a string builder followed by a new line to this string builder. Appending null will call + * {@link #appendNull()}. + * + * @param str + * the string to append + * @param startIndex + * the start index, inclusive, must be valid + * @param length + * the length to append, must be valid + * @return this, to enable chaining + */ + public TextStringBuilder appendln(final TextStringBuilder str, final int startIndex, final int length) { + return append(str, startIndex, length).appendNewLine(); + } + + /** + * Appends a char array followed by a new line to the string builder. Appending null will call + * {@link #appendNull()}. + * + * @param chars + * the char array to append + * @return this, to enable chaining + */ + public TextStringBuilder appendln(final char[] chars) { + return append(chars).appendNewLine(); + } + + /** + * Appends a char array followed by a new line to the string builder. Appending null will call + * {@link #appendNull()}. + * + * @param chars + * the char array to append + * @param startIndex + * the start index, inclusive, must be valid + * @param length + * the length to append, must be valid + * @return this, to enable chaining + */ + public TextStringBuilder appendln(final char[] chars, final int startIndex, final int length) { + return append(chars, startIndex, length).appendNewLine(); + } + + /** + * Appends a boolean value followed by a new line to the string builder. + * + * @param value + * the value to append + * @return this, to enable chaining + */ + public TextStringBuilder appendln(final boolean value) { + return append(value).appendNewLine(); + } + + /** + * Appends a char value followed by a new line to the string builder. + * + * @param ch + * the value to append + * @return this, to enable chaining + */ + public TextStringBuilder appendln(final char ch) { + return append(ch).appendNewLine(); + } + + /** + * Appends an int value followed by a new line to the string builder using <code>String.valueOf</code>. + * + * @param value + * the value to append + * @return this, to enable chaining + */ + public TextStringBuilder appendln(final int value) { + return append(value).appendNewLine(); + } + + /** + * Appends a long value followed by a new line to the string builder using <code>String.valueOf</code>. + * + * @param value + * the value to append + * @return this, to enable chaining + */ + public TextStringBuilder appendln(final long value) { + return append(value).appendNewLine(); + } + + /** + * Appends a float value followed by a new line to the string builder using <code>String.valueOf</code>. + * + * @param value + * the value to append + * @return this, to enable chaining + */ + public TextStringBuilder appendln(final float value) { + return append(value).appendNewLine(); + } + + /** + * Appends a double value followed by a new line to the string builder using <code>String.valueOf</code>. + * + * @param value + * the value to append + * @return this, to enable chaining + */ + public TextStringBuilder appendln(final double value) { + return append(value).appendNewLine(); + } + + // ----------------------------------------------------------------------- + /** + * Appends each item in an array to the builder without any separators. Appending a null array will have no effect. + * Each object is appended using {@link #append(Object)}. + * + * @param <T> + * the element type + * @param array + * the array to append + * @return this, to enable chaining + */ + public <T> TextStringBuilder appendAll(@SuppressWarnings("unchecked") final T... array) { + /* + * @SuppressWarnings used to hide warning about vararg usage. We cannot use @SafeVarargs, since this method is + * not final. Using @SuppressWarnings is fine, because it isn't inherited by subclasses, so each subclass must + * vouch for itself whether its use of 'array' is safe. + */ + if (array != null && array.length > 0) { + for (final Object element : array) { + append(element); + } + } + return this; + } + + /** + * Appends each item in an iterable to the builder without any separators. Appending a null iterable will have no + * effect. Each object is appended using {@link #append(Object)}. + * + * @param iterable + * the iterable to append + * @return this, to enable chaining + */ + public TextStringBuilder appendAll(final Iterable<?> iterable) { + if (iterable != null) { + for (final Object o : iterable) { + append(o); + } + } + return this; + } + + /** + * Appends each item in an iterator to the builder without any separators. Appending a null iterator will have no + * effect. Each object is appended using {@link #append(Object)}. + * + * @param it + * the iterator to append + * @return this, to enable chaining + */ + public TextStringBuilder appendAll(final Iterator<?> it) { + if (it != null) { + while (it.hasNext()) { + append(it.next()); + } + } + return this; + } + + // ----------------------------------------------------------------------- + /** + * Appends an array placing separators between each value, but not before the first or after the last. Appending a + * null array will have no effect. Each object is appended using {@link #append(Object)}. + * + * @param array + * the array to append + * @param separator + * the separator to use, null means no separator + * @return this, to enable chaining + */ + public TextStringBuilder appendWithSeparators(final Object[] array, final String separator) { + if (array != null && array.length > 0) { + final String sep = Objects.toString(separator, ""); + append(array[0]); + for (int i = 1; i < array.length; i++) { + append(sep); + append(array[i]); + } + } + return this; + } + + /** + * Appends an iterable placing separators between each value, but not before the first or after the last. Appending + * a null iterable will have no effect. Each object is appended using {@link #append(Object)}. + * + * @param iterable + * the iterable to append + * @param separator + * the separator to use, null means no separator + * @return this, to enable chaining + */ + public TextStringBuilder appendWithSeparators(final Iterable<?> iterable, final String separator) { + if (iterable != null) { + final String sep = Objects.toString(separator, ""); + final Iterator<?> it = iterable.iterator(); + while (it.hasNext()) { + append(it.next()); + if (it.hasNext()) { + append(sep); + } + } + } + return this; + } + + /** + * Appends an iterator placing separators between each value, but not before the first or after the last. Appending + * a null iterator will have no effect. Each object is appended using {@link #append(Object)}. + * + * @param it + * the iterator to append + * @param separator + * the separator to use, null means no separator + * @return this, to enable chaining + */ + public TextStringBuilder appendWithSeparators(final Iterator<?> it, final String separator) { + if (it != null) { + final String sep = Objects.toString(separator, ""); + while (it.hasNext()) { + append(it.next()); + if (it.hasNext()) { + append(sep); + } + } + } + return this; + } + + // ----------------------------------------------------------------------- + /** + * Appends a separator if the builder is currently non-empty. Appending a null separator will have no effect. The + * separator is appended using {@link #append(String)}. + * <p> + * This method is useful for adding a separator each time around the loop except the first. + * + * <pre> + * for (Iterator it = list.iterator(); it.hasNext();) { + * appendSeparator(","); + * append(it.next()); + * } + * </pre> + * + * Note that for this simple example, you should use {@link #appendWithSeparators(Iterable, String)}. + * + * @param separator + * the separator to use, null means no separator + * @return this, to enable chaining + */ + public TextStringBuilder appendSeparator(final String separator) { + return appendSeparator(separator, null); + } + + /** + * Appends one of both separators to the StrBuilder. If the builder is currently empty it will append the + * defaultIfEmpty-separator Otherwise it will append the standard-separator + * + * Appending a null separator will have no effect. The separator is appended using {@link #append(String)}. + * <p> + * This method is for example useful for constructing queries + * + * <pre> + * StrBuilder whereClause = new StrBuilder(); + * if(searchCommand.getPriority() != null) { + * whereClause.appendSeparator(" and", " where"); + * whereClause.append(" priority = ?") + * } + * if(searchCommand.getComponent() != null) { + * whereClause.appendSeparator(" and", " where"); + * whereClause.append(" component = ?") + * } + * selectClause.append(whereClause) + * </pre> + * + * @param standard + * the separator if builder is not empty, null means no separator + * @param defaultIfEmpty + * the separator if builder is empty, null means no separator + * @return this, to enable chaining + */ + public TextStringBuilder appendSeparator(final String standard, final String defaultIfEmpty) { + final String str = isEmpty() ? defaultIfEmpty : standard; + if (str != null) { + append(str); + } + return this; + } + + /** + * Appends a separator if the builder is currently non-empty. The separator is appended using {@link #append(char)}. + * <p> + * This method is useful for adding a separator each time around the loop except the first. + * + * <pre> + * for (Iterator it = list.iterator(); it.hasNext();) { + * appendSeparator(','); + * append(it.next()); + * } + * </pre> + * + * Note that for this simple example, you should use {@link #appendWithSeparators(Iterable, String)}. + * + * @param separator + * the separator to use + * @return this, to enable chaining + */ + public TextStringBuilder appendSeparator(final char separator) { + if (size() > 0) { + append(separator); + } + return this; + } + + /** + * Append one of both separators to the builder If the builder is currently empty it will append the + * defaultIfEmpty-separator Otherwise it will append the standard-separator + * + * The separator is appended using {@link #append(char)}. + * + * @param standard + * the separator if builder is not empty + * @param defaultIfEmpty + * the separator if builder is empty + * @return this, to enable chaining + */ + public TextStringBuilder appendSeparator(final char standard, final char defaultIfEmpty) { + if (size() > 0) { + append(standard); + } else { + append(defaultIfEmpty); + } + return this; + } + + /** + * Appends a separator to the builder if the loop index is greater than zero. Appending a null separator will have + * no effect. The separator is appended using {@link #append(String)}. + * <p> + * This method is useful for adding a separator each time around the loop except the first. + * </p> + * + * <pre> + * for (int i = 0; i < list.size(); i++) { + * appendSeparator(",", i); + * append(list.get(i)); + * } + * </pre> + * + * Note that for this simple example, you should use {@link #appendWithSeparators(Iterable, String)}. + * + * @param separator + * the separator to use, null means no separator + * @param loopIndex + * the loop index + * @return this, to enable chaining + */ + public TextStringBuilder appendSeparator(final String separator, final int loopIndex) { + if (separator != null && loopIndex > 0) { + append(separator); + } + return this; + } + + /** + * Appends a separator to the builder if the loop index is greater than zero. The separator is appended using + * {@link #append(char)}. + * <p> + * This method is useful for adding a separator each time around the loop except the first. + * </p> + * + * <pre> + * for (int i = 0; i < list.size(); i++) { + * appendSeparator(",", i); + * append(list.get(i)); + * } + * </pre> + * + * Note that for this simple example, you should use {@link #appendWithSeparators(Iterable, String)}. + * + * @param separator + * the separator to use + * @param loopIndex + * the loop index + * @return this, to enable chaining + */ + public TextStringBuilder appendSeparator(final char separator, final int loopIndex) { + if (loopIndex > 0) { + append(separator); + } + return this; + } + + // ----------------------------------------------------------------------- + /** + * Appends the pad character to the builder the specified number of times. + * + * @param length + * the length to append, negative means no append + * @param padChar + * the character to append + * @return this, to enable chaining + */ + public TextStringBuilder appendPadding(final int length, final char padChar) { + if (length >= 0) { + ensureCapacity(size + length); + for (int i = 0; i < length; i++) { + buffer[size++] = padChar; + } + } + return this; + } + + // ----------------------------------------------------------------------- + /** + * Appends an object to the builder padding on the left to a fixed width. The <code>toString</code> of the object is + * used. If the object is larger than the length, the left hand side is lost. If the object is null, the null text + * value is used. + * + * @param obj + * the object to append, null uses null text + * @param width + * the fixed field width, zero or negative has no effect + * @param padChar + * the pad character to use + * @return this, to enable chaining + */ + public TextStringBuilder appendFixedWidthPadLeft(final Object obj, final int width, final char padChar) { + if (width > 0) { + ensureCapacity(size + width); + String str = (obj == null ? getNullText() : obj.toString()); + if (str == null) { + str = ""; + } + final int strLen = str.length(); + if (strLen >= width) { + str.getChars(strLen - width, strLen, buffer, size); + } else { + final int padLen = width - strLen; + for (int i = 0; i < padLen; i++) { + buffer[size + i] = padChar; + } + str.getChars(0, strLen, buffer, size + padLen); + } + size += width; + } + return this; + } + + /** + * Appends an object to the builder padding on the left to a fixed width. The <code>String.valueOf</code> of the + * <code>int</code> value is used. If the formatted value is larger than the length, the left hand side is lost. + * + * @param value + * the value to append + * @param width + * the fixed field width, zero or negative has no effect + * @param padChar + * the pad character to use + * @return this, to enable chaining + */ + public TextStringBuilder appendFixedWidthPadLeft(final int value, final int width, final char padChar) { + return appendFixedWidthPadLeft(String.valueOf(value), width, padChar); + } + + /** + * Appends an object to the builder padding on the right to a fixed length. The <code>toString</code> of the object + * is used. If the object is larger than the length, the right hand side is lost. If the object is null, null text + * value is used. + * + * @param obj + * the object to append, null uses null text + * @param width + * the fixed field width, zero or negative has no effect + * @param padChar + * the pad character to use + * @return this, to enable chaining + */ + public TextStringBuilder appendFixedWidthPadRight(final Object obj, final int width, final char padChar) { + if (width > 0) { + ensureCapacity(size + width); + String str = (obj == null ? getNullText() : obj.toString()); + if (str == null) { + str = ""; + } + final int strLen = str.length(); + if (strLen >= width) { + str.getChars(0, width, buffer, size); + } else { + final int padLen = width - strLen; + str.getChars(0, strLen, buffer, size); + for (int i = 0; i < padLen; i++) { + buffer[size + strLen + i] = padChar; + } + } + size += width; + } + return this; + } + + /** + * Appends an object to the builder padding on the right to a fixed length. The <code>String.valueOf</code> of the + * <code>int</code> value is used. If the object is larger than the length, the right hand side is lost. + * + * @param value + * the value to append + * @param width + * the fixed field width, zero or negative has no effect + * @param padChar + * the pad character to use + * @return this, to enable chaining + */ + public TextStringBuilder appendFixedWidthPadRight(final int value, final int width, final char padChar) { + return appendFixedWidthPadRight(String.valueOf(value), width, padChar); + } + + // ----------------------------------------------------------------------- + /** + * Inserts the string representation of an object into this builder. Inserting null will use the stored null text + * value. + * + * @param index + * the index to add at, must be valid + * @param obj + * the object to insert + * @return this, to enable chaining + * @throws IndexOutOfBoundsException + * if the index is invalid + */ + public TextStringBuilder insert(final int index, final Object obj) { + if (obj == null) { + return insert(index, nullText); + } + return insert(index, obj.toString()); + } + + /** + * Inserts the string into this builder. Inserting null will use the stored null text value. + * + * @param index + * the index to add at, must be valid + * @param str + * the string to insert + * @return this, to enable chaining + * @throws IndexOutOfBoundsException + * if the index is invalid + */ + public TextStringBuilder insert(final int index, String str) { + validateIndex(index); + if (str == null) { + str = nullText; + } + if (str != null) { + final int strLen = str.length(); + if (strLen > 0) { + final int newSize = size + strLen; + ensureCapacity(newSize); + System.arraycopy(buffer, index, buffer, index + strLen, size - index); + size = newSize; + str.getChars(0, strLen, buffer, index); + } + } + return this; + } + + /** + * Inserts the character array into this builder. Inserting null will use the stored null text value. + * + * @param index + * the index to add at, must be valid + * @param chars + * the char array to insert + * @return this, to enable chaining + * @throws IndexOutOfBoundsException + * if the index is invalid + */ + public TextStringBuilder insert(final int index, final char[] chars) { + validateIndex(index); + if (chars == null) { + return insert(index, nullText); + } + final int len = chars.length; + if (len > 0) { + ensureCapacity(size + len); + System.arraycopy(buffer, index, buffer, index + len, size - index); + System.arraycopy(chars, 0, buffer, index, len); + size += len; + } + return this; + } + + /** + * Inserts part of the character array into this builder. Inserting null will use the stored null text value. + * + * @param index + * the index to add at, must be valid + * @param chars + * the char array to insert + * @param offset + * the offset into the character array to start at, must be valid + * @param length + * the length of the character array part to copy, must be positive + * @return this, to enable chaining + * @throws IndexOutOfBoundsException + * if any index is invalid + */ + public TextStringBuilder insert(final int index, final char[] chars, final int offset, final int length) { + validateIndex(index); + if (chars == null) { + return insert(index, nullText); + } + if (offset < 0 || offset > chars.length) { + throw new StringIndexOutOfBoundsException("Invalid offset: " + offset); + } + if (length < 0 || offset + length > chars.length) { + throw new StringIndexOutOfBoundsException("Invalid length: " + length); + } + if (length > 0) { + ensureCapacity(size + length); + System.arraycopy(buffer, index, buffer, index + length, size - index); + System.arraycopy(chars, offset, buffer, index, length); + size += length; + } + return this; + } + + /** + * Inserts the value into this builder. + * + * @param index + * the index to add at, must be valid + * @param value + * the value to insert + * @return this, to enable chaining + * @throws IndexOutOfBoundsException + * if the index is invalid + */ + public TextStringBuilder insert(int index, final boolean value) { + validateIndex(index); + if (value) { + ensureCapacity(size + TRUE_STRING_SIZE); + System.arraycopy(buffer, index, buffer, index + TRUE_STRING_SIZE, size - index); + buffer[index++] = 't'; + buffer[index++] = 'r'; + buffer[index++] = 'u'; + buffer[index] = 'e'; + size += TRUE_STRING_SIZE; + } else { + ensureCapacity(size + FALSE_STRING_SIZE); + System.arraycopy(buffer, index, buffer, index + FALSE_STRING_SIZE, size - index); + buffer[index++] = 'f'; + buffer[index++] = 'a'; + buffer[index++] = 'l'; + buffer[index++] = 's'; + buffer[index] = 'e'; + size += FALSE_STRING_SIZE; + } + return this; + } + + /** + * Inserts the value into this builder. + * + * @param index + * the index to add at, must be valid + * @param value + * the value to insert + * @return this, to enable chaining + * @throws IndexOutOfBoundsException + * if the index is invalid + */ + public TextStringBuilder insert(final int index, final char value) { + validateIndex(index); + ensureCapacity(size + 1); + System.arraycopy(buffer, index, buffer, index + 1, size - index); + buffer[index] = value; + size++; + return this; + } + + /** + * Inserts the value into this builder. + * + * @param index + * the index to add at, must be valid + * @param value + * the value to insert + * @return this, to enable chaining + * @throws IndexOutOfBoundsException + * if the index is invalid + */ + public TextStringBuilder insert(final int index, final int value) { + return insert(index, String.valueOf(value)); + } + + /** + * Inserts the value into this builder. + * + * @param index + * the index to add at, must be valid + * @param value + * the value to insert + * @return this, to enable chaining + * @throws IndexOutOfBoundsException + * if the index is invalid + */ + public TextStringBuilder insert(final int index, final long value) { + return insert(index, String.valueOf(value)); + } + + /** + * Inserts the value into this builder. + * + * @param index + * the index to add at, must be valid + * @param value + * the value to insert + * @return this, to enable chaining + * @throws IndexOutOfBoundsException + * if the index is invalid + */ + public TextStringBuilder insert(final int index, final float value) { + return insert(index, String.valueOf(value)); + } + + /** + * Inserts the value into this builder. + * + * @param index + * the index to add at, must be valid + * @param value + * the value to insert + * @return this, to enable chaining + * @throws IndexOutOfBoundsException + * if the index is invalid + */ + public TextStringBuilder insert(final int index, final double value) { + return insert(index, String.valueOf(value)); + } + + // ----------------------------------------------------------------------- + /** + * Internal method to delete a range without validation. + * + * @param startIndex + * the start index, must be valid + * @param endIndex + * the end index (exclusive), must be valid + * @param len + * the length, must be valid + * @throws IndexOutOfBoundsException + * if any index is invalid + */ + private void deleteImpl(final int startIndex, final int endIndex, final int len) { + System.arraycopy(buffer, endIndex, buffer, startIndex, size - endIndex); + size -= len; + } + + /** + * Deletes the characters between the two specified indices. + * + * @param startIndex + * the start index, inclusive, must be valid + * @param endIndex + * the end index, exclusive, must be valid except that if too large it is treated as end of string + * @return this, to enable chaining + * @throws IndexOutOfBoundsException + * if the index is invalid + */ + public TextStringBuilder delete(final int startIndex, int endIndex) { + endIndex = validateRange(startIndex, endIndex); + final int len = endIndex - startIndex; + if (len > 0) { + deleteImpl(startIndex, endIndex, len); + } + return this; + } + + // ----------------------------------------------------------------------- + /** + * Deletes the character wherever it occurs in the builder. + * + * @param ch + * the character to delete + * @return this, to enable chaining + */ + public TextStringBuilder deleteAll(final char ch) { + for (int i = 0; i < size; i++) { + if (buffer[i] == ch) { + final int start = i; + while (++i < size) { + if (buffer[i] != ch) { + break; + } + } + final int len = i - start; + deleteImpl(start, i, len); + i -= len; + } + } + return this; + } + + /** + * Deletes the character wherever it occurs in the builder. + * + * @param ch + * the character to delete + * @return this, to enable chaining + */ + public TextStringBuilder deleteFirst(final char ch) { + for (int i = 0; i < size; i++) { + if (buffer[i] == ch) { + deleteImpl(i, i + 1, 1); + break; + } + } + return this; + } + + // ----------------------------------------------------------------------- + /** + * Deletes the string wherever it occurs in the builder. + * + * @param str + * the string to delete, null causes no action + * @return this, to enable chaining + */ + public TextStringBuilder deleteAll(final String str) { + final int len = (str == null ? 0 : str.length()); + if (len > 0) { + int index = indexOf(str, 0); + while (index >= 0) { + deleteImpl(index, index + len, len); + index = indexOf(str, index); + } + } + return this; + } + + /** + * Deletes the string wherever it occurs in the builder. + * + * @param str + * the string to delete, null causes no action + * @return this, to enable chaining + */ + public TextStringBuilder deleteFirst(final String str) { + final int len = (str == null ? 0 : str.length()); + if (len > 0) { + final int index = indexOf(str, 0); + if (index >= 0) { + deleteImpl(index, index + len, len); + } + } + return this; + } + + // ----------------------------------------------------------------------- + /** + * Deletes all parts of the builder that the matcher matches. + * <p> + * Matchers can be used to perform advanced deletion behaviour. For example you could write a matcher to delete all + * occurrences where the character 'a' is followed by a number. + * + * @param matcher + * the matcher to use to find the deletion, null causes no action + * @return this, to enable chaining + */ + public TextStringBuilder deleteAll(final StringMatcher matcher) { + return replace(matcher, null, 0, size, -1); + } + + /** + * Deletes the first match within the builder using the specified matcher. + * <p> + * Matchers can be used to perform advanced deletion behaviour. For example you could write a matcher to delete + * where the character 'a' is followed by a number. + * + * @param matcher + * the matcher to use to find the deletion, null causes no action + * @return this, to enable chaining + */ + public TextStringBuilder deleteFirst(final StringMatcher matcher) { + return replace(matcher, null, 0, size, 1); + } + + // ----------------------------------------------------------------------- + /** + * Internal method to delete a range without validation. + * + * @param startIndex + * the start index, must be valid + * @param endIndex + * the end index (exclusive), must be valid + * @param removeLen + * the length to remove (endIndex - startIndex), must be valid + * @param insertStr + * the string to replace with, null means delete range + * @param insertLen + * the length of the insert string, must be valid + * @throws IndexOutOfBoundsException + * if any index is invalid + */ + private void replaceImpl(final int startIndex, final int endIndex, final int removeLen, final String insertStr, + final int insertLen) { + final int newSize = size - removeLen + insertLen; + if (insertLen != removeLen) { + ensureCapacity(newSize); + System.arraycopy(buffer, endIndex, buffer, startIndex + insertLen, size - endIndex); + size = newSize; + } + if (insertLen > 0) { + insertStr.getChars(0, insertLen, buffer, startIndex); + } + } + + /** + * Replaces a portion of the string builder with another string. The length of the inserted string does not have to + * match the removed length. + * + * @param startIndex + * the start index, inclusive, must be valid + * @param endIndex + * the end index, exclusive, must be valid except that if too large it is treated as end of string + * @param replaceStr + * the string to replace with, null means delete range + * @return this, to enable chaining + * @throws IndexOutOfBoundsException + * if the index is invalid + */ + public TextStringBuilder replace(final int startIndex, int endIndex, final String replaceStr) { + endIndex = validateRange(startIndex, endIndex); + final int insertLen = (replaceStr == null ? 0 : replaceStr.length()); + replaceImpl(startIndex, endIndex, endIndex - startIndex, replaceStr, insertLen); + return this; + } + + // ----------------------------------------------------------------------- + /** + * Replaces the search character with the replace character throughout the builder. + * + * @param search + * the search character + * @param replace + * the replace character + * @return this, to enable chaining + */ + public TextStringBuilder replaceAll(final char search, final char replace) { + if (search != replace) { + for (int i = 0; i < size; i++) { + if (buffer[i] == search) { + buffer[i] = replace; + } + } + } + return this; + } + + /** + * Replaces the first instance of the search character with the replace character in the builder. + * + * @param search + * the search character + * @param replace + * the replace character + * @return this, to enable chaining + */ + public TextStringBuilder replaceFirst(final char search, final char replace) { + if (search != replace) { + for (int i = 0; i < size; i++) { + if (buffer[i] == search) { + buffer[i] = replace; + break; + } + } + } + return this; + } + + // ----------------------------------------------------------------------- + /** + * Replaces the search string with the replace string throughout the builder. + * + * @param searchStr + * the search string, null causes no action to occur + * @param replaceStr + * the replace string, null is equivalent to an empty string + * @return this, to enable chaining + */ + public TextStringBuilder replaceAll(final String searchStr, final String replaceStr) { + final int searchLen = (searchStr == null ? 0 : searchStr.length()); + if (searchLen > 0) { + final int replaceLen = (replaceStr == null ? 0 : replaceStr.length()); + int index = indexOf(searchStr, 0); + while (index >= 0) { + replaceImpl(index, index + searchLen, searchLen, replaceStr, replaceLen); + index = indexOf(searchStr, index + replaceLen); + } + } + return this; + } + + /** + * Replaces the first instance of the search string with the replace string. + * + * @param searchStr + * the search string, null causes no action to occur + * @param replaceStr + * the replace string, null is equivalent to an empty string + * @return this, to enable chaining + */ + public TextStringBuilder replaceFirst(final String searchStr, final String replaceStr) { + final int searchLen = (searchStr == null ? 0 : searchStr.length()); + if (searchLen > 0) { + final int index = indexOf(searchStr, 0); + if (index >= 0) { + final int replaceLen = (replaceStr == null ? 0 : replaceStr.length()); + replaceImpl(index, index + searchLen, searchLen, replaceStr, replaceLen); + } + } + return this; + } + + // ----------------------------------------------------------------------- + /** + * Replaces all matches within the builder with the replace string. + * <p> + * Matchers can be used to perform advanced replace behaviour. For example you could write a matcher to replace all + * occurrences where the character 'a' is followed by a number. + * + * @param matcher + * the matcher to use to find the deletion, null causes no action + * @param replaceStr + * the replace string, null is equivalent to an empty string + * @return this, to enable chaining + */ + public TextStringBuilder replaceAll(final StringMatcher matcher, final String replaceStr) { + return replace(matcher, replaceStr, 0, size, -1); + } + + /** + * Replaces the first match within the builder with the replace string. + * <p> + * Matchers can be used to perform advanced replace behaviour. For example you could write a matcher to replace + * where the character 'a' is followed by a number. + * + * @param matcher + * the matcher to use to find the deletion, null causes no action + * @param replaceStr + * the replace string, null is equivalent to an empty string + * @return this, to enable chaining + */ + public TextStringBuilder replaceFirst(final StringMatcher matcher, final String replaceStr) { + return replace(matcher, replaceStr, 0, size, 1); + } + + // ----------------------------------------------------------------------- + /** + * Advanced search and replaces within the builder using a matcher. + * <p> + * Matchers can be used to perform advanced behaviour. For example you could write a matcher to delete all + * occurrences where the character 'a' is followed by a number. + * + * @param matcher + * the matcher to use to find the deletion, null causes no action + * @param replaceStr + * the string to replace the match with, null is a delete + * @param startIndex + * the start index, inclusive, must be valid + * @param endIndex + * the end index, exclusive, must be valid except that if too large it is treated as end of string + * @param replaceCount + * the number of times to replace, -1 for replace all + * @return this, to enable chaining + * @throws IndexOutOfBoundsException + * if start index is invalid + */ + public TextStringBuilder replace(final StringMatcher matcher, final String replaceStr, final int startIndex, + int endIndex, final int replaceCount) { + endIndex = validateRange(startIndex, endIndex); + return replaceImpl(matcher, replaceStr, startIndex, endIndex, replaceCount); + } + + /** + * Replaces within the builder using a matcher. + * <p> + * Matchers can be used to perform advanced behaviour. For example you could write a matcher to delete all + * occurrences where the character 'a' is followed by a number. + * + * @param matcher + * the matcher to use to find the deletion, null causes no action + * @param replaceStr + * the string to replace the match with, null is a delete + * @param from + * the start index, must be valid + * @param to + * the end index (exclusive), must be valid + * @param replaceCount + * the number of times to replace, -1 for replace all + * @return this, to enable chaining + * @throws IndexOutOfBoundsException + * if any index is invalid + */ + private TextStringBuilder replaceImpl(final StringMatcher matcher, final String replaceStr, final int from, int to, + int replaceCount) { + if (matcher == null || size == 0) { + return this; + } + final int replaceLen = (replaceStr == null ? 0 : replaceStr.length()); + for (int i = from; i < to && replaceCount != 0; i++) { + final char[] buf = buffer; + final int removeLen = matcher.isMatch(buf, i, from, to); + if (removeLen > 0) { + replaceImpl(i, i + removeLen, removeLen, replaceStr, replaceLen); + to = to - removeLen + replaceLen; + i = i + replaceLen - 1; + if (replaceCount > 0) { + replaceCount--; + } + } + } + return this; + } + + // ----------------------------------------------------------------------- + /** + * Reverses the string builder placing each character in the opposite index. + * + * @return this, to enable chaining + */ + public TextStringBuilder reverse() { + if (size == 0) { + return this; + } + + final int half = size / 2; + final char[] buf = buffer; + for (int leftIdx = 0, rightIdx = size - 1; leftIdx < half; leftIdx++, rightIdx--) { + final char swap = buf[leftIdx]; + buf[leftIdx] = buf[rightIdx]; + buf[rightIdx] = swap; + } + return this; + } + + // ----------------------------------------------------------------------- + /** + * Trims the builder by removing characters less than or equal to a space from the beginning and end. + * + * @return this, to enable chaining + */ + public TextStringBuilder trim() { + if (size == 0) { + return this; + } + int len = size; + final char[] buf = buffer; + int pos = 0; + while (pos < len && buf[pos] <= ' ') { + pos++; + } + while (pos < len && buf[len - 1] <= ' ') { + len--; + } + if (len < size) { + delete(len, size); + } + if (pos > 0) { + delete(0, pos); + } + return this; + } + + // ----------------------------------------------------------------------- + /** + * Checks whether this builder starts with the specified string. + * <p> + * Note that this method handles null input quietly, unlike String. + * + * @param str + * the string to search for, null returns false + * @return true if the builder starts with the string + */ + public boolean startsWith(final String str) { + if (str == null) { + return false; + } + final int len = str.length(); + if (len == 0) { + return true; + } + if (len > size) { + return false; + } + for (int i = 0; i < len; i++) { + if (buffer[i] != str.charAt(i)) { + return false; + } + } + return true; + } + + /** + * Checks whether this builder ends with the specified string. + * <p> + * Note that this method handles null input quietly, unlike String. + * + * @param str + * the string to search for, null returns false + * @return true if the builder ends with the string + */ + public boolean endsWith(final String str) { + if (str == null) { + return false; + } + final int len = str.length(); + if (len == 0) { + return true; + } + if (len > size) { + return false; + } + int pos = size - len; + for (int i = 0; i < len; i++, pos++) { + if (buffer[pos] != str.charAt(i)) { + return false; + } + } + return true; + } + + // ----------------------------------------------------------------------- + /** + * {@inheritDoc} + */ + @Override + public CharSequence subSequence(final int startIndex, final int endIndex) { + if (startIndex < 0) { + throw new StringIndexOutOfBoundsException(startIndex); + } + if (endIndex > size) { + throw new StringIndexOutOfBoundsException(endIndex); + } + if (startIndex > endIndex) { + throw new StringIndexOutOfBoundsException(endIndex - startIndex); + } + return substring(startIndex, endIndex); + } + + /** + * Extracts a portion of this string builder as a string. + * + * @param start + * the start index, inclusive, must be valid + * @return the new string + * @throws IndexOutOfBoundsException + * if the index is invalid + */ + public String substring(final int start) { + return substring(start, size); + } + + /** + * Extracts a portion of this string builder as a string. + * <p> + * Note: This method treats an endIndex greater than the length of the builder as equal to the length of the + * builder, and continues without error, unlike StringBuffer or String. + * + * @param startIndex + * the start index, inclusive, must be valid + * @param endIndex + * the end index, exclusive, must be valid except that if too large it is treated as end of string + * @return the new string + * @throws IndexOutOfBoundsException + * if the index is invalid + */ + public String substring(final int startIndex, int endIndex) { + endIndex = validateRange(startIndex, endIndex); + return new String(buffer, startIndex, endIndex - startIndex); + } + + /** + * Extracts the leftmost characters from the string builder without throwing an exception. + * <p> + * This method extracts the left <code>length</code> characters from the builder. If this many characters are not + * available, the whole builder is returned. Thus the returned string may be shorter than the length requested. + * + * @param length + * the number of characters to extract, negative returns empty string + * @return the new string + */ + public String leftString(final int length) { + if (length <= 0) { + return ""; + } else if (length >= size) { + return new String(buffer, 0, size); + } else { + return new String(buffer, 0, length); + } + } + + /** + * Extracts the rightmost characters from the string builder without throwing an exception. + * <p> + * This method extracts the right <code>length</code> characters from the builder. If this many characters are not + * available, the whole builder is returned. Thus the returned string may be shorter than the length requested. + * + * @param length + * the number of characters to extract, negative returns empty string + * @return the new string + */ + public String rightString(final int length) { + if (length <= 0) { + return ""; + } else if (length >= size) { + return new String(buffer, 0, size); + } else { + return new String(buffer, size - length, length); + } + } + + /** + * Extracts some characters from the middle of the string builder without throwing an exception. + * <p> + * This method extracts <code>length</code> characters from the builder at the specified index. If the index is + * negative it is treated as zero. If the index is greater than the builder size, it is treated as the builder size. + * If the length is negative, the empty string is returned. If insufficient characters are available in the builder, + * as much as possible is returned. Thus the returned string may be shorter than the length requested. + * + * @param index + * the index to start at, negative means zero + * @param length + * the number of characters to extract, negative returns empty string + * @return the new string + */ + public String midString(int index, final int length) { + if (index < 0) { + index = 0; + } + if (length <= 0 || index >= size) { + return ""; + } + if (size <= index + length) { + return new String(buffer, index, size - index); + } + return new String(buffer, index, length); + } + + // ----------------------------------------------------------------------- + /** + * Checks if the string builder contains the specified char. + * + * @param ch + * the character to find + * @return true if the builder contains the character + */ + public boolean contains(final char ch) { + final char[] thisBuf = buffer; + for (int i = 0; i < this.size; i++) { + if (thisBuf[i] == ch) { + return true; + } + } + return false; + } + + /** + * Checks if the string builder contains the specified string. + * + * @param str + * the string to find + * @return true if the builder contains the string + */ + public boolean contains(final String str) { + return indexOf(str, 0) >= 0; + } + + /** + * Checks if the string builder contains a string matched using the specified matcher. + * <p> + * Matchers can be used to perform advanced searching behaviour. For example you c
<TRUNCATED>