Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id EB9A5200C26 for ; Fri, 10 Feb 2017 14:18:00 +0100 (CET) Received: by cust-asf.ponee.io (Postfix) id EA4C3160B69; Fri, 10 Feb 2017 13:18:00 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id B37E1160B5C for ; Fri, 10 Feb 2017 14:17:58 +0100 (CET) Received: (qmail 44300 invoked by uid 500); 10 Feb 2017 13:17:57 -0000 Mailing-List: contact commits-help@commons.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@commons.apache.org Delivered-To: mailing list commits@commons.apache.org Received: (qmail 38190 invoked by uid 99); 10 Feb 2017 13:17:15 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 10 Feb 2017 13:17:15 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 0F5BFE005F; Fri, 10 Feb 2017 13:17:15 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: chtompki@apache.org To: commits@commons.apache.org Date: Fri, 10 Feb 2017 13:17:37 -0000 Message-Id: In-Reply-To: <27a66f156ead43a7a38690a2b19ebef7@git.apache.org> References: <27a66f156ead43a7a38690a2b19ebef7@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [24/25] [text] chore: update packages back to org.apache.commons.text.* archived-at: Fri, 10 Feb 2017 13:18:01 -0000 http://git-wip-us.apache.org/repos/asf/commons-text/blob/c7cf533d/src/main/java/org/apache/commons/text/StrBuilder.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/text/StrBuilder.java b/src/main/java/org/apache/commons/text/StrBuilder.java new file mode 100644 index 0000000..a3bc429 --- /dev/null +++ b/src/main/java/org/apache/commons/text/StrBuilder.java @@ -0,0 +1,3092 @@ +/* + * 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; + +/** + * Builds a string from constituent parts providing a more flexible and powerful API + * than StringBuffer. + *

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

+ *
    + *
  • Not synchronized
  • + *
  • Not final
  • + *
  • Subclasses have direct access to character array
  • + *
  • Additional methods + *
      + *
    • appendWithSeparators - adds an array of values, with a separator
    • + *
    • appendPadding - adds a length padding characters
    • + *
    • appendFixedLength - adds a fixed width field to the builder
    • + *
    • toCharArray/getChars - simpler ways to get a range of the character array
    • + *
    • delete - delete char or string
    • + *
    • replace - search and replace for a char or string
    • + *
    • leftString/rightString/midString - substring without exceptions
    • + *
    • contains - whether the builder contains a char or string
    • + *
    • size/clear/isEmpty - collections style API methods
    • + *
    + *
  • + *
  • Views + *
      + *
    • asTokenizer - uses the internal buffer as the source of a StrTokenizer
    • + *
    • asReader - uses the internal buffer as the source of a Reader
    • + *
    • asWriter - allows a Writer to write directly to the internal buffer
    • + *
    + *
  • + *
+ *

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

+ * + * @since 1.0 + * + */ +public class StrBuilder implements CharSequence, Appendable, Serializable, Builder { + + /** + * 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. */ + 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 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(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 StrBuilder 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 StrBuilder 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 StrBuilder 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 StrBuilder 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 StrBuilder 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. + *

+ * 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 + */ + @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 StrBuilder 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 StrBuilder 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. + *

+ * 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(System.getProperty("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(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 StrBuilder append(final CharSequence seq) { + if (seq == null) { + return appendNull(); + } + if (seq instanceof StrBuilder) { + return append((StrBuilder) 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 StrBuilder 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 StrBuilder 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 StrBuilder 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 StrBuilder 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 StrBuilder 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 StrBuilder 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 StrBuilder 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 StrBuilder 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 StrBuilder 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 StrBuilder 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 StrBuilder append(final StrBuilder 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 StrBuilder append(final StrBuilder 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 StrBuilder 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 StrBuilder 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 StrBuilder append(final 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 + */ + @Override + public StrBuilder 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 String.valueOf. + * + * @param value the value to append + * @return this, to enable chaining + */ + public StrBuilder append(final 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(final 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(final 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(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 StrBuilder 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 StrBuilder 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 StrBuilder 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 StrBuilder 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 StrBuilder 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 StrBuilder 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 StrBuilder 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 StrBuilder 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 StrBuilder appendln(final 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 + */ + public StrBuilder appendln(final StrBuilder 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 StrBuilder 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 StrBuilder 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 StrBuilder 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 StrBuilder appendln(final 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 + */ + public StrBuilder appendln(final 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 + */ + public StrBuilder appendln(final 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 + */ + public StrBuilder appendln(final 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 + */ + public StrBuilder 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 the element type + * @param array the array to append + * @return this, to enable chaining + */ + public StrBuilder 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 @SupressWarnings + * 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 StrBuilder 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 StrBuilder 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 StrBuilder 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 StrBuilder 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 StrBuilder 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)}. + *

+ * 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(Iterable, String)}. + * + * @param separator the separator to use, null means no separator + * @return this, to enable chaining + */ + public StrBuilder 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)}. + *

+ * This method is for example useful for constructing queries + *

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

+ * 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(Iterable, String)}. + * + * @param separator the separator to use + * @return this, to enable chaining + */ + public StrBuilder 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 StrBuilder 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)}. + *

+ * 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(Iterable, String)}. + * + * @param separator the separator to use, null means no separator + * @param loopIndex the loop index + * @return this, to enable chaining + */ + public StrBuilder 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)}. + *

+ * 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(Iterable, String)}. + * + * @param separator the separator to use + * @param loopIndex the loop index + * @return this, to enable chaining + */ + public StrBuilder 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 StrBuilder 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 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(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 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(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 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(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 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(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 StrBuilder 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 StrBuilder 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 StrBuilder 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 StrBuilder 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 StrBuilder insert(int index, final 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(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 StrBuilder 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 StrBuilder 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 StrBuilder 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 StrBuilder 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 StrBuilder 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 StrBuilder 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 StrBuilder 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 StrBuilder 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 StrBuilder 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. + *

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

+ * 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 StrBuilder replaceAll(final StrMatcher matcher, final 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(final StrMatcher matcher, final 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 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 StrBuilder replace( + final StrMatcher 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. + *

+ * 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 StrBuilder replaceImpl( + final StrMatcher 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 StrBuilder 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 StrBuilder 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. + *

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

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

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

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

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

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

+ * 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(final 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(final 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(final char ch, int startIndex) { + startIndex = (startIndex < 0 ? 0 : startIndex); + if (startIndex >= size) { + return -1; + } + final 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(final 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(final String str, int startIndex) { + startIndex = (startIndex < 0 ? 0 : startIndex); + if (str == null || startIndex >= size) { + return -1; + } + final int strLen = str.length(); + if (strLen == 1) { + return indexOf(str.charAt(0), startIndex); + } + if (strLen == 0) { + return startIndex; + } + if (strLen > size) { + return -1; + } + final char[] thisBuf = buffer; + final 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(final 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(final StrMatcher matcher, int startIndex) { + startIndex = (startIndex < 0 ? 0 : startIndex); + if (matcher == null || startIndex >= size) { + return -1; + } + final int len = size; + final char[] buf = buffer; + for (int i = startIndex; i < len; i++) { + if (matcher.isMatch(buf, i, startIndex, len