From 806fca5434f46dfa3a20f821af07583247455e3b Mon Sep 17 00:00:00 2001 From: tdurieux Date: Tue, 7 Mar 2017 13:26:49 +0100 Subject: [PATCH] buggy files form Lang #47 --- .../apache/commons/lang/text/StrBuilder.java | 2712 +++++++++++++++++ 1 file changed, 2712 insertions(+) create mode 100644 projects/Lang/47/org/apache/commons/lang/text/StrBuilder.java diff --git a/projects/Lang/47/org/apache/commons/lang/text/StrBuilder.java b/projects/Lang/47/org/apache/commons/lang/text/StrBuilder.java new file mode 100644 index 0000000..13281ce --- /dev/null +++ b/projects/Lang/47/org/apache/commons/lang/text/StrBuilder.java @@ -0,0 +1,2712 @@ +/* + * 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.lang.text; + +import java.io.Reader; +import java.io.Writer; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.lang.SystemUtils; + +/** + * Builds a string from constituent parts providing a more flexible and powerful API + * than StringBuffer. + *

+ * The main differences from StringBuffer/StringBuilder are: + *

+ *
  • Views + * + *
  • + * + *

    + * 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)}. + * + * @author Stephen Colebourne + * @since 2.2 + * @version $Id$ + */ +public class StrBuilder implements Cloneable { + + /** + * The extra capacity for new builders. + */ + static final int CAPACITY = 32; + + /** + * Required for serialization support. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = 7628716375283629643L; + + /** Internal data storage. */ + protected char[] buffer; + /** Current size of the buffer. */ + protected 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 StrBuilder() { + 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 StrBuilder(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 StrBuilder(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 StrBuilder setNewLineText(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 StrBuilder setNullText(String nullText) { + if (nullText != null && nullText.length() == 0) { + nullText = null; + } + this.nullText = nullText; + return this; + } + + //----------------------------------------------------------------------- + /** + * Gets the length of the string builder. + * + * @return the length + */ + 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 StrBuilder setLength(int length) { + if (length < 0) { + throw new StringIndexOutOfBoundsException(length); + } + if (length < size) { + size = length; + } else if (length > size) { + ensureCapacity(length); + int oldEnd = size; + 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 StrBuilder ensureCapacity(int capacity) { + if (capacity > buffer.length) { + char[] old = buffer; + buffer = new char[capacity]; + 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 StrBuilder minimizeCapacity() { + if (buffer.length > length()) { + char[] old = buffer; + buffer = new char[length()]; + System.arraycopy(old, 0, buffer, 0, size); + } + return this; + } + + //----------------------------------------------------------------------- + /** + * Gets the length of the string builder. + *

    + * 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). + *

    + * This method is the same as checking {@link #length()} and is provided to match the + * API of Collections. + * + * @return true if the size is 0. + */ + public boolean isEmpty() { + return size == 0; + } + + /** + * Clears the string builder (convenience Collections API style method). + *

    + * This method does not reduce the size of the internal character buffer. + * To do that, call clear() followed by {@link #minimizeCapacity()}. + *

    + * 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 StrBuilder 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 + */ + public char charAt(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 StrBuilder setCharAt(int index, 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 StrBuilder deleteCharAt(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 ArrayUtils.EMPTY_CHAR_ARRAY; + } + 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(int startIndex, int endIndex) { + endIndex = validateRange(startIndex, endIndex); + int len = endIndex - startIndex; + if (len == 0) { + return ArrayUtils.EMPTY_CHAR_ARRAY; + } + 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) { + 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(int startIndex, int endIndex, char destination[], 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); + } + + //----------------------------------------------------------------------- + /** + * Appends the new line string to this string builder. + *

    + * 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 StrBuilder appendNewLine() { + if (newLine == null) { + append(SystemUtils.LINE_SEPARATOR); + return this; + } + return append(newLine); + } + + /** + * Appends the text representing null to this string builder. + * + * @return this, to enable chaining + */ + public StrBuilder 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 StrBuilder append(Object obj) { + if (obj == null) { + return appendNull(); + } + return append(obj.toString()); + } + + /** + * 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 StrBuilder append(String str) { + if (str == null) { + return appendNull(); + } + int strLen = str.length(); + if (strLen > 0) { + 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 StrBuilder append(String str, int startIndex, 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) { + int len = length(); + ensureCapacity(len + length); + str.getChars(startIndex, startIndex + length, buffer, len); + size += 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 StrBuilder append(StringBuffer str) { + if (str == null) { + return appendNull(); + } + int strLen = str.length(); + if (strLen > 0) { + 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 StrBuilder append(StringBuffer str, int startIndex, 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) { + 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 StrBuilder append(StrBuilder str) { + if (str == null) { + return appendNull(); + } + int strLen = str.length(); + if (strLen > 0) { + 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 StrBuilder append(StrBuilder str, int startIndex, 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) { + 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 StrBuilder append(char[] chars) { + if (chars == null) { + return appendNull(); + } + int strLen = chars.length; + if (strLen > 0) { + 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 StrBuilder append(char[] chars, int startIndex, 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) { + 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 StrBuilder append(boolean value) { + if (value) { + ensureCapacity(size + 4); + buffer[size++] = 't'; + buffer[size++] = 'r'; + buffer[size++] = 'u'; + buffer[size++] = 'e'; + } else { + ensureCapacity(size + 5); + 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 + */ + public StrBuilder append(char ch) { + int len = length(); + ensureCapacity(len + 1); + buffer[size++] = ch; + return this; + } + + /** + * Appends an int value to the string builder using String.valueOf. + * + * @param value the value to append + * @return this, to enable chaining + */ + public StrBuilder append(int value) { + return append(String.valueOf(value)); + } + + /** + * Appends a long value to the string builder using String.valueOf. + * + * @param value the value to append + * @return this, to enable chaining + */ + public StrBuilder append(long value) { + return append(String.valueOf(value)); + } + + /** + * Appends a float value to the string builder using String.valueOf. + * + * @param value the value to append + * @return this, to enable chaining + */ + public StrBuilder append(float value) { + return append(String.valueOf(value)); + } + + /** + * Appends a double value to the string builder using String.valueOf. + * + * @param value the value to append + * @return this, to enable chaining + */ + public StrBuilder append(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 + * @since 2.3 + */ + public StrBuilder appendln(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 + * @since 2.3 + */ + public StrBuilder appendln(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 + * @since 2.3 + */ + public StrBuilder appendln(String str, int startIndex, int length) { + return append(str, startIndex, length).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 + * @since 2.3 + */ + public StrBuilder appendln(StringBuffer str) { + return append(str).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 + * @since 2.3 + */ + public StrBuilder appendln(StringBuffer str, int startIndex, 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 + * @since 2.3 + */ + public StrBuilder appendln(StrBuilder 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 + * @since 2.3 + */ + public StrBuilder appendln(StrBuilder str, int startIndex, 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 + * @since 2.3 + */ + public StrBuilder appendln(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 + * @since 2.3 + */ + public StrBuilder appendln(char[] chars, int startIndex, 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 + * @since 2.3 + */ + public StrBuilder appendln(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 + * @since 2.3 + */ + public StrBuilder appendln(char ch) { + return append(ch).appendNewLine(); + } + + /** + * Appends an int value followed by a new line to the string builder using String.valueOf. + * + * @param value the value to append + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendln(int value) { + return append(value).appendNewLine(); + } + + /** + * Appends a long value followed by a new line to the string builder using String.valueOf. + * + * @param value the value to append + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendln(long value) { + return append(value).appendNewLine(); + } + + /** + * Appends a float value followed by a new line to the string builder using String.valueOf. + * + * @param value the value to append + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendln(float value) { + return append(value).appendNewLine(); + } + + /** + * Appends a double value followed by a new line to the string builder using String.valueOf. + * + * @param value the value to append + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendln(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 array the array to append + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendAll(Object[] array) { + if (array != null && array.length > 0) { + for (int i = 0; i < array.length; i++) { + append(array[i]); + } + } + return this; + } + + /** + * Appends each item in a collection to the builder without any separators. + * Appending a null collection will have no effect. + * Each object is appended using {@link #append(Object)}. + * + * @param coll the collection to append + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendAll(Collection coll) { + if (coll != null && coll.size() > 0) { + Iterator it = coll.iterator(); + while (it.hasNext()) { + append(it.next()); + } + } + 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 + * @since 2.3 + */ + public StrBuilder appendAll(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 StrBuilder appendWithSeparators(Object[] array, String separator) { + if (array != null && array.length > 0) { + separator = (separator == null ? "" : separator); + append(array[0]); + for (int i = 1; i < array.length; i++) { + append(separator); + append(array[i]); + } + } + return this; + } + + /** + * Appends a collection placing separators between each value, but + * not before the first or after the last. + * Appending a null collection will have no effect. + * Each object is appended using {@link #append(Object)}. + * + * @param coll the collection to append + * @param separator the separator to use, null means no separator + * @return this, to enable chaining + */ + public StrBuilder appendWithSeparators(Collection coll, String separator) { + if (coll != null && coll.size() > 0) { + separator = (separator == null ? "" : separator); + Iterator it = coll.iterator(); + while (it.hasNext()) { + append(it.next()); + if (it.hasNext()) { + append(separator); + } + } + } + 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 StrBuilder appendWithSeparators(Iterator it, String separator) { + if (it != null) { + separator = (separator == null ? "" : separator); + while (it.hasNext()) { + append(it.next()); + if (it.hasNext()) { + append(separator); + } + } + } + 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)}. + *

    + * This method is useful for adding a separator each time around the + * loop except the first. + *

    +     * for (Iterator it = list.iterator(); it.hasNext(); ) {
    +     *   appendSeparator(",");
    +     *   append(it.next());
    +     * }
    +     * 
    + * Note that for this simple example, you should use + * {@link #appendWithSeparators(Collection, String)}. + * + * @param separator the separator to use, null means no separator + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendSeparator(String separator) { + if (separator != null && size() > 0) { + append(separator); + } + return this; + } + + /** + * Appends a separator if the builder is currently non-empty. + * The separator is appended using {@link #append(char)}. + *

    + * This method is useful for adding a separator each time around the + * loop except the first. + *

    +     * for (Iterator it = list.iterator(); it.hasNext(); ) {
    +     *   appendSeparator(',');
    +     *   append(it.next());
    +     * }
    +     * 
    + * Note that for this simple example, you should use + * {@link #appendWithSeparators(Collection, String)}. + * + * @param separator the separator to use + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendSeparator(char separator) { + if (size() > 0) { + append(separator); + } + 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)}. + *

    + * This method is useful for adding a separator each time around the + * loop except the first. + *

    +     * for (int i = 0; i < list.size(); i++) {
    +     *   appendSeparator(",", i);
    +     *   append(list.get(i));
    +     * }
    +     * 
    + * Note that for this simple example, you should use + * {@link #appendWithSeparators(Collection, String)}. + * + * @param separator the separator to use, null means no separator + * @param loopIndex the loop index + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendSeparator(String separator, 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)}. + *

    + * This method is useful for adding a separator each time around the + * loop except the first. + *

    +     * for (int i = 0; i < list.size(); i++) {
    +     *   appendSeparator(",", i);
    +     *   append(list.get(i));
    +     * }
    +     * 
    + * Note that for this simple example, you should use + * {@link #appendWithSeparators(Collection, String)}. + * + * @param separator the separator to use + * @param loopIndex the loop index + * @return this, to enable chaining + * @since 2.3 + */ + public StrBuilder appendSeparator(char separator, 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 StrBuilder appendPadding(int length, 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 toString 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 StrBuilder appendFixedWidthPadLeft(Object obj, int width, char padChar) { + if (width > 0) { + ensureCapacity(size + width); + String str = (obj == null ? getNullText() : obj.toString()); + int strLen = str.length(); + if (strLen >= width) { + str.getChars(strLen - width, strLen, buffer, size); + } else { + 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 String.valueOf of the int 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 StrBuilder appendFixedWidthPadLeft(int value, int width, char padChar) { + return appendFixedWidthPadLeft(String.valueOf(value), width, padChar); + } + + /** + * Appends an object to the builder padding on the right to a fixed length. + * The toString 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 StrBuilder appendFixedWidthPadRight(Object obj, int width, char padChar) { + if (width > 0) { + ensureCapacity(size + width); + String str = (obj == null ? getNullText() : obj.toString()); + int strLen = str.length(); + if (strLen >= width) { + str.getChars(0, width, buffer, size); + } else { + 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 String.valueOf of the int 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 StrBuilder appendFixedWidthPadRight(int value, int width, 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 StrBuilder insert(int index, 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 StrBuilder insert(int index, String str) { + validateIndex(index); + if (str == null) { + str = nullText; + } + int strLen = (str == null ? 0 : str.length()); + if (strLen > 0) { + 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 StrBuilder insert(int index, char chars[]) { + validateIndex(index); + if (chars == null) { + return insert(index, nullText); + } + 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 StrBuilder insert(int index, char chars[], int offset, 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 StrBuilder insert(int index, boolean value) { + validateIndex(index); + if (value) { + ensureCapacity(size + 4); + System.arraycopy(buffer, index, buffer, index + 4, size - index); + buffer[index++] = 't'; + buffer[index++] = 'r'; + buffer[index++] = 'u'; + buffer[index] = 'e'; + size += 4; + } else { + ensureCapacity(size + 5); + System.arraycopy(buffer, index, buffer, index + 5, size - index); + buffer[index++] = 'f'; + buffer[index++] = 'a'; + buffer[index++] = 'l'; + buffer[index++] = 's'; + buffer[index] = 'e'; + size += 5; + } + 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 StrBuilder insert(int index, 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 StrBuilder insert(int index, 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 StrBuilder insert(int index, 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 StrBuilder insert(int index, 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 StrBuilder insert(int index, 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(int startIndex, int endIndex, 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 StrBuilder delete(int startIndex, int endIndex) { + endIndex = validateRange(startIndex, endIndex); + 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 StrBuilder deleteAll(char ch) { + for (int i = 0; i < size; i++) { + if (buffer[i] == ch) { + int start = i; + while (++i < size) { + if (buffer[i] != ch) { + break; + } + } + 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 StrBuilder deleteFirst(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 StrBuilder deleteAll(String str) { + 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 StrBuilder deleteFirst(String str) { + int len = (str == null ? 0 : str.length()); + if (len > 0) { + 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. + *

    + * Matchers can be used to perform advanced deletion behaviour. + * For example you could write a matcher to delete all occurances + * 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 StrBuilder deleteAll(StrMatcher matcher) { + return replace(matcher, null, 0, size, -1); + } + + /** + * Deletes the first match within the builder using the specified matcher. + *

    + * 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 StrBuilder deleteFirst(StrMatcher 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(int startIndex, int endIndex, int removeLen, String insertStr, int insertLen) { + 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 StrBuilder replace(int startIndex, int endIndex, String replaceStr) { + endIndex = validateRange(startIndex, endIndex); + 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 StrBuilder replaceAll(char search, 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 StrBuilder replaceFirst(char search, 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 StrBuilder replaceAll(String searchStr, String replaceStr) { + int searchLen = (searchStr == null ? 0 : searchStr.length()); + if (searchLen > 0) { + 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 StrBuilder replaceFirst(String searchStr, String replaceStr) { + int searchLen = (searchStr == null ? 0 : searchStr.length()); + if (searchLen > 0) { + int index = indexOf(searchStr, 0); + if (index >= 0) { + 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. + *

    + * Matchers can be used to perform advanced replace behaviour. + * For example you could write a matcher to replace all occurances + * 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 StrBuilder replaceAll(StrMatcher matcher, String replaceStr) { + return replace(matcher, replaceStr, 0, size, -1); + } + + /** + * Replaces the first match within the builder with the replace string. + *

    + * 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 StrBuilder replaceFirst(StrMatcher matcher, String replaceStr) { + return replace(matcher, replaceStr, 0, size, 1); + } + + // ----------------------------------------------------------------------- + /** + * Advanced search and replaces within the builder using a matcher. + *

    + * Matchers can be used to perform advanced behaviour. + * For example you could write a matcher to delete all occurances + * 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 StrBuilder replace( + StrMatcher matcher, String replaceStr, + int startIndex, int endIndex, int replaceCount) { + endIndex = validateRange(startIndex, endIndex); + return replaceImpl(matcher, replaceStr, startIndex, endIndex, replaceCount); + } + + /** + * Replaces within the builder using a matcher. + *

    + * Matchers can be used to perform advanced behaviour. + * For example you could write a matcher to delete all occurances + * 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 StrBuilder replaceImpl( + StrMatcher matcher, String replaceStr, + int from, int to, int replaceCount) { + if (matcher == null || size == 0) { + return this; + } + int replaceLen = (replaceStr == null ? 0 : replaceStr.length()); + char[] buf = buffer; + for (int i = from; i < to && replaceCount != 0; i++) { + 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 StrBuilder reverse() { + if (size == 0) { + return this; + } + + int half = size / 2; + char[] buf = buffer; + for (int leftIdx = 0, rightIdx = size - 1; leftIdx < half; leftIdx++,rightIdx--) { + 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 StrBuilder trim() { + if (size == 0) { + return this; + } + int len = size; + 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. + *

    + * 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(String str) { + if (str == null) { + return false; + } + 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. + *

    + * 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(String str) { + if (str == null) { + return false; + } + 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; + } + + //----------------------------------------------------------------------- + /** + * 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(int start) { + return substring(start, size); + } + + /** + * Extracts a portion of this string builder as a string. + *

    + * 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(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. + *

    + * This method extracts the left length 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(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. + *

    + * This method extracts the right length 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(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. + *

    + * This method extracts length 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, int length) { + if (index < 0) { + index = 0; + } + if (length <= 0 || index >= size) { + return ""; + } + if (size <= index + length) { + return new String(buffer, index, size - index); + } else { + 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(char ch) { + 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(String str) { + return indexOf(str, 0) >= 0; + } + + /** + * Checks if the string builder contains a string matched using the + * specified matcher. + *

    + * Matchers can be used to perform advanced searching behaviour. + * For example you could write a matcher to search for the character + * 'a' followed by a number. + * + * @param matcher the matcher to use, null returns -1 + * @return true if the matcher finds a match in the builder + */ + public boolean contains(StrMatcher matcher) { + return indexOf(matcher, 0) >= 0; + } + + //----------------------------------------------------------------------- + /** + * Searches the string builder to find the first reference to the specified char. + * + * @param ch the character to find + * @return the first index of the character, or -1 if not found + */ + public int indexOf(char ch) { + return indexOf(ch, 0); + } + + /** + * Searches the string builder to find the first reference to the specified char. + * + * @param ch the character to find + * @param startIndex the index to start at, invalid index rounded to edge + * @return the first index of the character, or -1 if not found + */ + public int indexOf(char ch, int startIndex) { + startIndex = (startIndex < 0 ? 0 : startIndex); + if (startIndex >= size) { + return -1; + } + char[] thisBuf = buffer; + for (int i = startIndex; i < size; i++) { + if (thisBuf[i] == ch) { + return i; + } + } + return -1; + } + + /** + * Searches the string builder to find the first reference to the specified string. + *

    + * Note that a null input string will return -1, whereas the JDK throws an exception. + * + * @param str the string to find, null returns -1 + * @return the first index of the string, or -1 if not found + */ + public int indexOf(String str) { + return indexOf(str, 0); + } + + /** + * Searches the string builder to find the first reference to the specified + * string starting searching from the given index. + *

    + * Note that a null input string will return -1, whereas the JDK throws an exception. + * + * @param str the string to find, null returns -1 + * @param startIndex the index to start at, invalid index rounded to edge + * @return the first index of the string, or -1 if not found + */ + public int indexOf(String str, int startIndex) { + startIndex = (startIndex < 0 ? 0 : startIndex); + if (str == null || startIndex >= size) { + return -1; + } + int strLen = str.length(); + if (strLen == 1) { + return indexOf(str.charAt(0), startIndex); + } + if (strLen == 0) { + return startIndex; + } + if (strLen > size) { + return -1; + } + char[] thisBuf = buffer; + int len = size - strLen + 1; + outer: + for (int i = startIndex; i < len; i++) { + for (int j = 0; j < strLen; j++) { + if (str.charAt(j) != thisBuf[i + j]) { + continue outer; + } + } + return i; + } + return -1; + } + + /** + * Searches the string builder using the matcher to find the first match. + *

    + * Matchers can be used to perform advanced searching behaviour. + * For example you could write a matcher to find the character 'a' + * followed by a number. + * + * @param matcher the matcher to use, null returns -1 + * @return the first index matched, or -1 if not found + */ + public int indexOf(StrMatcher matcher) { + return indexOf(matcher, 0); + } + + /** + * Searches the string builder using the matcher to find the first + * match searching from the given index. + *

    + * Matchers can be used to perform advanced searching behaviour. + * For example you could write a matcher to find the character 'a' + * followed by a number. + * + * @param matcher the matcher to use, null returns -1 + * @param startIndex the index to start at, invalid index rounded to edge + * @return the first index matched, or -1 if not found + */ + public int indexOf(StrMatcher matcher, int startIndex) { + startIndex = (startIndex < 0 ? 0 : startIndex); + if (matcher == null || startIndex >= size) { + return -1; + } + int len = size; + char[] buf = buffer; + for (int i = startIndex; i < len; i++) { + if (matcher.isMatch(buf, i, startIndex, len) > 0) { + return i; + } + } + return -1; + } + + //----------------------------------------------------------------------- + /** + * Searches the string builder to find the last reference to the specified char. + * + * @param ch the character to find + * @return the last index of the character, or -1 if not found + */ + public int lastIndexOf(char ch) { + return lastIndexOf(ch, size - 1); + } + + /** + * Searches the string builder to find the last reference to the specified char. + * + * @param ch the character to find + * @param startIndex the index to start at, invalid index rounded to edge + * @return the last index of the character, or -1 if not found + */ + public int lastIndexOf(char ch, int startIndex) { + startIndex = (startIndex >= size ? size - 1 : startIndex); + if (startIndex < 0) { + return -1; + } + for (int i = startIndex; i >= 0; i--) { + if (buffer[i] == ch) { + return i; + } + } + return -1; + } + + /** + * Searches the string builder to find the last reference to the specified string. + *

    + * Note that a null input string will return -1, whereas the JDK throws an exception. + * + * @param str the string to find, null returns -1 + * @return the last index of the string, or -1 if not found + */ + public int lastIndexOf(String str) { + return lastIndexOf(str, size - 1); + } + + /** + * Searches the string builder to find the last reference to the specified + * string starting searching from the given index. + *

    + * Note that a null input string will return -1, whereas the JDK throws an exception. + * + * @param str the string to find, null returns -1 + * @param startIndex the index to start at, invalid index rounded to edge + * @return the last index of the string, or -1 if not found + */ + public int lastIndexOf(String str, int startIndex) { + startIndex = (startIndex >= size ? size - 1 : startIndex); + if (str == null || startIndex < 0) { + return -1; + } + int strLen = str.length(); + if (strLen > 0 && strLen <= size) { + if (strLen == 1) { + return lastIndexOf(str.charAt(0), startIndex); + } + + outer: + for (int i = startIndex - strLen + 1; i >= 0; i--) { + for (int j = 0; j < strLen; j++) { + if (str.charAt(j) != buffer[i + j]) { + continue outer; + } + } + return i; + } + + } else if (strLen == 0) { + return startIndex; + } + return -1; + } + + /** + * Searches the string builder using the matcher to find the last match. + *

    + * Matchers can be used to perform advanced searching behaviour. + * For example you could write a matcher to find the character 'a' + * followed by a number. + * + * @param matcher the matcher to use, null returns -1 + * @return the last index matched, or -1 if not found + */ + public int lastIndexOf(StrMatcher matcher) { + return lastIndexOf(matcher, size); + } + + /** + * Searches the string builder using the matcher to find the last + * match searching from the given index. + *

    + * Matchers can be used to perform advanced searching behaviour. + * For example you could write a matcher to find the character 'a' + * followed by a number. + * + * @param matcher the matcher to use, null returns -1 + * @param startIndex the index to start at, invalid index rounded to edge + * @return the last index matched, or -1 if not found + */ + public int lastIndexOf(StrMatcher matcher, int startIndex) { + startIndex = (startIndex >= size ? size - 1 : startIndex); + if (matcher == null || startIndex < 0) { + return -1; + } + char[] buf = buffer; + int endIndex = startIndex + 1; + for (int i = startIndex; i >= 0; i--) { + if (matcher.isMatch(buf, i, 0, endIndex) > 0) { + return i; + } + } + return -1; + } + + //----------------------------------------------------------------------- + /** + * Creates a tokenizer that can tokenize the contents of this builder. + *

    + * This method allows the contents of this builder to be tokenized. + * The tokenizer will be setup by default to tokenize on space, tab, + * newline and formfeed (as per StringTokenizer). These values can be + * changed on the tokenizer class, before retrieving the tokens. + *

    + * The returned tokenizer is linked to this builder. You may intermix + * calls to the buider and tokenizer within certain limits, however + * there is no synchronization. Once the tokenizer has been used once, + * it must be {@link StrTokenizer#reset() reset} to pickup the latest + * changes in the builder. For example: + *

    +     * StrBuilder b = new StrBuilder();
    +     * b.append("a b ");
    +     * StrTokenizer t = b.asTokenizer();
    +     * String[] tokens1 = t.getTokenArray();  // returns a,b
    +     * b.append("c d ");
    +     * String[] tokens2 = t.getTokenArray();  // returns a,b (c and d ignored)
    +     * t.reset();              // reset causes builder changes to be picked up
    +     * String[] tokens3 = t.getTokenArray();  // returns a,b,c,d
    +     * 
    + * In addition to simply intermixing appends and tokenization, you can also + * call the set methods on the tokenizer to alter how it tokenizes. Just + * remember to call reset when you want to pickup builder changes. + *

    + * Calling {@link StrTokenizer#reset(String)} or {@link StrTokenizer#reset(char[])} + * with a non-null value will break the link with the builder. + * + * @return a tokenizer that is linked to this builder + */ + public StrTokenizer asTokenizer() { + return new StrBuilderTokenizer(); + } + + //----------------------------------------------------------------------- + /** + * Gets the contents of this builder as a Reader. + *

    + * This method allows the contents of the builder to be read + * using any standard method that expects a Reader. + *

    + * To use, simply create a StrBuilder, populate it with + * data, call asReader, and then read away. + *

    + * The internal character array is shared between the builder and the reader. + * This allows you to append to the builder after creating the reader, + * and the changes will be picked up. + * Note however, that no synchronization occurs, so you must perform + * all operations with the builder and the reader in one thread. + *

    + * The returned reader supports marking, and ignores the flush method. + * + * @return a reader that reads from this builder + */ + public Reader asReader() { + return new StrBuilderReader(); + } + + //----------------------------------------------------------------------- + /** + * Gets this builder as a Writer that can be written to. + *

    + * This method allows you to populate the contents of the builder + * using any standard method that takes a Writer. + *

    + * To use, simply create a StrBuilder, + * call asWriter, and populate away. The data is available + * at any time using the methods of the StrBuilder. + *

    + * The internal character array is shared between the builder and the writer. + * This allows you to intermix calls that append to the builder and + * write using the writer and the changes will be occur correctly. + * Note however, that no synchronization occurs, so you must perform + * all operations with the builder and the writer in one thread. + *

    + * The returned writer ignores the close and flush methods. + * + * @return a writer that populates this builder + */ + public Writer asWriter() { + return new StrBuilderWriter(); + } + + //----------------------------------------------------------------------- +// /** +// * Gets a String version of the string builder by calling the internal +// * constructor of String by reflection. +// *

    +// * WARNING: You must not use the StrBuilder after calling this method +// * as the buffer is now shared with the String object. To ensure this, +// * the internal character array is set to null, so you will get +// * NullPointerExceptions on all method calls. +// * +// * @return the builder as a String +// */ +// public String toSharedString() { +// try { +// Constructor con = String.class.getDeclaredConstructor( +// new Class[] {int.class, int.class, char[].class}); +// con.setAccessible(true); +// char[] buffer = buf; +// buf = null; +// size = -1; +// nullText = null; +// return (String) con.newInstance( +// new Object[] {new Integer(0), new Integer(size), buffer}); +// +// } catch (Exception ex) { +// ex.printStackTrace(); +// throw new UnsupportedOperationException("StrBuilder.toSharedString is unsupported: " + ex.getMessage()); +// } +// } + + //----------------------------------------------------------------------- + /** + * Checks the contents of this builder against another to see if they + * contain the same character content ignoring case. + * + * @param other the object to check, null returns false + * @return true if the builders contain the same characters in the same order + */ + public boolean equalsIgnoreCase(StrBuilder other) { + if (this == other) { + return true; + } + if (this.size != other.size) { + return false; + } + char thisBuf[] = this.buffer; + char otherBuf[] = other.buffer; + for (int i = size - 1; i >= 0; i--) { + char c1 = thisBuf[i]; + char c2 = otherBuf[i]; + if (c1 != c2 && Character.toUpperCase(c1) != Character.toUpperCase(c2)) { + return false; + } + } + return true; + } + + /** + * Checks the contents of this builder against another to see if they + * contain the same character content. + * + * @param other the object to check, null returns false + * @return true if the builders contain the same characters in the same order + */ + public boolean equals(StrBuilder other) { + if (this == other) { + return true; + } + if (this.size != other.size) { + return false; + } + char thisBuf[] = this.buffer; + char otherBuf[] = other.buffer; + for (int i = size - 1; i >= 0; i--) { + if (thisBuf[i] != otherBuf[i]) { + return false; + } + } + return true; + } + + /** + * Checks the contents of this builder against another to see if they + * contain the same character content. + * + * @param obj the object to check, null returns false + * @return true if the builders contain the same characters in the same order + */ + public boolean equals(Object obj) { + if (obj instanceof StrBuilder) { + return equals((StrBuilder) obj); + } + return false; + } + + /** + * Gets a suitable hash code for this builder. + * + * @return a hash code + */ + public int hashCode() { + char buf[] = buffer; + int hash = 0; + for (int i = size - 1; i >= 0; i--) { + hash = 31 * hash + buf[i]; + } + return hash; + } + + //----------------------------------------------------------------------- + /** + * Gets a String version of the string builder, creating a new instance + * each time the method is called. + *

    + * Note that unlike StringBuffer, the string version returned is + * independent of the string builder. + * + * @return the builder as a String + */ + public String toString() { + return new String(buffer, 0, size); + } + + /** + * Gets a StringBuffer version of the string builder, creating a + * new instance each time the method is called. + * + * @return the builder as a StringBuffer + */ + public StringBuffer toStringBuffer() { + return new StringBuffer(size).append(buffer, 0, size); + } + + //----------------------------------------------------------------------- + /** + * Validates parameters defining a range of the builder. + * + * @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 + */ + protected int validateRange(int startIndex, int endIndex) { + if (startIndex < 0) { + throw new StringIndexOutOfBoundsException(startIndex); + } + if (endIndex > size) { + endIndex = size; + } + if (startIndex > endIndex) { + throw new StringIndexOutOfBoundsException("end < start"); + } + return endIndex; + } + + /** + * Validates parameters defining a single index in the builder. + * + * @param index the index, must be valid + * @throws IndexOutOfBoundsException if the index is invalid + */ + protected void validateIndex(int index) { + if (index < 0 || index > size) { + throw new StringIndexOutOfBoundsException(index); + } + } + + //----------------------------------------------------------------------- + /** + * Inner class to allow StrBuilder to operate as a tokenizer. + */ + class StrBuilderTokenizer extends StrTokenizer { + + /** {@inheritDoc} */ + StrBuilderTokenizer() { + super(); + } + + /** {@inheritDoc} */ + protected List tokenize(char[] chars, int offset, int count) { + if (chars == null) { + return super.tokenize(StrBuilder.this.buffer, 0, StrBuilder.this.size()); + } else { + return super.tokenize(chars, offset, count); + } + } + + /** {@inheritDoc} */ + public String getContent() { + String str = super.getContent(); + if (str == null) { + return StrBuilder.this.toString(); + } else { + return str; + } + } + } + + //----------------------------------------------------------------------- + /** + * Inner class to allow StrBuilder to operate as a writer. + */ + class StrBuilderReader extends Reader { + /** The current stream position. */ + private int pos; + /** The last mark position. */ + private int mark; + + /** {@inheritDoc} */ + StrBuilderReader() { + super(); + } + + /** {@inheritDoc} */ + public void close() { + // do nothing + } + + /** {@inheritDoc} */ + public int read() { + if (ready() == false) { + return -1; + } + return StrBuilder.this.charAt(pos++); + } + + /** {@inheritDoc} */ + public int read(char b[], int off, int len) { + if (off < 0 || len < 0 || off > b.length || + (off + len) > b.length || (off + len) < 0) { + throw new IndexOutOfBoundsException(); + } + if (len == 0) { + return 0; + } + if (pos >= StrBuilder.this.size()) { + return -1; + } + if (pos + len > size()) { + len = StrBuilder.this.size() - pos; + } + StrBuilder.this.getChars(pos, pos + len, b, off); + pos += len; + return len; + } + + /** {@inheritDoc} */ + public long skip(long n) { + if (pos + n > StrBuilder.this.size()) { + n = StrBuilder.this.size() - pos; + } + if (n < 0) { + return 0; + } + pos += n; + return n; + } + + /** {@inheritDoc} */ + public boolean ready() { + return pos < StrBuilder.this.size(); + } + + /** {@inheritDoc} */ + public boolean markSupported() { + return true; + } + + /** {@inheritDoc} */ + public void mark(int readAheadLimit) { + mark = pos; + } + + /** {@inheritDoc} */ + public void reset() { + pos = mark; + } + } + + //----------------------------------------------------------------------- + /** + * Inner class to allow StrBuilder to operate as a writer. + */ + class StrBuilderWriter extends Writer { + + /** {@inheritDoc} */ + StrBuilderWriter() { + super(); + } + + /** {@inheritDoc} */ + public void close() { + // do nothing + } + + /** {@inheritDoc} */ + public void flush() { + // do nothing + } + + /** {@inheritDoc} */ + public void write(int c) { + StrBuilder.this.append((char) c); + } + + /** {@inheritDoc} */ + public void write(char[] cbuf) { + StrBuilder.this.append(cbuf); + } + + /** {@inheritDoc} */ + public void write(char[] cbuf, int off, int len) { + StrBuilder.this.append(cbuf, off, len); + } + + /** {@inheritDoc} */ + public void write(String str) { + StrBuilder.this.append(str); + } + + /** {@inheritDoc} */ + public void write(String str, int off, int len) { + StrBuilder.this.append(str, off, len); + } + } + +}