You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by ch...@apache.org on 2016/11/13 20:34:04 UTC
[01/14] [text] TEXT-23: adding unit test for AggregateTranslator
Repository: commons-text
Updated Branches:
refs/heads/master ad7ee397a -> a408a7cfa
TEXT-23: adding unit test for AggregateTranslator
Project: http://git-wip-us.apache.org/repos/asf/commons-text/repo
Commit: http://git-wip-us.apache.org/repos/asf/commons-text/commit/2f55bd15
Tree: http://git-wip-us.apache.org/repos/asf/commons-text/tree/2f55bd15
Diff: http://git-wip-us.apache.org/repos/asf/commons-text/diff/2f55bd15
Branch: refs/heads/master
Commit: 2f55bd1520d73c3c46d48b9066107d37f38926c4
Parents: e330798
Author: Rob Tompkins <ch...@capitalone.com>
Authored: Wed Nov 9 08:31:45 2016 -0500
Committer: Rob Tompkins <ch...@capitalone.com>
Committed: Wed Nov 9 08:31:45 2016 -0500
----------------------------------------------------------------------
.../text/translate/AggregateTranslatorTest.java | 56 ++++++++++++++++++++
1 file changed, 56 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/commons-text/blob/2f55bd15/src/test/java/org/apache/commons/text/translate/AggregateTranslatorTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/text/translate/AggregateTranslatorTest.java b/src/test/java/org/apache/commons/text/translate/AggregateTranslatorTest.java
new file mode 100644
index 0000000..799c3e9
--- /dev/null
+++ b/src/test/java/org/apache/commons/text/translate/AggregateTranslatorTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.translate;
+
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.lang.reflect.Field;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+/**
+ * Unit tests for {@link org.apache.commons.text.translate.AggregateTranslator}.
+ */
+public class AggregateTranslatorTest {
+
+ @Test
+ public void testNullConstructor() throws NoSuchFieldException, IllegalAccessException {
+ org.apache.commons.text.translate.AggregateTranslator subject = new org.apache.commons.text.translate.AggregateTranslator(null);
+ Field field = AggregateTranslator.class.getDeclaredField("translators");
+ field.setAccessible(Boolean.TRUE);
+ assertNull(field.get(subject));
+ }
+
+ @Test
+ public void testNonNull() throws IOException{
+ CharSequenceTranslator translator1 = new org.apache.commons.text.translate.LookupTranslator(new CharSequence[][] { { "one", "two" } });
+ CharSequenceTranslator translator2 = new org.apache.commons.text.translate.LookupTranslator(new CharSequence[][] { { "three", "four" } });
+ org.apache.commons.text.translate.AggregateTranslator subject = new org.apache.commons.text.translate.AggregateTranslator(translator1, translator2);
+ final StringWriter out1 = new StringWriter();
+ final int result1 = subject.translate(new StringBuffer("one"), 0, out1);
+ assertEquals("Incorrect codepoint consumption", 3, result1);
+ assertEquals("Incorrect value", "two", out1.toString());
+ final StringWriter out2 = new StringWriter();
+ final int result2 = subject.translate(new StringBuffer("three"), 0, out2);
+ assertEquals("Incorrect codepoint consumption", 5, result2);
+ assertEquals("Incorrect value", "four", out2.toString());
+ }
+
+}
[09/14] [text] TEXT-23: Adding remainder of the o.a.c.l.text package
Posted by ch...@apache.org.
http://git-wip-us.apache.org/repos/asf/commons-text/blob/6f24aa45/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..5aa4a31
--- /dev/null
+++ b/src/main/java/org/apache/commons/text/StrBuilder.java
@@ -0,0 +1,3117 @@
+/*
+ * 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.
+ * <p>
+ * The main differences from StringBuffer/StringBuilder are:
+ * </p>
+ * <ul>
+ * <li>Not synchronized</li>
+ * <li>Not final</li>
+ * <li>Subclasses have direct access to character array</li>
+ * <li>Additional methods
+ * <ul>
+ * <li>appendWithSeparators - adds an array of values, with a separator</li>
+ * <li>appendPadding - adds a length padding characters</li>
+ * <li>appendFixedLength - adds a fixed width field to the builder</li>
+ * <li>toCharArray/getChars - simpler ways to get a range of the character array</li>
+ * <li>delete - delete char or string</li>
+ * <li>replace - search and replace for a char or string</li>
+ * <li>leftString/rightString/midString - substring without exceptions</li>
+ * <li>contains - whether the builder contains a char or string</li>
+ * <li>size/clear/isEmpty - collections style API methods</li>
+ * </ul>
+ * </li>
+ * <li>Views
+ * <ul>
+ * <li>asTokenizer - uses the internal buffer as the source of a StrTokenizer</li>
+ * <li>asReader - uses the internal buffer as the source of a Reader</li>
+ * <li>asWriter - allows a Writer to write directly to the internal buffer</li>
+ * </ul>
+ * </li>
+ * </ul>
+ * <p>
+ * The aim has been to provide an API that mimics very closely what StringBuffer
+ * provides, but with additional methods. It should be noted that some edge cases,
+ * with invalid indices or null input, have been altered - see individual methods.
+ * The biggest of these changes is that by default, null will not output the text
+ * 'null'. This can be controlled by a property, {@link #setNullText(String)}.
+ * <p>
+ *
+ */
+public class StrBuilder implements CharSequence, Appendable, Serializable, Builder<String> {
+
+ /**
+ * 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; // TODO make private?
+ /** Current size of the buffer. */
+ protected int size; // TODO make private?
+ /** 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.
+ * <p>
+ * This method is the same as {@link #length()} and is provided to match the
+ * API of Collections.
+ *
+ * @return the length
+ */
+ public int size() {
+ return size;
+ }
+
+ /**
+ * Checks is the string builder is empty (convenience Collections API style method).
+ * <p>
+ * This method is the same as checking {@link #length()} and is provided to match the
+ * API of Collections.
+ *
+ * @return <code>true</code> if the size is <code>0</code>.
+ */
+ public boolean isEmpty() {
+ return size == 0;
+ }
+
+ /**
+ * Clears the string builder (convenience Collections API style method).
+ * <p>
+ * This method does not reduce the size of the internal character buffer.
+ * To do that, call <code>clear()</code> followed by {@link #minimizeCapacity()}.
+ * <p>
+ * This method is the same as {@link #setLength(int)} called with zero
+ * and is provided to match the API of Collections.
+ *
+ * @return this, to enable chaining
+ */
+ public 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
+ *
+ * @since 3.4
+ * @see #appendTo(Appendable)
+ */
+ public int readFrom(final Readable readable) throws IOException {
+ final int oldSize = size;
+ if (readable instanceof Reader) {
+ final Reader r = (Reader) readable;
+ ensureCapacity(size + 1);
+ int read;
+ while ((read = r.read(buffer, size, buffer.length - size)) != -1) {
+ size += read;
+ ensureCapacity(size + 1);
+ }
+ } else if (readable instanceof CharBuffer) {
+ final CharBuffer cb = (CharBuffer) readable;
+ final int remaining = cb.remaining();
+ ensureCapacity(size + remaining);
+ cb.get(buffer, size, remaining);
+ size += remaining;
+ } else {
+ while (true) {
+ ensureCapacity(size + 1);
+ final CharBuffer buf = CharBuffer.wrap(buffer, size, buffer.length - size);
+ final int read = readable.read(buf);
+ if (read == -1) {
+ break;
+ }
+ size += read;
+ }
+ }
+ return size - oldSize;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Appends the new line string to this string builder.
+ * <p>
+ * The new line string can be altered using {@link #setNewLineText(String)}.
+ * This might be used to force the output to always use Unix line endings
+ * even when on Windows.
+ *
+ * @return this, to enable chaining
+ */
+ public StrBuilder appendNewLine() {
+ if (newLine == null) {
+ append(System.getProperty("line.separator"));
+ return this;
+ }
+ return append(newLine);
+ }
+
+ /**
+ * Appends the text representing <code>null</code> 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
+ * @since 3.0
+ */
+ @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
+ * @since 3.0
+ */
+ @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...)
+ * @since 3.2
+ */
+ 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
+ * @since 3.4
+ */
+ 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
+ * @since 3.4
+ */
+ 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
+ * @since 3.2
+ */
+ 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
+ * @since 3.2
+ */
+ 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
+ * @since 3.0
+ */
+ @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 <code>String.valueOf</code>.
+ *
+ * @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 <code>String.valueOf</code>.
+ *
+ * @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 <code>String.valueOf</code>.
+ *
+ * @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 <code>String.valueOf</code>.
+ *
+ * @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
+ * @since 2.3
+ */
+ 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
+ * @since 2.3
+ */
+ 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
+ * @since 2.3
+ */
+ 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...)
+ * @since 3.2
+ */
+ 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
+ * @since 2.3
+ */
+ 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
+ * @since 3.2
+ */
+ 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
+ * @since 3.2
+ */
+ 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
+ * @since 2.3
+ */
+ 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
+ * @since 2.3
+ */
+ 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
+ * @since 2.3
+ */
+ 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
+ * @since 2.3
+ */
+ 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
+ * @since 2.3
+ */
+ 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
+ * @since 2.3
+ */
+ 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
+ * @since 2.3
+ */
+ public StrBuilder appendln(final char ch) {
+ return append(ch).appendNewLine();
+ }
+
+ /**
+ * Appends an int value followed by a new line to the string builder using <code>String.valueOf</code>.
+ *
+ * @param value the value to append
+ * @return this, to enable chaining
+ * @since 2.3
+ */
+ public StrBuilder appendln(final int value) {
+ return append(value).appendNewLine();
+ }
+
+ /**
+ * Appends a long value followed by a new line to the string builder using <code>String.valueOf</code>.
+ *
+ * @param value the value to append
+ * @return this, to enable chaining
+ * @since 2.3
+ */
+ public StrBuilder appendln(final long value) {
+ return append(value).appendNewLine();
+ }
+
+ /**
+ * Appends a float value followed by a new line to the string builder using <code>String.valueOf</code>.
+ *
+ * @param value the value to append
+ * @return this, to enable chaining
+ * @since 2.3
+ */
+ public StrBuilder appendln(final float value) {
+ return append(value).appendNewLine();
+ }
+
+ /**
+ * Appends a double value followed by a new line to the string builder using <code>String.valueOf</code>.
+ *
+ * @param value the value to append
+ * @return this, to enable chaining
+ * @since 2.3
+ */
+ 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 <T> the element type
+ * @param array the array to append
+ * @return this, to enable chaining
+ * @since 2.3
+ */
+ public <T> StrBuilder appendAll(final T... array) {
+ 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
+ * @since 2.3
+ */
+ 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
+ * @since 2.3
+ */
+ 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)}.
+ * <p>
+ * This method is useful for adding a separator each time around the
+ * loop except the first.
+ * <pre>
+ * for (Iterator it = list.iterator(); it.hasNext(); ) {
+ * appendSeparator(",");
+ * append(it.next());
+ * }
+ * </pre>
+ * Note that for this simple example, you should use
+ * {@link #appendWithSeparators(Iterable, String)}.
+ *
+ * @param separator the separator to use, null means no separator
+ * @return this, to enable chaining
+ * @since 2.3
+ */
+ 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)}.
+ * <p>
+ * This method is for example useful for constructing queries
+ * <pre>
+ * StrBuilder whereClause = new StrBuilder();
+ * if(searchCommand.getPriority() != null) {
+ * whereClause.appendSeparator(" and", " where");
+ * whereClause.append(" priority = ?")
+ * }
+ * if(searchCommand.getComponent() != null) {
+ * whereClause.appendSeparator(" and", " where");
+ * whereClause.append(" component = ?")
+ * }
+ * selectClause.append(whereClause)
+ * </pre>
+ *
+ * @param standard the separator if builder is not empty, null means no separator
+ * @param defaultIfEmpty the separator if builder is empty, null means no separator
+ * @return this, to enable chaining
+ * @since 2.5
+ */
+ 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)}.
+ * <p>
+ * This method is useful for adding a separator each time around the
+ * loop except the first.
+ * <pre>
+ * for (Iterator it = list.iterator(); it.hasNext(); ) {
+ * appendSeparator(',');
+ * append(it.next());
+ * }
+ * </pre>
+ * Note that for this simple example, you should use
+ * {@link #appendWithSeparators(Iterable, String)}.
+ *
+ * @param separator the separator to use
+ * @return this, to enable chaining
+ * @since 2.3
+ */
+ 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
+ * @since 2.5
+ */
+ 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)}.
+ * <p>
+ * This method is useful for adding a separator each time around the
+ * loop except the first.
+ * </p>
+ * <pre>
+ * for (int i = 0; i < list.size(); i++) {
+ * appendSeparator(",", i);
+ * append(list.get(i));
+ * }
+ * </pre>
+ * Note that for this simple example, you should use
+ * {@link #appendWithSeparators(Iterable, String)}.
+ *
+ * @param separator the separator to use, null means no separator
+ * @param loopIndex the loop index
+ * @return this, to enable chaining
+ * @since 2.3
+ */
+ 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)}.
+ * <p>
+ * This method is useful for adding a separator each time around the
+ * loop except the first.
+ * </p>
+ * <pre>
+ * for (int i = 0; i < list.size(); i++) {
+ * appendSeparator(",", i);
+ * append(list.get(i));
+ * }
+ * </pre>
+ * Note that for this simple example, you should use
+ * {@link #appendWithSeparators(Iterable, String)}.
+ *
+ * @param separator the separator to use
+ * @param loopIndex the loop index
+ * @return this, to enable chaining
+ * @since 2.3
+ */
+ 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 <code>toString</code> of the object is used.
+ * If the object is larger than the length, the left hand side is lost.
+ * If the object is null, the null text value is used.
+ *
+ * @param obj the object to append, null uses null text
+ * @param width the fixed field width, zero or negative has no effect
+ * @param padChar the pad character to use
+ * @return this, to enable chaining
+ */
+ public 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 <code>String.valueOf</code> of the <code>int</code> value is used.
+ * If the formatted value is larger than the length, the left hand side is lost.
+ *
+ * @param value the value to append
+ * @param width the fixed field width, zero or negative has no effect
+ * @param padChar the pad character to use
+ * @return this, to enable chaining
+ */
+ public 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 <code>toString</code> of the object is used.
+ * If the object is larger than the length, the right hand side is lost.
+ * If the object is null, null text value is used.
+ *
+ * @param obj the object to append, null uses null text
+ * @param width the fixed field width, zero or negative has no effect
+ * @param padChar the pad character to use
+ * @return this, to enable chaining
+ */
+ public 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 <code>String.valueOf</code> of the <code>int</code> value is used.
+ * If the object is larger than the length, the right hand side is lost.
+ *
+ * @param value the value to append
+ * @param width the fixed field width, zero or negative has no effect
+ * @param padChar the pad character to use
+ * @return this, to enable chaining
+ */
+ public 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.
+ * <p>
+ * Matchers can be used to perform advanced deletion behaviour.
+ * For example you could write a matcher to delete all occurrences
+ * where the character 'a' is followed by a number.
+ *
+ * @param matcher the matcher to use to find the deletion, null causes no action
+ * @return this, to enable chaining
+ */
+ public StrBuilder deleteAll(final StrMatcher matcher) {
+ return replace(matcher, null, 0, size, -1);
+ }
+
+ /**
+ * Deletes the first match within the builder using the specified matcher.
+ * <p>
+ * Matchers can be used to perform advanced deletion behaviour.
+ * For example you could write a matcher to delete
+ * where the character 'a' is followed by a number.
+ *
+ * @param matcher the matcher to use to find the deletion, null causes no action
+ * @return this, to enable chaining
+ */
+ public 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.
+ * <p>
+ * Matchers can be used to perform advanced replace behaviour.
+ * For example you could write a matcher to replace all occurrences
+ * where the character 'a' is followed by a number.
+ *
+ * @param matcher the matcher to use to find the deletion, null causes no action
+ * @param replaceStr the replace string, null is equivalent to an empty string
+ * @return this, to enable chaining
+ */
+ public 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.
+ * <p>
+ * Matchers can be used to perform advanced replace behaviour.
+ * For example you could write a matcher to replace
+ * where the character 'a' is followed by a number.
+ *
+ * @param matcher the matcher to use to find the deletion, null causes no action
+ * @param replaceStr the replace string, null is equivalent to an empty string
+ * @return this, to enable chaining
+ */
+ public 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.
+ * <p>
+ * Matchers can be used to perform advanced behaviour.
+ * For example you could write a matcher to delete all occurrences
+ * where the character 'a' is followed by a number.
+ *
+ * @param matcher the matcher to use to find the deletion, null causes no action
+ * @param replaceStr the string to replace the match with, null is a delete
+ * @param startIndex the start index, inclusive, must be valid
+ * @param endIndex the end index, exclusive, must be valid except
+ * that if too large it is treated as end of string
+ * @param replaceCount the number of times to replace, -1 for replace all
+ * @return this, to enable chaining
+ * @throws IndexOutOfBoundsException if start index is invalid
+ */
+ public 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.
+ * <p>
+ * Matchers can be used to perform advanced behaviour.
+ * For example you could write a matcher to delete all occurrences
+ * where the character 'a' is followed by a number.
+ *
+ * @param matcher the matcher to use to find the deletion, null causes no action
+ * @param replaceStr the string to replace the match with, null is a delete
+ * @param from the start index, must be valid
+ * @param to the end index (exclusive), must be valid
+ * @param replaceCount the number of times to replace, -1 for replace all
+ * @return this, to enable chaining
+ * @throws IndexOutOfBoundsException if any index is invalid
+ */
+ private 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.
+ * <p>
+ * Note that this method handles null input quietly, unlike String.
+ *
+ * @param str the string to search for, null returns false
+ * @return true if the builder starts with the string
+ */
+ public boolean startsWith(final String str) {
+ if (str == null) {
+ return false;
+ }
+ final int len = str.length();
+ if (len == 0) {
+ return true;
+ }
+ if (len > size) {
+ return false;
+ }
+ for (int i = 0; i < len; i++) {
+ if (buffer[i] != str.charAt(i)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks whether this builder ends with the specified string.
+ * <p>
+ * Note that this method handles null input quietly, unlike String.
+ *
+ * @param str the string to search for, null returns false
+ * @return true if the builder ends with the string
+ */
+ public boolean endsWith(final String str) {
+ if (str == null) {
+ return false;
+ }
+ final int len = str.length();
+ if (len == 0) {
+ return true;
+ }
+ if (len > size) {
+ return false;
+ }
+ int pos = size - len;
+ for (int i = 0; i < len; i++,pos++) {
+ if (buffer[pos] != str.charAt(i)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * {@inheritDoc}
+ * @since 3.0
+ */
+ @Override
+ public CharSequence subSequence(final int startIndex, final int endIndex) {
+ if (startIndex < 0) {
+ throw new StringIndexOutOfBoundsException(startIndex);
+ }
+ if (endIndex > size) {
+ throw new StringIndexOutOfBoundsException(endIndex);
+ }
+ if (startIndex > endIndex) {
+ throw new StringIndexOutOfBoundsException(endIndex - startIndex);
+ }
+ return substring(startIndex, endIndex);
+ }
+
+ /**
+ * Extracts a portion of this string builder as a string.
+ *
+ * @param start the start index, inclusive, must be valid
+ * @return the new string
+ * @throws IndexOutOfBoundsException if the index is invalid
+ */
+ public String substring(final int start) {
+ return substring(start, size);
+ }
+
+ /**
+ * Extracts a portion of this string builder as a string.
+ * <p>
+ * Note: This method treats an endIndex greater than the length of the
+ * builder as equal to the length of the builder, and continues
+ * without error, unlike StringBuffer or String.
+ *
+ * @param startIndex the start index, inclusive, must be valid
+ * @param endIndex the end index, exclusive, must be valid except
+ * that if too large it is treated as end of string
+ * @return the new string
+ * @throws IndexOutOfBoundsException if the index is invalid
+ */
+ public String substring(final int startIndex, int endIndex) {
+ endIndex = validateRange(startIndex, endIndex);
+ return new String(buffer, startIndex, endIndex - startIndex);
+ }
+
+ /**
+ * Extracts the leftmost characters from the string builder without
+ * throwing an exception.
+ * <p>
+ * This method extracts the left <code>length</code> characters from
+ * the builder. If this many characters are not available, the whole
+ * builder is returned. Thus the returned string may be shorter than the
+ * length requested.
+ *
+ * @param length the number of characters to extract, negative returns empty string
+ * @return the new string
+ */
+ public String leftString(final int length) {
+ if (length <= 0) {
+ return "";
+ } else if (length >= size) {
+ return new String(buffer, 0, size);
+ } else {
+ return new String(buffer, 0, length);
+ }
+ }
+
+ /**
+ * Extracts the rightmost characters from the string builder without
+ * throwing an exception.
+ * <p>
+ * This method extracts the right <code>length</code> characters from
+ * the builder. If this many characters are not available, the whole
+ * builder is returned. Thus the returned string may be shorter than the
+ * length requested.
+ *
+ * @param length the number of characters to extract, negative returns empty string
+ * @return the new string
+ */
+ public String rightString(final int length) {
+ if (length <= 0) {
+ return "";
+ } else if (length >= size) {
+ return new String(buffer, 0, size);
+ } else {
+ return new String(buffer, size - length, length);
+ }
+ }
+
+ /**
+ * Extracts some characters from the middle of the string builder without
+ * throwing an exception.
+ * <p>
+ * This method extracts <code>length</code> characters from the builder
+ * at the specified index.
+ * If the index is negative it is treated as zero.
+ * If the index is greater than the builder size, it is treated as the builder size.
+ * If the length is negative, the empty string is returned.
+ * If insufficient characters are available in the builder, as much as possible is returned.
+ * Thus the returned string may be shorter than the length requested.
+ *
+ * @param index the index to start at, negative means zero
+ * @param length the number of characters to extract, negative returns empty string
+ * @return the new string
+ */
+ public String midString(int index, final int length) {
+ if (index < 0) {
+ index = 0;
+ }
+ if (length <= 0 || index >= size) {
+ return "";
+ }
+ if (size <= index + length) {
+ return new String(buffer, index, size - index);
+ }
+ return new String(buffer, index, length);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Checks if the string builder contains the specified char.
+ *
+ * @param ch the character to find
+ * @return true if the builder contains the character
+ */
+ public boolean contains(final char ch) {
+ final char[] thisBuf = buffer;
+ for (int i = 0; i < this.size; i++) {
+ if (thisBuf[i] == ch) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks if the string builder contains the specified string.
+ *
+ * @param str the string to find
+ * @return true if the builder contains the string
+ */
+ public boolean contains(final String str) {
+ return indexOf(str, 0) >= 0;
+ }
+
+ /**
+ * Checks if the string builder contains a string matched using the
+ * specified matcher.
+ * <p>
+ * Matchers can be used to perform advanced searching behaviour.
+ * For example you 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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 c
<TRUNCATED>
[11/14] [text] TEXT-23: fix assertEquals, SrtBuilderTest diff objs
Posted by ch...@apache.org.
TEXT-23: fix assertEquals, SrtBuilderTest diff objs
Project: http://git-wip-us.apache.org/repos/asf/commons-text/repo
Commit: http://git-wip-us.apache.org/repos/asf/commons-text/commit/7adf1d5e
Tree: http://git-wip-us.apache.org/repos/asf/commons-text/tree/7adf1d5e
Diff: http://git-wip-us.apache.org/repos/asf/commons-text/diff/7adf1d5e
Branch: refs/heads/master
Commit: 7adf1d5ea33fc1a67196f96ce71c17ebdb28efed
Parents: 6f24aa4
Author: Rob Tompkins <ch...@capitalone.com>
Authored: Wed Nov 9 16:00:03 2016 -0500
Committer: Rob Tompkins <ch...@capitalone.com>
Committed: Wed Nov 9 16:13:57 2016 -0500
----------------------------------------------------------------------
src/test/java/org/apache/commons/text/StrBuilderTest.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/commons-text/blob/7adf1d5e/src/test/java/org/apache/commons/text/StrBuilderTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/text/StrBuilderTest.java b/src/test/java/org/apache/commons/text/StrBuilderTest.java
index 1920984..e44c767 100644
--- a/src/test/java/org/apache/commons/text/StrBuilderTest.java
+++ b/src/test/java/org/apache/commons/text/StrBuilderTest.java
@@ -467,7 +467,7 @@ public class StrBuilderTest {
@Test
public void testToCharArray() {
final StrBuilder sb = new StrBuilder();
- assertEquals(ArrayUtils.EMPTY_CHAR_ARRAY, sb.toCharArray());
+ assertEquals(ArrayUtils.EMPTY_CHAR_ARRAY.length, sb.toCharArray().length);
char[] a = sb.toCharArray();
assertNotNull("toCharArray() result is null", a);
@@ -482,7 +482,7 @@ public class StrBuilderTest {
@Test
public void testToCharArrayIntInt() {
final StrBuilder sb = new StrBuilder();
- assertEquals(ArrayUtils.EMPTY_CHAR_ARRAY, sb.toCharArray(0, 0));
+ assertEquals(ArrayUtils.EMPTY_CHAR_ARRAY.length, sb.toCharArray(0, 0).length);
sb.append("junit");
char[] a = sb.toCharArray(0, 20); // too large test
[14/14] [text] Merge branch 'TEXT-23' of
github.com:chtompki/commons-text into TEXT-23
Posted by ch...@apache.org.
Merge branch 'TEXT-23' of github.com:chtompki/commons-text into TEXT-23
Project: http://git-wip-us.apache.org/repos/asf/commons-text/repo
Commit: http://git-wip-us.apache.org/repos/asf/commons-text/commit/a408a7cf
Tree: http://git-wip-us.apache.org/repos/asf/commons-text/tree/a408a7cf
Diff: http://git-wip-us.apache.org/repos/asf/commons-text/diff/a408a7cf
Branch: refs/heads/master
Commit: a408a7cfa9a436d84954003e6977abea1bf0a3cd
Parents: ad7ee39 56c0a07
Author: Rob Tompkins <ch...@gmail.com>
Authored: Sun Nov 13 15:32:01 2016 -0500
Committer: Rob Tompkins <ch...@gmail.com>
Committed: Sun Nov 13 15:32:01 2016 -0500
----------------------------------------------------------------------
src/changes/changes.xml | 1 +
.../java/org/apache/commons/text/Builder.java | 87 +
.../apache/commons/text/CompositeFormat.java | 114 +
.../commons/text/ExtendedMessageFormat.java | 529 +++
.../org/apache/commons/text/FormatFactory.java | 39 +
.../apache/commons/text/FormattableUtils.java | 151 +
.../org/apache/commons/text/StrBuilder.java | 3117 ++++++++++++++++++
.../java/org/apache/commons/text/StrLookup.java | 178 +
.../org/apache/commons/text/StrMatcher.java | 437 +++
.../org/apache/commons/text/StrSubstitutor.java | 1227 +++++++
.../org/apache/commons/text/StrTokenizer.java | 1116 +++++++
.../java/org/apache/commons/text/WordUtils.java | 733 ++++
.../text/translate/AggregateTranslator.java | 2 -
.../text/translate/CharSequenceTranslator.java | 6 +-
.../text/translate/CodePointTranslator.java | 2 -
.../commons/text/translate/EntityArrays.java | 2 -
.../text/translate/JavaUnicodeEscaper.java | 14 +-
.../text/translate/LookupTranslator.java | 2 -
.../text/translate/NumericEntityEscaper.java | 14 +-
.../text/translate/NumericEntityUnescaper.java | 2 -
.../commons/text/translate/OctalUnescaper.java | 2 -
.../commons/text/translate/UnicodeEscaper.java | 14 +-
.../text/translate/UnicodeUnescaper.java | 2 -
.../commons/text/translate/package-info.java | 2 -
.../commons/text/CompositeFormatTest.java | 85 +
.../commons/text/ExtendedMessageFormatTest.java | 484 +++
.../commons/text/FormattableUtilsTest.java | 115 +
.../text/StrBuilderAppendInsertTest.java | 1607 +++++++++
.../org/apache/commons/text/StrBuilderTest.java | 2009 +++++++++++
.../org/apache/commons/text/StrLookupTest.java | 115 +
.../org/apache/commons/text/StrMatcherTest.java | 214 ++
.../apache/commons/text/StrSubstitutorTest.java | 740 +++++
.../apache/commons/text/StrTokenizerTest.java | 879 +++++
.../org/apache/commons/text/WordUtilsTest.java | 421 +++
.../text/translate/AggregateTranslatorTest.java | 56 +
.../text/translate/EntityArraysTest.java | 37 +-
.../text/translate/JavaUnicodeEscaperTest.java | 65 +
37 files changed, 14575 insertions(+), 45 deletions(-)
----------------------------------------------------------------------
[07/14] [text] TEXT-23: Adding remainder of the o.a.c.l.text package
Posted by ch...@apache.org.
http://git-wip-us.apache.org/repos/asf/commons-text/blob/6f24aa45/src/main/java/org/apache/commons/text/StrTokenizer.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/StrTokenizer.java b/src/main/java/org/apache/commons/text/StrTokenizer.java
new file mode 100644
index 0000000..a980bf9
--- /dev/null
+++ b/src/main/java/org/apache/commons/text/StrTokenizer.java
@@ -0,0 +1,1116 @@
+/*
+ * 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.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Tokenizes a string based based on delimiters (separators)
+ * and supporting quoting and ignored character concepts.
+ * <p>
+ * This class can split a String into many smaller strings. It aims
+ * to do a similar job to {@link java.util.StringTokenizer StringTokenizer},
+ * however it offers much more control and flexibility including implementing
+ * the <code>ListIterator</code> interface. By default, it is set up
+ * like <code>StringTokenizer</code>.
+ * <p>
+ * The input String is split into a number of <i>tokens</i>.
+ * Each token is separated from the next String by a <i>delimiter</i>.
+ * One or more delimiter characters must be specified.
+ * <p>
+ * Each token may be surrounded by quotes.
+ * The <i>quote</i> matcher specifies the quote character(s).
+ * A quote may be escaped within a quoted section by duplicating itself.
+ * <p>
+ * Between each token and the delimiter are potentially characters that need trimming.
+ * The <i>trimmer</i> matcher specifies these characters.
+ * One usage might be to trim whitespace characters.
+ * <p>
+ * At any point outside the quotes there might potentially be invalid characters.
+ * The <i>ignored</i> matcher specifies these characters to be removed.
+ * One usage might be to remove new line characters.
+ * <p>
+ * Empty tokens may be removed or returned as null.
+ * <pre>
+ * "a,b,c" - Three tokens "a","b","c" (comma delimiter)
+ * " a, b , c " - Three tokens "a","b","c" (default CSV processing trims whitespace)
+ * "a, ", b ,", c" - Three tokens "a, " , " b ", ", c" (quoted text untouched)
+ * </pre>
+ * <p>
+ *
+ * This tokenizer has the following properties and options:
+ *
+ * <table summary="Tokenizer Properties">
+ * <tr>
+ * <th>Property</th><th>Type</th><th>Default</th>
+ * </tr>
+ * <tr>
+ * <td>delim</td><td>CharSetMatcher</td><td>{ \t\n\r\f}</td>
+ * </tr>
+ * <tr>
+ * <td>quote</td><td>NoneMatcher</td><td>{}</td>
+ * </tr>
+ * <tr>
+ * <td>ignore</td><td>NoneMatcher</td><td>{}</td>
+ * </tr>
+ * <tr>
+ * <td>emptyTokenAsNull</td><td>boolean</td><td>false</td>
+ * </tr>
+ * <tr>
+ * <td>ignoreEmptyTokens</td><td>boolean</td><td>true</td>
+ * </tr>
+ * </table>
+ */
+public class StrTokenizer implements ListIterator<String>, Cloneable {
+
+ private static final StrTokenizer CSV_TOKENIZER_PROTOTYPE;
+ private static final StrTokenizer TSV_TOKENIZER_PROTOTYPE;
+ static {
+ CSV_TOKENIZER_PROTOTYPE = new StrTokenizer();
+ CSV_TOKENIZER_PROTOTYPE.setDelimiterMatcher(StrMatcher.commaMatcher());
+ CSV_TOKENIZER_PROTOTYPE.setQuoteMatcher(StrMatcher.doubleQuoteMatcher());
+ CSV_TOKENIZER_PROTOTYPE.setIgnoredMatcher(StrMatcher.noneMatcher());
+ CSV_TOKENIZER_PROTOTYPE.setTrimmerMatcher(StrMatcher.trimMatcher());
+ CSV_TOKENIZER_PROTOTYPE.setEmptyTokenAsNull(false);
+ CSV_TOKENIZER_PROTOTYPE.setIgnoreEmptyTokens(false);
+
+ TSV_TOKENIZER_PROTOTYPE = new StrTokenizer();
+ TSV_TOKENIZER_PROTOTYPE.setDelimiterMatcher(StrMatcher.tabMatcher());
+ TSV_TOKENIZER_PROTOTYPE.setQuoteMatcher(StrMatcher.doubleQuoteMatcher());
+ TSV_TOKENIZER_PROTOTYPE.setIgnoredMatcher(StrMatcher.noneMatcher());
+ TSV_TOKENIZER_PROTOTYPE.setTrimmerMatcher(StrMatcher.trimMatcher());
+ TSV_TOKENIZER_PROTOTYPE.setEmptyTokenAsNull(false);
+ TSV_TOKENIZER_PROTOTYPE.setIgnoreEmptyTokens(false);
+ }
+
+ /** The text to work on. */
+ private char chars[];
+ /** The parsed tokens */
+ private String tokens[];
+ /** The current iteration position */
+ private int tokenPos;
+
+ /** The delimiter matcher */
+ private StrMatcher delimMatcher = StrMatcher.splitMatcher();
+ /** The quote matcher */
+ private StrMatcher quoteMatcher = StrMatcher.noneMatcher();
+ /** The ignored matcher */
+ private StrMatcher ignoredMatcher = StrMatcher.noneMatcher();
+ /** The trimmer matcher */
+ private StrMatcher trimmerMatcher = StrMatcher.noneMatcher();
+
+ /** Whether to return empty tokens as null */
+ private boolean emptyAsNull = false;
+ /** Whether to ignore empty tokens */
+ private boolean ignoreEmptyTokens = true;
+
+ //-----------------------------------------------------------------------
+
+ /**
+ * Returns a clone of <code>CSV_TOKENIZER_PROTOTYPE</code>.
+ *
+ * @return a clone of <code>CSV_TOKENIZER_PROTOTYPE</code>.
+ */
+ private static StrTokenizer getCSVClone() {
+ return (StrTokenizer) CSV_TOKENIZER_PROTOTYPE.clone();
+ }
+
+ /**
+ * Gets a new tokenizer instance which parses Comma Separated Value strings
+ * initializing it with the given input. The default for CSV processing
+ * will be trim whitespace from both ends (which can be overridden with
+ * the setTrimmer method).
+ * <p>
+ * You must call a "reset" method to set the string which you want to parse.
+ * @return a new tokenizer instance which parses Comma Separated Value strings
+ */
+ public static StrTokenizer getCSVInstance() {
+ return getCSVClone();
+ }
+
+ /**
+ * Gets a new tokenizer instance which parses Comma Separated Value strings
+ * initializing it with the given input. The default for CSV processing
+ * will be trim whitespace from both ends (which can be overridden with
+ * the setTrimmer method).
+ *
+ * @param input the text to parse
+ * @return a new tokenizer instance which parses Comma Separated Value strings
+ */
+ public static StrTokenizer getCSVInstance(final String input) {
+ final StrTokenizer tok = getCSVClone();
+ tok.reset(input);
+ return tok;
+ }
+
+ /**
+ * Gets a new tokenizer instance which parses Comma Separated Value strings
+ * initializing it with the given input. The default for CSV processing
+ * will be trim whitespace from both ends (which can be overridden with
+ * the setTrimmer method).
+ *
+ * @param input the text to parse
+ * @return a new tokenizer instance which parses Comma Separated Value strings
+ */
+ public static StrTokenizer getCSVInstance(final char[] input) {
+ final StrTokenizer tok = getCSVClone();
+ tok.reset(input);
+ return tok;
+ }
+
+ /**
+ * Returns a clone of <code>TSV_TOKENIZER_PROTOTYPE</code>.
+ *
+ * @return a clone of <code>TSV_TOKENIZER_PROTOTYPE</code>.
+ */
+ private static StrTokenizer getTSVClone() {
+ return (StrTokenizer) TSV_TOKENIZER_PROTOTYPE.clone();
+ }
+
+
+ /**
+ * Gets a new tokenizer instance which parses Tab Separated Value strings.
+ * The default for CSV processing will be trim whitespace from both ends
+ * (which can be overridden with the setTrimmer method).
+ * <p>
+ * You must call a "reset" method to set the string which you want to parse.
+ * @return a new tokenizer instance which parses Tab Separated Value strings.
+ */
+ public static StrTokenizer getTSVInstance() {
+ return getTSVClone();
+ }
+
+ /**
+ * Gets a new tokenizer instance which parses Tab Separated Value strings.
+ * The default for CSV processing will be trim whitespace from both ends
+ * (which can be overridden with the setTrimmer method).
+ * @param input the string to parse
+ * @return a new tokenizer instance which parses Tab Separated Value strings.
+ */
+ public static StrTokenizer getTSVInstance(final String input) {
+ final StrTokenizer tok = getTSVClone();
+ tok.reset(input);
+ return tok;
+ }
+
+ /**
+ * Gets a new tokenizer instance which parses Tab Separated Value strings.
+ * The default for CSV processing will be trim whitespace from both ends
+ * (which can be overridden with the setTrimmer method).
+ * @param input the string to parse
+ * @return a new tokenizer instance which parses Tab Separated Value strings.
+ */
+ public static StrTokenizer getTSVInstance(final char[] input) {
+ final StrTokenizer tok = getTSVClone();
+ tok.reset(input);
+ return tok;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Constructs a tokenizer splitting on space, tab, newline and formfeed
+ * as per StringTokenizer, but with no text to tokenize.
+ * <p>
+ * This constructor is normally used with {@link #reset(String)}.
+ */
+ public StrTokenizer() {
+ super();
+ this.chars = null;
+ }
+
+ /**
+ * Constructs a tokenizer splitting on space, tab, newline and formfeed
+ * as per StringTokenizer.
+ *
+ * @param input the string which is to be parsed
+ */
+ public StrTokenizer(final String input) {
+ super();
+ if (input != null) {
+ chars = input.toCharArray();
+ } else {
+ chars = null;
+ }
+ }
+
+ /**
+ * Constructs a tokenizer splitting on the specified delimiter character.
+ *
+ * @param input the string which is to be parsed
+ * @param delim the field delimiter character
+ */
+ public StrTokenizer(final String input, final char delim) {
+ this(input);
+ setDelimiterChar(delim);
+ }
+
+ /**
+ * Constructs a tokenizer splitting on the specified delimiter string.
+ *
+ * @param input the string which is to be parsed
+ * @param delim the field delimiter string
+ */
+ public StrTokenizer(final String input, final String delim) {
+ this(input);
+ setDelimiterString(delim);
+ }
+
+ /**
+ * Constructs a tokenizer splitting using the specified delimiter matcher.
+ *
+ * @param input the string which is to be parsed
+ * @param delim the field delimiter matcher
+ */
+ public StrTokenizer(final String input, final StrMatcher delim) {
+ this(input);
+ setDelimiterMatcher(delim);
+ }
+
+ /**
+ * Constructs a tokenizer splitting on the specified delimiter character
+ * and handling quotes using the specified quote character.
+ *
+ * @param input the string which is to be parsed
+ * @param delim the field delimiter character
+ * @param quote the field quoted string character
+ */
+ public StrTokenizer(final String input, final char delim, final char quote) {
+ this(input, delim);
+ setQuoteChar(quote);
+ }
+
+ /**
+ * Constructs a tokenizer splitting using the specified delimiter matcher
+ * and handling quotes using the specified quote matcher.
+ *
+ * @param input the string which is to be parsed
+ * @param delim the field delimiter matcher
+ * @param quote the field quoted string matcher
+ */
+ public StrTokenizer(final String input, final StrMatcher delim, final StrMatcher quote) {
+ this(input, delim);
+ setQuoteMatcher(quote);
+ }
+
+ /**
+ * Constructs a tokenizer splitting on space, tab, newline and formfeed
+ * as per StringTokenizer.
+ *
+ * @param input the string which is to be parsed, not cloned
+ */
+ public StrTokenizer(final char[] input) {
+ super();
+ if (input == null) {
+ this.chars = null;
+ } else {
+ this.chars = input.clone();
+ }
+ }
+
+ /**
+ * Constructs a tokenizer splitting on the specified character.
+ *
+ * @param input the string which is to be parsed, not cloned
+ * @param delim the field delimiter character
+ */
+ public StrTokenizer(final char[] input, final char delim) {
+ this(input);
+ setDelimiterChar(delim);
+ }
+
+ /**
+ * Constructs a tokenizer splitting on the specified string.
+ *
+ * @param input the string which is to be parsed, not cloned
+ * @param delim the field delimiter string
+ */
+ public StrTokenizer(final char[] input, final String delim) {
+ this(input);
+ setDelimiterString(delim);
+ }
+
+ /**
+ * Constructs a tokenizer splitting using the specified delimiter matcher.
+ *
+ * @param input the string which is to be parsed, not cloned
+ * @param delim the field delimiter matcher
+ */
+ public StrTokenizer(final char[] input, final StrMatcher delim) {
+ this(input);
+ setDelimiterMatcher(delim);
+ }
+
+ /**
+ * Constructs a tokenizer splitting on the specified delimiter character
+ * and handling quotes using the specified quote character.
+ *
+ * @param input the string which is to be parsed, not cloned
+ * @param delim the field delimiter character
+ * @param quote the field quoted string character
+ */
+ public StrTokenizer(final char[] input, final char delim, final char quote) {
+ this(input, delim);
+ setQuoteChar(quote);
+ }
+
+ /**
+ * Constructs a tokenizer splitting using the specified delimiter matcher
+ * and handling quotes using the specified quote matcher.
+ *
+ * @param input the string which is to be parsed, not cloned
+ * @param delim the field delimiter character
+ * @param quote the field quoted string character
+ */
+ public StrTokenizer(final char[] input, final StrMatcher delim, final StrMatcher quote) {
+ this(input, delim);
+ setQuoteMatcher(quote);
+ }
+
+ // API
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the number of tokens found in the String.
+ *
+ * @return the number of matched tokens
+ */
+ public int size() {
+ checkTokenized();
+ return tokens.length;
+ }
+
+ /**
+ * Gets the next token from the String.
+ * Equivalent to {@link #next()} except it returns null rather than
+ * throwing {@link NoSuchElementException} when no tokens remain.
+ *
+ * @return the next sequential token, or null when no more tokens are found
+ */
+ public String nextToken() {
+ if (hasNext()) {
+ return tokens[tokenPos++];
+ }
+ return null;
+ }
+
+ /**
+ * Gets the previous token from the String.
+ *
+ * @return the previous sequential token, or null when no more tokens are found
+ */
+ public String previousToken() {
+ if (hasPrevious()) {
+ return tokens[--tokenPos];
+ }
+ return null;
+ }
+
+ /**
+ * Gets a copy of the full token list as an independent modifiable array.
+ *
+ * @return the tokens as a String array
+ */
+ public String[] getTokenArray() {
+ checkTokenized();
+ return tokens.clone();
+ }
+
+ /**
+ * Gets a copy of the full token list as an independent modifiable list.
+ *
+ * @return the tokens as a String array
+ */
+ public List<String> getTokenList() {
+ checkTokenized();
+ final List<String> list = new ArrayList<>(tokens.length);
+ for (final String element : tokens) {
+ list.add(element);
+ }
+ return list;
+ }
+
+ /**
+ * Resets this tokenizer, forgetting all parsing and iteration already completed.
+ * <p>
+ * This method allows the same tokenizer to be reused for the same String.
+ *
+ * @return this, to enable chaining
+ */
+ public org.apache.commons.text.StrTokenizer reset() {
+ tokenPos = 0;
+ tokens = null;
+ return this;
+ }
+
+ /**
+ * Reset this tokenizer, giving it a new input string to parse.
+ * In this manner you can re-use a tokenizer with the same settings
+ * on multiple input lines.
+ *
+ * @param input the new string to tokenize, null sets no text to parse
+ * @return this, to enable chaining
+ */
+ public org.apache.commons.text.StrTokenizer reset(final String input) {
+ reset();
+ if (input != null) {
+ this.chars = input.toCharArray();
+ } else {
+ this.chars = null;
+ }
+ return this;
+ }
+
+ /**
+ * Reset this tokenizer, giving it a new input string to parse.
+ * In this manner you can re-use a tokenizer with the same settings
+ * on multiple input lines.
+ *
+ * @param input the new character array to tokenize, not cloned, null sets no text to parse
+ * @return this, to enable chaining
+ */
+ public org.apache.commons.text.StrTokenizer reset(final char[] input) {
+ reset();
+ if (input != null) {
+ this.chars = input;
+ } else {
+ this.chars = null;
+ }
+ return this;
+ }
+
+ // ListIterator
+ //-----------------------------------------------------------------------
+ /**
+ * Checks whether there are any more tokens.
+ *
+ * @return true if there are more tokens
+ */
+ @Override
+ public boolean hasNext() {
+ checkTokenized();
+ return tokenPos < tokens.length;
+ }
+
+ /**
+ * Gets the next token.
+ *
+ * @return the next String token
+ * @throws NoSuchElementException if there are no more elements
+ */
+ @Override
+ public String next() {
+ if (hasNext()) {
+ return tokens[tokenPos++];
+ }
+ throw new NoSuchElementException();
+ }
+
+ /**
+ * Gets the index of the next token to return.
+ *
+ * @return the next token index
+ */
+ @Override
+ public int nextIndex() {
+ return tokenPos;
+ }
+
+ /**
+ * Checks whether there are any previous tokens that can be iterated to.
+ *
+ * @return true if there are previous tokens
+ */
+ @Override
+ public boolean hasPrevious() {
+ checkTokenized();
+ return tokenPos > 0;
+ }
+
+ /**
+ * Gets the token previous to the last returned token.
+ *
+ * @return the previous token
+ */
+ @Override
+ public String previous() {
+ if (hasPrevious()) {
+ return tokens[--tokenPos];
+ }
+ throw new NoSuchElementException();
+ }
+
+ /**
+ * Gets the index of the previous token.
+ *
+ * @return the previous token index
+ */
+ @Override
+ public int previousIndex() {
+ return tokenPos - 1;
+ }
+
+ /**
+ * Unsupported ListIterator operation.
+ *
+ * @throws UnsupportedOperationException always
+ */
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("remove() is unsupported");
+ }
+
+ /**
+ * Unsupported ListIterator operation.
+ * @param obj this parameter ignored.
+ * @throws UnsupportedOperationException always
+ */
+ @Override
+ public void set(final String obj) {
+ throw new UnsupportedOperationException("set() is unsupported");
+ }
+
+ /**
+ * Unsupported ListIterator operation.
+ * @param obj this parameter ignored.
+ * @throws UnsupportedOperationException always
+ */
+ @Override
+ public void add(final String obj) {
+ throw new UnsupportedOperationException("add() is unsupported");
+ }
+
+ // Implementation
+ //-----------------------------------------------------------------------
+ /**
+ * Checks if tokenization has been done, and if not then do it.
+ */
+ private void checkTokenized() {
+ if (tokens == null) {
+ if (chars == null) {
+ // still call tokenize as subclass may do some work
+ final List<String> split = tokenize(null, 0, 0);
+ tokens = split.toArray(new String[split.size()]);
+ } else {
+ final List<String> split = tokenize(chars, 0, chars.length);
+ tokens = split.toArray(new String[split.size()]);
+ }
+ }
+ }
+
+ /**
+ * Internal method to performs the tokenization.
+ * <p>
+ * Most users of this class do not need to call this method. This method
+ * will be called automatically by other (public) methods when required.
+ * <p>
+ * This method exists to allow subclasses to add code before or after the
+ * tokenization. For example, a subclass could alter the character array,
+ * offset or count to be parsed, or call the tokenizer multiple times on
+ * multiple strings. It is also be possible to filter the results.
+ * <p>
+ * <code>StrTokenizer</code> will always pass a zero offset and a count
+ * equal to the length of the array to this method, however a subclass
+ * may pass other values, or even an entirely different array.
+ *
+ * @param srcChars the character array being tokenized, may be null
+ * @param offset the start position within the character array, must be valid
+ * @param count the number of characters to tokenize, must be valid
+ * @return the modifiable list of String tokens, unmodifiable if null array or zero count
+ */
+ protected List<String> tokenize(final char[] srcChars, final int offset, final int count) {
+ if (srcChars == null || count == 0) {
+ return Collections.emptyList();
+ }
+ final StrBuilder buf = new StrBuilder();
+ final List<String> tokenList = new ArrayList<>();
+ int pos = offset;
+
+ // loop around the entire buffer
+ while (pos >= 0 && pos < count) {
+ // find next token
+ pos = readNextToken(srcChars, pos, count, buf, tokenList);
+
+ // handle case where end of string is a delimiter
+ if (pos >= count) {
+ addToken(tokenList, "");
+ }
+ }
+ return tokenList;
+ }
+
+ /**
+ * Adds a token to a list, paying attention to the parameters we've set.
+ *
+ * @param list the list to add to
+ * @param tok the token to add
+ */
+ private void addToken(final List<String> list, String tok) {
+ if (tok == null || tok.length() == 0) {
+ if (isIgnoreEmptyTokens()) {
+ return;
+ }
+ if (isEmptyTokenAsNull()) {
+ tok = null;
+ }
+ }
+ list.add(tok);
+ }
+
+ /**
+ * Reads character by character through the String to get the next token.
+ *
+ * @param srcChars the character array being tokenized
+ * @param start the first character of field
+ * @param len the length of the character array being tokenized
+ * @param workArea a temporary work area
+ * @param tokenList the list of parsed tokens
+ * @return the starting position of the next field (the character
+ * immediately after the delimiter), or -1 if end of string found
+ */
+ private int readNextToken(final char[] srcChars, int start, final int len, final StrBuilder workArea, final List<String> tokenList) {
+ // skip all leading whitespace, unless it is the
+ // field delimiter or the quote character
+ while (start < len) {
+ final int removeLen = Math.max(
+ getIgnoredMatcher().isMatch(srcChars, start, start, len),
+ getTrimmerMatcher().isMatch(srcChars, start, start, len));
+ if (removeLen == 0 ||
+ getDelimiterMatcher().isMatch(srcChars, start, start, len) > 0 ||
+ getQuoteMatcher().isMatch(srcChars, start, start, len) > 0) {
+ break;
+ }
+ start += removeLen;
+ }
+
+ // handle reaching end
+ if (start >= len) {
+ addToken(tokenList, "");
+ return -1;
+ }
+
+ // handle empty token
+ final int delimLen = getDelimiterMatcher().isMatch(srcChars, start, start, len);
+ if (delimLen > 0) {
+ addToken(tokenList, "");
+ return start + delimLen;
+ }
+
+ // handle found token
+ final int quoteLen = getQuoteMatcher().isMatch(srcChars, start, start, len);
+ if (quoteLen > 0) {
+ return readWithQuotes(srcChars, start + quoteLen, len, workArea, tokenList, start, quoteLen);
+ }
+ return readWithQuotes(srcChars, start, len, workArea, tokenList, 0, 0);
+ }
+
+ /**
+ * Reads a possibly quoted string token.
+ *
+ * @param srcChars the character array being tokenized
+ * @param start the first character of field
+ * @param len the length of the character array being tokenized
+ * @param workArea a temporary work area
+ * @param tokenList the list of parsed tokens
+ * @param quoteStart the start position of the matched quote, 0 if no quoting
+ * @param quoteLen the length of the matched quote, 0 if no quoting
+ * @return the starting position of the next field (the character
+ * immediately after the delimiter, or if end of string found,
+ * then the length of string
+ */
+ private int readWithQuotes(final char[] srcChars, final int start, final int len, final StrBuilder workArea,
+ final List<String> tokenList, final int quoteStart, final int quoteLen) {
+ // Loop until we've found the end of the quoted
+ // string or the end of the input
+ workArea.clear();
+ int pos = start;
+ boolean quoting = quoteLen > 0;
+ int trimStart = 0;
+
+ while (pos < len) {
+ // quoting mode can occur several times throughout a string
+ // we must switch between quoting and non-quoting until we
+ // encounter a non-quoted delimiter, or end of string
+ if (quoting) {
+ // In quoting mode
+
+ // If we've found a quote character, see if it's
+ // followed by a second quote. If so, then we need
+ // to actually put the quote character into the token
+ // rather than end the token.
+ if (isQuote(srcChars, pos, len, quoteStart, quoteLen)) {
+ if (isQuote(srcChars, pos + quoteLen, len, quoteStart, quoteLen)) {
+ // matched pair of quotes, thus an escaped quote
+ workArea.append(srcChars, pos, quoteLen);
+ pos += quoteLen * 2;
+ trimStart = workArea.size();
+ continue;
+ }
+
+ // end of quoting
+ quoting = false;
+ pos += quoteLen;
+ continue;
+ }
+
+ // copy regular character from inside quotes
+ workArea.append(srcChars[pos++]);
+ trimStart = workArea.size();
+
+ } else {
+ // Not in quoting mode
+
+ // check for delimiter, and thus end of token
+ final int delimLen = getDelimiterMatcher().isMatch(srcChars, pos, start, len);
+ if (delimLen > 0) {
+ // return condition when end of token found
+ addToken(tokenList, workArea.substring(0, trimStart));
+ return pos + delimLen;
+ }
+
+ // check for quote, and thus back into quoting mode
+ if (quoteLen > 0 && isQuote(srcChars, pos, len, quoteStart, quoteLen)) {
+ quoting = true;
+ pos += quoteLen;
+ continue;
+ }
+
+ // check for ignored (outside quotes), and ignore
+ final int ignoredLen = getIgnoredMatcher().isMatch(srcChars, pos, start, len);
+ if (ignoredLen > 0) {
+ pos += ignoredLen;
+ continue;
+ }
+
+ // check for trimmed character
+ // don't yet know if its at the end, so copy to workArea
+ // use trimStart to keep track of trim at the end
+ final int trimmedLen = getTrimmerMatcher().isMatch(srcChars, pos, start, len);
+ if (trimmedLen > 0) {
+ workArea.append(srcChars, pos, trimmedLen);
+ pos += trimmedLen;
+ continue;
+ }
+
+ // copy regular character from outside quotes
+ workArea.append(srcChars[pos++]);
+ trimStart = workArea.size();
+ }
+ }
+
+ // return condition when end of string found
+ addToken(tokenList, workArea.substring(0, trimStart));
+ return -1;
+ }
+
+ /**
+ * Checks if the characters at the index specified match the quote
+ * already matched in readNextToken().
+ *
+ * @param srcChars the character array being tokenized
+ * @param pos the position to check for a quote
+ * @param len the length of the character array being tokenized
+ * @param quoteStart the start position of the matched quote, 0 if no quoting
+ * @param quoteLen the length of the matched quote, 0 if no quoting
+ * @return true if a quote is matched
+ */
+ private boolean isQuote(final char[] srcChars, final int pos, final int len, final int quoteStart, final int quoteLen) {
+ for (int i = 0; i < quoteLen; i++) {
+ if (pos + i >= len || srcChars[pos + i] != srcChars[quoteStart + i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Delimiter
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the field delimiter matcher.
+ *
+ * @return the delimiter matcher in use
+ */
+ public StrMatcher getDelimiterMatcher() {
+ return this.delimMatcher;
+ }
+
+ /**
+ * Sets the field delimiter matcher.
+ * <p>
+ * The delimitier is used to separate one token from another.
+ *
+ * @param delim the delimiter matcher to use
+ * @return this, to enable chaining
+ */
+ public StrTokenizer setDelimiterMatcher(final StrMatcher delim) {
+ if (delim == null) {
+ this.delimMatcher = StrMatcher.noneMatcher();
+ } else {
+ this.delimMatcher = delim;
+ }
+ return this;
+ }
+
+ /**
+ * Sets the field delimiter character.
+ *
+ * @param delim the delimiter character to use
+ * @return this, to enable chaining
+ */
+ public StrTokenizer setDelimiterChar(final char delim) {
+ return setDelimiterMatcher(StrMatcher.charMatcher(delim));
+ }
+
+ /**
+ * Sets the field delimiter string.
+ *
+ * @param delim the delimiter string to use
+ * @return this, to enable chaining
+ */
+ public StrTokenizer setDelimiterString(final String delim) {
+ return setDelimiterMatcher(StrMatcher.stringMatcher(delim));
+ }
+
+ // Quote
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the quote matcher currently in use.
+ * <p>
+ * The quote character is used to wrap data between the tokens.
+ * This enables delimiters to be entered as data.
+ * The default value is '"' (double quote).
+ *
+ * @return the quote matcher in use
+ */
+ public StrMatcher getQuoteMatcher() {
+ return quoteMatcher;
+ }
+
+ /**
+ * Set the quote matcher to use.
+ * <p>
+ * The quote character is used to wrap data between the tokens.
+ * This enables delimiters to be entered as data.
+ *
+ * @param quote the quote matcher to use, null ignored
+ * @return this, to enable chaining
+ */
+ public StrTokenizer setQuoteMatcher(final StrMatcher quote) {
+ if (quote != null) {
+ this.quoteMatcher = quote;
+ }
+ return this;
+ }
+
+ /**
+ * Sets the quote character to use.
+ * <p>
+ * The quote character is used to wrap data between the tokens.
+ * This enables delimiters to be entered as data.
+ *
+ * @param quote the quote character to use
+ * @return this, to enable chaining
+ */
+ public StrTokenizer setQuoteChar(final char quote) {
+ return setQuoteMatcher(StrMatcher.charMatcher(quote));
+ }
+
+ // Ignored
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the ignored character matcher.
+ * <p>
+ * These characters are ignored when parsing the String, unless they are
+ * within a quoted region.
+ * The default value is not to ignore anything.
+ *
+ * @return the ignored matcher in use
+ */
+ public StrMatcher getIgnoredMatcher() {
+ return ignoredMatcher;
+ }
+
+ /**
+ * Set the matcher for characters to ignore.
+ * <p>
+ * These characters are ignored when parsing the String, unless they are
+ * within a quoted region.
+ *
+ * @param ignored the ignored matcher to use, null ignored
+ * @return this, to enable chaining
+ */
+ public StrTokenizer setIgnoredMatcher(final StrMatcher ignored) {
+ if (ignored != null) {
+ this.ignoredMatcher = ignored;
+ }
+ return this;
+ }
+
+ /**
+ * Set the character to ignore.
+ * <p>
+ * This character is ignored when parsing the String, unless it is
+ * within a quoted region.
+ *
+ * @param ignored the ignored character to use
+ * @return this, to enable chaining
+ */
+ public StrTokenizer setIgnoredChar(final char ignored) {
+ return setIgnoredMatcher(StrMatcher.charMatcher(ignored));
+ }
+
+ // Trimmer
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the trimmer character matcher.
+ * <p>
+ * These characters are trimmed off on each side of the delimiter
+ * until the token or quote is found.
+ * The default value is not to trim anything.
+ *
+ * @return the trimmer matcher in use
+ */
+ public StrMatcher getTrimmerMatcher() {
+ return trimmerMatcher;
+ }
+
+ /**
+ * Sets the matcher for characters to trim.
+ * <p>
+ * These characters are trimmed off on each side of the delimiter
+ * until the token or quote is found.
+ *
+ * @param trimmer the trimmer matcher to use, null ignored
+ * @return this, to enable chaining
+ */
+ public StrTokenizer setTrimmerMatcher(final StrMatcher trimmer) {
+ if (trimmer != null) {
+ this.trimmerMatcher = trimmer;
+ }
+ return this;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets whether the tokenizer currently returns empty tokens as null.
+ * The default for this property is false.
+ *
+ * @return true if empty tokens are returned as null
+ */
+ public boolean isEmptyTokenAsNull() {
+ return this.emptyAsNull;
+ }
+
+ /**
+ * Sets whether the tokenizer should return empty tokens as null.
+ * The default for this property is false.
+ *
+ * @param emptyAsNull whether empty tokens are returned as null
+ * @return this, to enable chaining
+ */
+ public StrTokenizer setEmptyTokenAsNull(final boolean emptyAsNull) {
+ this.emptyAsNull = emptyAsNull;
+ return this;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets whether the tokenizer currently ignores empty tokens.
+ * The default for this property is true.
+ *
+ * @return true if empty tokens are not returned
+ */
+ public boolean isIgnoreEmptyTokens() {
+ return ignoreEmptyTokens;
+ }
+
+ /**
+ * Sets whether the tokenizer should ignore and not return empty tokens.
+ * The default for this property is true.
+ *
+ * @param ignoreEmptyTokens whether empty tokens are not returned
+ * @return this, to enable chaining
+ */
+ public StrTokenizer setIgnoreEmptyTokens(final boolean ignoreEmptyTokens) {
+ this.ignoreEmptyTokens = ignoreEmptyTokens;
+ return this;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the String content that the tokenizer is parsing.
+ *
+ * @return the string content being parsed
+ */
+ public String getContent() {
+ if (chars == null) {
+ return null;
+ }
+ return new String(chars);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Creates a new instance of this Tokenizer. The new instance is reset so
+ * that it will be at the start of the token list.
+ * If a {@link CloneNotSupportedException} is caught, return <code>null</code>.
+ *
+ * @return a new instance of this Tokenizer which has been reset.
+ */
+ @Override
+ public Object clone() {
+ try {
+ return cloneReset();
+ } catch (final CloneNotSupportedException ex) {
+ return null;
+ }
+ }
+
+ /**
+ * Creates a new instance of this Tokenizer. The new instance is reset so that
+ * it will be at the start of the token list.
+ *
+ * @return a new instance of this Tokenizer which has been reset.
+ * @throws CloneNotSupportedException if there is a problem cloning
+ */
+ Object cloneReset() throws CloneNotSupportedException {
+ // this method exists to enable 100% test coverage
+ final StrTokenizer cloned = (StrTokenizer) super.clone();
+ if (cloned.chars != null) {
+ cloned.chars = cloned.chars.clone();
+ }
+ cloned.reset();
+ return cloned;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the String content that the tokenizer is parsing.
+ *
+ * @return the string content being parsed
+ */
+ @Override
+ public String toString() {
+ if (tokens == null) {
+ return "StrTokenizer[not tokenized yet]";
+ }
+ return "StrTokenizer" + getTokenList();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/commons-text/blob/6f24aa45/src/main/java/org/apache/commons/text/WordUtils.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/WordUtils.java b/src/main/java/org/apache/commons/text/WordUtils.java
new file mode 100644
index 0000000..1d0085c
--- /dev/null
+++ b/src/main/java/org/apache/commons/text/WordUtils.java
@@ -0,0 +1,733 @@
+/*
+ * 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.lang.reflect.Array;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * <p>Operations on Strings that contain words.</p>
+ *
+ * <p>This class tries to handle <code>null</code> input gracefully.
+ * An exception will not be thrown for a <code>null</code> input.
+ * Each method documents its behaviour in more detail.</p>
+ */
+public class WordUtils {
+
+ /**
+ * <p><code>WordUtils</code> instances should NOT be constructed in
+ * standard programming. Instead, the class should be used as
+ * <code>WordUtils.wrap("foo bar", 20);</code>.</p>
+ *
+ * <p>This constructor is public to permit tools that require a JavaBean
+ * instance to operate.</p>
+ */
+ public WordUtils() {
+ super();
+ }
+
+ // Wrapping
+ //--------------------------------------------------------------------------
+ /**
+ * <p>Wraps a single line of text, identifying words by <code>' '</code>.</p>
+ *
+ * <p>New lines will be separated by the system property line separator.
+ * Very long words, such as URLs will <i>not</i> be wrapped.</p>
+ *
+ * <p>Leading spaces on a new line are stripped.
+ * Trailing spaces are not stripped.</p>
+ *
+ * <table border="1" summary="Wrap Results">
+ * <tr>
+ * <th>input</th>
+ * <th>wrapLength</th>
+ * <th>result</th>
+ * </tr>
+ * <tr>
+ * <td>null</td>
+ * <td>*</td>
+ * <td>null</td>
+ * </tr>
+ * <tr>
+ * <td>""</td>
+ * <td>*</td>
+ * <td>""</td>
+ * </tr>
+ * <tr>
+ * <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
+ * <td>20</td>
+ * <td>"Here is one line of\ntext that is going\nto be wrapped after\n20 columns."</td>
+ * </tr>
+ * <tr>
+ * <td>"Click here to jump to the commons website - http://commons.apache.org"</td>
+ * <td>20</td>
+ * <td>"Click here to jump\nto the commons\nwebsite -\nhttp://commons.apache.org"</td>
+ * </tr>
+ * <tr>
+ * <td>"Click here, http://commons.apache.org, to jump to the commons website"</td>
+ * <td>20</td>
+ * <td>"Click here,\nhttp://commons.apache.org,\nto jump to the\ncommons website"</td>
+ * </tr>
+ * </table>
+ *
+ * (assuming that '\n' is the systems line separator)
+ *
+ * @param str the String to be word wrapped, may be null
+ * @param wrapLength the column to wrap the words at, less than 1 is treated as 1
+ * @return a line with newlines inserted, <code>null</code> if null input
+ */
+ public static String wrap(final String str, final int wrapLength) {
+ return wrap(str, wrapLength, null, false);
+ }
+
+ /**
+ * <p>Wraps a single line of text, identifying words by <code>' '</code>.</p>
+ *
+ * <p>Leading spaces on a new line are stripped.
+ * Trailing spaces are not stripped.</p>
+ *
+ * <table border="1" summary="Wrap Results">
+ * <tr>
+ * <th>input</th>
+ * <th>wrapLenght</th>
+ * <th>newLineString</th>
+ * <th>wrapLongWords</th>
+ * <th>result</th>
+ * </tr>
+ * <tr>
+ * <td>null</td>
+ * <td>*</td>
+ * <td>*</td>
+ * <td>true/false</td>
+ * <td>null</td>
+ * </tr>
+ * <tr>
+ * <td>""</td>
+ * <td>*</td>
+ * <td>*</td>
+ * <td>true/false</td>
+ * <td>""</td>
+ * </tr>
+ * <tr>
+ * <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
+ * <td>20</td>
+ * <td>"\n"</td>
+ * <td>true/false</td>
+ * <td>"Here is one line of\ntext that is going\nto be wrapped after\n20 columns."</td>
+ * </tr>
+ * <tr>
+ * <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
+ * <td>20</td>
+ * <td>"<br />"</td>
+ * <td>true/false</td>
+ * <td>"Here is one line of<br />text that is going<br />to be wrapped after<br />20 columns."</td>
+ * </tr>
+ * <tr>
+ * <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
+ * <td>20</td>
+ * <td>null</td>
+ * <td>true/false</td>
+ * <td>"Here is one line of" + systemNewLine + "text that is going" + systemNewLine + "to be wrapped after" + systemNewLine + "20 columns."</td>
+ * </tr>
+ * <tr>
+ * <td>"Click here to jump to the commons website - http://commons.apache.org"</td>
+ * <td>20</td>
+ * <td>"\n"</td>
+ * <td>false</td>
+ * <td>"Click here to jump\nto the commons\nwebsite -\nhttp://commons.apache.org"</td>
+ * </tr>
+ * <tr>
+ * <td>"Click here to jump to the commons website - http://commons.apache.org"</td>
+ * <td>20</td>
+ * <td>"\n"</td>
+ * <td>true</td>
+ * <td>"Click here to jump\nto the commons\nwebsite -\nhttp://commons.apach\ne.org"</td>
+ * </tr>
+ * </table>
+ *
+ * @param str the String to be word wrapped, may be null
+ * @param wrapLength the column to wrap the words at, less than 1 is treated as 1
+ * @param newLineStr the string to insert for a new line,
+ * <code>null</code> uses the system property line separator
+ * @param wrapLongWords true if long words (such as URLs) should be wrapped
+ * @return a line with newlines inserted, <code>null</code> if null input
+ */
+ public static String wrap(final String str, final int wrapLength, final String newLineStr, final boolean wrapLongWords) {
+ return wrap(str, wrapLength, newLineStr, wrapLongWords, " ");
+ }
+
+ /**
+ * <p>Wraps a single line of text, identifying words by <code>wrapOn</code>.</p>
+ *
+ * <p>Leading spaces on a new line are stripped.
+ * Trailing spaces are not stripped.</p>
+ *
+ * <table border="1" summary="Wrap Results">
+ * <tr>
+ * <th>input</th>
+ * <th>wrapLenght</th>
+ * <th>newLineString</th>
+ * <th>wrapLongWords</th>
+ * <th>wrapOn</th>
+ * <th>result</th>
+ * </tr>
+ * <tr>
+ * <td>null</td>
+ * <td>*</td>
+ * <td>*</td>
+ * <td>true/false</td>
+ * <td>*</td>
+ * <td>null</td>
+ * </tr>
+ * <tr>
+ * <td>""</td>
+ * <td>*</td>
+ * <td>*</td>
+ * <td>true/false</td>
+ * <td>*</td>
+ * <td>""</td>
+ * </tr>
+ * <tr>
+ * <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
+ * <td>20</td>
+ * <td>"\n"</td>
+ * <td>true/false</td>
+ * <td>" "</td>
+ * <td>"Here is one line of\ntext that is going\nto be wrapped after\n20 columns."</td>
+ * </tr>
+ * <tr>
+ * <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
+ * <td>20</td>
+ * <td>"<br />"</td>
+ * <td>true/false</td>
+ * <td>" "</td>
+ * <td>"Here is one line of<br />text that is going<br />to be wrapped after<br />20 columns."</td>
+ * </tr>
+ * <tr>
+ * <td>"Here is one line of text that is going to be wrapped after 20 columns."</td>
+ * <td>20</td>
+ * <td>null</td>
+ * <td>true/false</td>
+ * <td>" "</td>
+ * <td>"Here is one line of" + systemNewLine + "text that is going" + systemNewLine + "to be wrapped after" + systemNewLine + "20 columns."</td>
+ * </tr>
+ * <tr>
+ * <td>"Click here to jump to the commons website - http://commons.apache.org"</td>
+ * <td>20</td>
+ * <td>"\n"</td>
+ * <td>false</td>
+ * <td>" "</td>
+ * <td>"Click here to jump\nto the commons\nwebsite -\nhttp://commons.apache.org"</td>
+ * </tr>
+ * <tr>
+ * <td>"Click here to jump to the commons website - http://commons.apache.org"</td>
+ * <td>20</td>
+ * <td>"\n"</td>
+ * <td>true</td>
+ * <td>" "</td>
+ * <td>"Click here to jump\nto the commons\nwebsite -\nhttp://commons.apach\ne.org"</td>
+ * </tr>
+ * <tr>
+ * <td>"flammable/inflammable"</td>
+ * <td>20</td>
+ * <td>"\n"</td>
+ * <td>true</td>
+ * <td>"/"</td>
+ * <td>"flammable\ninflammable"</td>
+ * </tr>
+ * </table>
+ * @param str the String to be word wrapped, may be null
+ * @param wrapLength the column to wrap the words at, less than 1 is treated as 1
+ * @param newLineStr the string to insert for a new line,
+ * <code>null</code> uses the system property line separator
+ * @param wrapLongWords true if long words (such as URLs) should be wrapped
+ * @param wrapOn regex expression to be used as a breakable characters,
+ * if blank string is provided a space character will be used
+ * @return a line with newlines inserted, <code>null</code> if null input
+ */
+ public static String wrap(final String str, int wrapLength, String newLineStr, final boolean wrapLongWords, String wrapOn) {
+ if (str == null) {
+ return null;
+ }
+ if (newLineStr == null) {
+ newLineStr = System.getProperty("line.separator");
+ }
+ if (wrapLength < 1) {
+ wrapLength = 1;
+ }
+ if (wrapOn == null || wrapOn.length() == 0 || wrapOn.trim().length() == 0) {
+ wrapOn = " ";
+ }
+ final Pattern patternToWrapOn = Pattern.compile(wrapOn);
+ final int inputLineLength = str.length();
+ int offset = 0;
+ final StringBuilder wrappedLine = new StringBuilder(inputLineLength + 32);
+
+ while (offset < inputLineLength) {
+ int spaceToWrapAt = -1;
+ Matcher matcher = patternToWrapOn.matcher(str.substring(offset, Math
+ .min(offset + wrapLength + 1, inputLineLength)));
+ if (matcher.find()) {
+ if (matcher.start() == 0) {
+ offset += matcher.end();
+ continue;
+ }else {
+ spaceToWrapAt = matcher.start();
+ }
+ }
+
+ // only last line without leading spaces is left
+ if(inputLineLength - offset <= wrapLength) {
+ break;
+ }
+
+ while(matcher.find()){
+ spaceToWrapAt = matcher.start() + offset;
+ }
+
+ if (spaceToWrapAt >= offset) {
+ // normal case
+ wrappedLine.append(str.substring(offset, spaceToWrapAt));
+ wrappedLine.append(newLineStr);
+ offset = spaceToWrapAt + 1;
+
+ } else {
+ // really long word or URL
+ if (wrapLongWords) {
+ // wrap really long word one line at a time
+ wrappedLine.append(str.substring(offset, wrapLength + offset));
+ wrappedLine.append(newLineStr);
+ offset += wrapLength;
+ } else {
+ // do not wrap really long word, just extend beyond limit
+ matcher = patternToWrapOn.matcher(str.substring(offset + wrapLength));
+ if (matcher.find()) {
+ spaceToWrapAt = matcher.start() + offset + wrapLength;
+ }
+
+ if (spaceToWrapAt >= 0) {
+ wrappedLine.append(str.substring(offset, spaceToWrapAt));
+ wrappedLine.append(newLineStr);
+ offset = spaceToWrapAt + 1;
+ } else {
+ wrappedLine.append(str.substring(offset));
+ offset = inputLineLength;
+ }
+ }
+ }
+ }
+
+ // Whatever is left in line is short enough to just pass through
+ wrappedLine.append(str.substring(offset));
+
+ return wrappedLine.toString();
+ }
+
+ // Capitalizing
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Capitalizes all the whitespace separated words in a String.
+ * Only the first character of each word is changed. To convert the
+ * rest of each word to lowercase at the same time,
+ * use {@link #capitalizeFully(String)}.</p>
+ *
+ * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.
+ * A <code>null</code> input String returns <code>null</code>.
+ * Capitalization uses the Unicode title case, normally equivalent to
+ * upper case.</p>
+ *
+ * <pre>
+ * WordUtils.capitalize(null) = null
+ * WordUtils.capitalize("") = ""
+ * WordUtils.capitalize("i am FINE") = "I Am FINE"
+ * </pre>
+ *
+ * @param str the String to capitalize, may be null
+ * @return capitalized String, <code>null</code> if null String input
+ * @see #uncapitalize(String)
+ * @see #capitalizeFully(String)
+ */
+ public static String capitalize(final String str) {
+ return capitalize(str, null);
+ }
+
+ /**
+ * <p>Capitalizes all the delimiter separated words in a String.
+ * Only the first character of each word is changed. To convert the
+ * rest of each word to lowercase at the same time,
+ * use {@link #capitalizeFully(String, char[])}.</p>
+ *
+ * <p>The delimiters represent a set of characters understood to separate words.
+ * The first string character and the first non-delimiter character after a
+ * delimiter will be capitalized. </p>
+ *
+ * <p>A <code>null</code> input String returns <code>null</code>.
+ * Capitalization uses the Unicode title case, normally equivalent to
+ * upper case.</p>
+ *
+ * <pre>
+ * WordUtils.capitalize(null, *) = null
+ * WordUtils.capitalize("", *) = ""
+ * WordUtils.capitalize(*, new char[0]) = *
+ * WordUtils.capitalize("i am fine", null) = "I Am Fine"
+ * WordUtils.capitalize("i aM.fine", {'.'}) = "I aM.Fine"
+ * </pre>
+ *
+ * @param str the String to capitalize, may be null
+ * @param delimiters set of characters to determine capitalization, null means whitespace
+ * @return capitalized String, <code>null</code> if null String input
+ * @see #uncapitalize(String)
+ * @see #capitalizeFully(String)
+ * @since 2.1
+ */
+ public static String capitalize(final String str, final char... delimiters) {
+ final int delimLen = delimiters == null ? -1 : delimiters.length;
+ if (str == null || str.length() == 0 || delimLen == 0) {
+ return str;
+ }
+ final char[] buffer = str.toCharArray();
+ boolean capitalizeNext = true;
+ for (int i = 0; i < buffer.length; i++) {
+ final char ch = buffer[i];
+ if (isDelimiter(ch, delimiters)) {
+ capitalizeNext = true;
+ } else if (capitalizeNext) {
+ buffer[i] = Character.toTitleCase(ch);
+ capitalizeNext = false;
+ }
+ }
+ return new String(buffer);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Converts all the whitespace separated words in a String into capitalized words,
+ * that is each word is made up of a titlecase character and then a series of
+ * lowercase characters. </p>
+ *
+ * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.
+ * A <code>null</code> input String returns <code>null</code>.
+ * Capitalization uses the Unicode title case, normally equivalent to
+ * upper case.</p>
+ *
+ * <pre>
+ * WordUtils.capitalizeFully(null) = null
+ * WordUtils.capitalizeFully("") = ""
+ * WordUtils.capitalizeFully("i am FINE") = "I Am Fine"
+ * </pre>
+ *
+ * @param str the String to capitalize, may be null
+ * @return capitalized String, <code>null</code> if null String input
+ */
+ public static String capitalizeFully(final String str) {
+ return capitalizeFully(str, null);
+ }
+
+ /**
+ * <p>Converts all the delimiter separated words in a String into capitalized words,
+ * that is each word is made up of a titlecase character and then a series of
+ * lowercase characters. </p>
+ *
+ * <p>The delimiters represent a set of characters understood to separate words.
+ * The first string character and the first non-delimiter character after a
+ * delimiter will be capitalized. </p>
+ *
+ * <p>A <code>null</code> input String returns <code>null</code>.
+ * Capitalization uses the Unicode title case, normally equivalent to
+ * upper case.</p>
+ *
+ * <pre>
+ * WordUtils.capitalizeFully(null, *) = null
+ * WordUtils.capitalizeFully("", *) = ""
+ * WordUtils.capitalizeFully(*, null) = *
+ * WordUtils.capitalizeFully(*, new char[0]) = *
+ * WordUtils.capitalizeFully("i aM.fine", {'.'}) = "I am.Fine"
+ * </pre>
+ *
+ * @param str the String to capitalize, may be null
+ * @param delimiters set of characters to determine capitalization, null means whitespace
+ * @return capitalized String, <code>null</code> if null String input
+ * @since 2.1
+ */
+ public static String capitalizeFully(String str, final char... delimiters) {
+ final int delimLen = delimiters == null ? -1 : delimiters.length;
+ if (str == null || str.length() == 0 || delimLen == 0) {
+ return str;
+ }
+ str = str.toLowerCase();
+ return capitalize(str, delimiters);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Uncapitalizes all the whitespace separated words in a String.
+ * Only the first character of each word is changed.</p>
+ *
+ * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.
+ * A <code>null</code> input String returns <code>null</code>.</p>
+ *
+ * <pre>
+ * WordUtils.uncapitalize(null) = null
+ * WordUtils.uncapitalize("") = ""
+ * WordUtils.uncapitalize("I Am FINE") = "i am fINE"
+ * </pre>
+ *
+ * @param str the String to uncapitalize, may be null
+ * @return uncapitalized String, <code>null</code> if null String input
+ * @see #capitalize(String)
+ */
+ public static String uncapitalize(final String str) {
+ return uncapitalize(str, null);
+ }
+
+ /**
+ * <p>Uncapitalizes all the whitespace separated words in a String.
+ * Only the first character of each word is changed.</p>
+ *
+ * <p>The delimiters represent a set of characters understood to separate words.
+ * The first string character and the first non-delimiter character after a
+ * delimiter will be uncapitalized. </p>
+ *
+ * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.
+ * A <code>null</code> input String returns <code>null</code>.</p>
+ *
+ * <pre>
+ * WordUtils.uncapitalize(null, *) = null
+ * WordUtils.uncapitalize("", *) = ""
+ * WordUtils.uncapitalize(*, null) = *
+ * WordUtils.uncapitalize(*, new char[0]) = *
+ * WordUtils.uncapitalize("I AM.FINE", {'.'}) = "i AM.fINE"
+ * </pre>
+ *
+ * @param str the String to uncapitalize, may be null
+ * @param delimiters set of characters to determine uncapitalization, null means whitespace
+ * @return uncapitalized String, <code>null</code> if null String input
+ * @see #capitalize(String)
+ * @since 2.1
+ */
+ public static String uncapitalize(final String str, final char... delimiters) {
+ final int delimLen = delimiters == null ? -1 : delimiters.length;
+ if (str == null || str.length() == 0 || delimLen == 0) {
+ return str;
+ }
+ final char[] buffer = str.toCharArray();
+ boolean uncapitalizeNext = true;
+ for (int i = 0; i < buffer.length; i++) {
+ final char ch = buffer[i];
+ if (isDelimiter(ch, delimiters)) {
+ uncapitalizeNext = true;
+ } else if (uncapitalizeNext) {
+ buffer[i] = Character.toLowerCase(ch);
+ uncapitalizeNext = false;
+ }
+ }
+ return new String(buffer);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Swaps the case of a String using a word based algorithm.</p>
+ *
+ * <ul>
+ * <li>Upper case character converts to Lower case</li>
+ * <li>Title case character converts to Lower case</li>
+ * <li>Lower case character after Whitespace or at start converts to Title case</li>
+ * <li>Other Lower case character converts to Upper case</li>
+ * </ul>
+ *
+ * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.
+ * A <code>null</code> input String returns <code>null</code>.</p>
+ *
+ * <pre>
+ * StringUtils.swapCase(null) = null
+ * StringUtils.swapCase("") = ""
+ * StringUtils.swapCase("The dog has a BONE") = "tHE DOG HAS A bone"
+ * </pre>
+ *
+ * @param str the String to swap case, may be null
+ * @return the changed String, <code>null</code> if null String input
+ */
+ public static String swapCase(final String str) {
+ if (str == null || str.length() == 0) {
+ return str;
+ }
+ final char[] buffer = str.toCharArray();
+
+ boolean whitespace = true;
+
+ for (int i = 0; i < buffer.length; i++) {
+ final char ch = buffer[i];
+ if (Character.isUpperCase(ch)) {
+ buffer[i] = Character.toLowerCase(ch);
+ whitespace = false;
+ } else if (Character.isTitleCase(ch)) {
+ buffer[i] = Character.toLowerCase(ch);
+ whitespace = false;
+ } else if (Character.isLowerCase(ch)) {
+ if (whitespace) {
+ buffer[i] = Character.toTitleCase(ch);
+ whitespace = false;
+ } else {
+ buffer[i] = Character.toUpperCase(ch);
+ }
+ } else {
+ whitespace = Character.isWhitespace(ch);
+ }
+ }
+ return new String(buffer);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Extracts the initial characters from each word in the String.</p>
+ *
+ * <p>All first characters after whitespace are returned as a new string.
+ * Their case is not changed.</p>
+ *
+ * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.
+ * A <code>null</code> input String returns <code>null</code>.</p>
+ *
+ * <pre>
+ * WordUtils.initials(null) = null
+ * WordUtils.initials("") = ""
+ * WordUtils.initials("Ben John Lee") = "BJL"
+ * WordUtils.initials("Ben J.Lee") = "BJ"
+ * </pre>
+ *
+ * @param str the String to get initials from, may be null
+ * @return String of initial letters, <code>null</code> if null String input
+ * @see #initials(String,char[])
+ * @since 2.2
+ */
+ public static String initials(final String str) {
+ return initials(str, null);
+ }
+
+ /**
+ * <p>Extracts the initial characters from each word in the String.</p>
+ *
+ * <p>All first characters after the defined delimiters are returned as a new string.
+ * Their case is not changed.</p>
+ *
+ * <p>If the delimiters array is null, then Whitespace is used.
+ * Whitespace is defined by {@link Character#isWhitespace(char)}.
+ * A <code>null</code> input String returns <code>null</code>.
+ * An empty delimiter array returns an empty String.</p>
+ *
+ * <pre>
+ * WordUtils.initials(null, *) = null
+ * WordUtils.initials("", *) = ""
+ * WordUtils.initials("Ben John Lee", null) = "BJL"
+ * WordUtils.initials("Ben J.Lee", null) = "BJ"
+ * WordUtils.initials("Ben J.Lee", [' ','.']) = "BJL"
+ * WordUtils.initials(*, new char[0]) = ""
+ * </pre>
+ *
+ * @param str the String to get initials from, may be null
+ * @param delimiters set of characters to determine words, null means whitespace
+ * @return String of initial characters, <code>null</code> if null String input
+ * @see #initials(String)
+ * @since 2.2
+ */
+ public static String initials(final String str, final char... delimiters) {
+ if (str == null || str.length() == 0) {
+ return str;
+ }
+ if (delimiters != null && delimiters.length == 0) {
+ return "";
+ }
+ final int strLen = str.length();
+ final char[] buf = new char[strLen / 2 + 1];
+ int count = 0;
+ boolean lastWasGap = true;
+ for (int i = 0; i < strLen; i++) {
+ final char ch = str.charAt(i);
+
+ if (isDelimiter(ch, delimiters)) {
+ lastWasGap = true;
+ } else if (lastWasGap) {
+ buf[count++] = ch;
+ lastWasGap = false;
+ } else {
+ continue; // ignore ch
+ }
+ }
+ return new String(buf, 0, count);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * <p>Checks if the String contains all words in the given array.</p>
+ *
+ * <p>
+ * A {@code null} String will return {@code false}. A {@code null}, zero
+ * length search array or if one element of array is null will return {@code false}.
+ * </p>
+ *
+ * <pre>
+ * WordUtils.containsAllWords(null, *) = false
+ * WordUtils.containsAllWords("", *) = false
+ * WordUtils.containsAllWords(*, null) = false
+ * WordUtils.containsAllWords(*, []) = false
+ * WordUtils.containsAllWords("abcd", "ab", "cd") = false
+ * WordUtils.containsAllWords("abc def", "def", "abc") = true
+ * </pre>
+ *
+ *
+ * @param word The CharSequence to check, may be null
+ * @param words The array of String words to search for, may be null
+ * @return {@code true} if all search words are found, {@code false} otherwise
+ * @since 3.5
+ */
+ public static boolean containsAllWords(final CharSequence word, final CharSequence... words) {
+ if (word == null || word.length() == 0 || words == null || Array.getLength(words) == 0) {
+ return false;
+ }
+ for (final CharSequence w : words) {
+ if (w == null || w.length() == 0 || String.valueOf(w).trim().length() == 0 ) {
+ return false;
+ }
+ final Pattern p = Pattern.compile(".*\\b" + w + "\\b.*");
+ if (!p.matcher(word).matches()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Is the character a delimiter.
+ *
+ * @param ch the character to check
+ * @param delimiters the delimiters
+ * @return true if it is a delimiter
+ */
+ private static boolean isDelimiter(final char ch, final char[] delimiters) {
+ if (delimiters == null) {
+ return Character.isWhitespace(ch);
+ }
+ for (final char delimiter : delimiters) {
+ if (ch == delimiter) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/commons-text/blob/6f24aa45/src/main/java/org/apache/commons/text/translate/AggregateTranslator.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/translate/AggregateTranslator.java b/src/main/java/org/apache/commons/text/translate/AggregateTranslator.java
index d8b50e8..d209b3c 100644
--- a/src/main/java/org/apache/commons/text/translate/AggregateTranslator.java
+++ b/src/main/java/org/apache/commons/text/translate/AggregateTranslator.java
@@ -22,8 +22,6 @@ import java.io.Writer;
/**
* Executes a sequence of translators one after the other. Execution ends whenever
* the first translator consumes codepoints from the input.
- *
- * @since 3.0
*/
public class AggregateTranslator extends CharSequenceTranslator {
http://git-wip-us.apache.org/repos/asf/commons-text/blob/6f24aa45/src/main/java/org/apache/commons/text/translate/CharSequenceTranslator.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/translate/CharSequenceTranslator.java b/src/main/java/org/apache/commons/text/translate/CharSequenceTranslator.java
index 16ec02f..0fed939 100644
--- a/src/main/java/org/apache/commons/text/translate/CharSequenceTranslator.java
+++ b/src/main/java/org/apache/commons/text/translate/CharSequenceTranslator.java
@@ -25,8 +25,6 @@ import java.util.Locale;
* An API for translating text.
* Its core use is to escape and unescape text. Because escaping and unescaping
* is completely contextual, the API does not present two separate signatures.
- *
- * @since 3.0
*/
public abstract class CharSequenceTranslator {
@@ -114,8 +112,8 @@ public abstract class CharSequenceTranslator {
* @param translators CharSequenceTranslator array of translators to merge with this one
* @return CharSequenceTranslator merging this translator with the others
*/
- public final org.apache.commons.text.translate.CharSequenceTranslator with(final org.apache.commons.text.translate.CharSequenceTranslator... translators) {
- final org.apache.commons.text.translate.CharSequenceTranslator[] newArray = new org.apache.commons.text.translate.CharSequenceTranslator[translators.length + 1];
+ public final CharSequenceTranslator with(final CharSequenceTranslator... translators) {
+ final CharSequenceTranslator[] newArray = new CharSequenceTranslator[translators.length + 1];
newArray[0] = this;
System.arraycopy(translators, 0, newArray, 1, translators.length);
return new AggregateTranslator(newArray);
http://git-wip-us.apache.org/repos/asf/commons-text/blob/6f24aa45/src/main/java/org/apache/commons/text/translate/CodePointTranslator.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/translate/CodePointTranslator.java b/src/main/java/org/apache/commons/text/translate/CodePointTranslator.java
index cac3d8f..c63165c 100644
--- a/src/main/java/org/apache/commons/text/translate/CodePointTranslator.java
+++ b/src/main/java/org/apache/commons/text/translate/CodePointTranslator.java
@@ -22,8 +22,6 @@ import java.io.Writer;
/**
* Helper subclass to CharSequenceTranslator to allow for translations that
* will replace up to one character at a time.
- *
- * @since 3.0
*/
public abstract class CodePointTranslator extends CharSequenceTranslator {
http://git-wip-us.apache.org/repos/asf/commons-text/blob/6f24aa45/src/main/java/org/apache/commons/text/translate/EntityArrays.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/translate/EntityArrays.java b/src/main/java/org/apache/commons/text/translate/EntityArrays.java
index 5c7c4e3..99626e6 100644
--- a/src/main/java/org/apache/commons/text/translate/EntityArrays.java
+++ b/src/main/java/org/apache/commons/text/translate/EntityArrays.java
@@ -20,8 +20,6 @@ package org.apache.commons.text.translate;
* Class holding various entity data for HTML and XML - generally for use with
* the LookupTranslator.
* All arrays are of length [*][2].
- *
- * @since 3.0
*/
public class EntityArrays {
http://git-wip-us.apache.org/repos/asf/commons-text/blob/6f24aa45/src/main/java/org/apache/commons/text/translate/JavaUnicodeEscaper.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/translate/JavaUnicodeEscaper.java b/src/main/java/org/apache/commons/text/translate/JavaUnicodeEscaper.java
index a9a186e..8c5b2b6 100644
--- a/src/main/java/org/apache/commons/text/translate/JavaUnicodeEscaper.java
+++ b/src/main/java/org/apache/commons/text/translate/JavaUnicodeEscaper.java
@@ -18,8 +18,6 @@ package org.apache.commons.text.translate;
/**
* Translates codepoints to their Unicode escaped value suitable for Java source.
- *
- * @since 3.2
*/
public class JavaUnicodeEscaper extends UnicodeEscaper {
@@ -32,7 +30,7 @@ public class JavaUnicodeEscaper extends UnicodeEscaper {
* above which to escape
* @return the newly created {@code UnicodeEscaper} instance
*/
- public static org.apache.commons.text.translate.JavaUnicodeEscaper above(final int codepoint) {
+ public static JavaUnicodeEscaper above(final int codepoint) {
return outsideOf(0, codepoint);
}
@@ -45,7 +43,7 @@ public class JavaUnicodeEscaper extends UnicodeEscaper {
* below which to escape
* @return the newly created {@code UnicodeEscaper} instance
*/
- public static org.apache.commons.text.translate.JavaUnicodeEscaper below(final int codepoint) {
+ public static JavaUnicodeEscaper below(final int codepoint) {
return outsideOf(codepoint, Integer.MAX_VALUE);
}
@@ -60,8 +58,8 @@ public class JavaUnicodeEscaper extends UnicodeEscaper {
* below which to escape
* @return the newly created {@code UnicodeEscaper} instance
*/
- public static org.apache.commons.text.translate.JavaUnicodeEscaper between(final int codepointLow, final int codepointHigh) {
- return new org.apache.commons.text.translate.JavaUnicodeEscaper(codepointLow, codepointHigh, true);
+ public static JavaUnicodeEscaper between(final int codepointLow, final int codepointHigh) {
+ return new JavaUnicodeEscaper(codepointLow, codepointHigh, true);
}
/**
@@ -75,8 +73,8 @@ public class JavaUnicodeEscaper extends UnicodeEscaper {
* above which to escape
* @return the newly created {@code UnicodeEscaper} instance
*/
- public static org.apache.commons.text.translate.JavaUnicodeEscaper outsideOf(final int codepointLow, final int codepointHigh) {
- return new org.apache.commons.text.translate.JavaUnicodeEscaper(codepointLow, codepointHigh, false);
+ public static JavaUnicodeEscaper outsideOf(final int codepointLow, final int codepointHigh) {
+ return new JavaUnicodeEscaper(codepointLow, codepointHigh, false);
}
/**
http://git-wip-us.apache.org/repos/asf/commons-text/blob/6f24aa45/src/main/java/org/apache/commons/text/translate/LookupTranslator.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/translate/LookupTranslator.java b/src/main/java/org/apache/commons/text/translate/LookupTranslator.java
index 614c86e..f73f312 100644
--- a/src/main/java/org/apache/commons/text/translate/LookupTranslator.java
+++ b/src/main/java/org/apache/commons/text/translate/LookupTranslator.java
@@ -23,8 +23,6 @@ import java.util.HashSet;
/**
* Translates a value using a lookup table.
- *
- * @since 3.0
*/
public class LookupTranslator extends CharSequenceTranslator {
http://git-wip-us.apache.org/repos/asf/commons-text/blob/6f24aa45/src/main/java/org/apache/commons/text/translate/NumericEntityEscaper.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/translate/NumericEntityEscaper.java b/src/main/java/org/apache/commons/text/translate/NumericEntityEscaper.java
index a3bf24b..3e4bbd0 100644
--- a/src/main/java/org/apache/commons/text/translate/NumericEntityEscaper.java
+++ b/src/main/java/org/apache/commons/text/translate/NumericEntityEscaper.java
@@ -21,8 +21,6 @@ import java.io.Writer;
/**
* Translates codepoints to their XML numeric entity escaped value.
- *
- * @since 3.0
*/
public class NumericEntityEscaper extends CodePointTranslator {
@@ -59,7 +57,7 @@ public class NumericEntityEscaper extends CodePointTranslator {
* @param codepoint below which to escape
* @return the newly created {@code NumericEntityEscaper} instance
*/
- public static org.apache.commons.text.translate.NumericEntityEscaper below(final int codepoint) {
+ public static NumericEntityEscaper below(final int codepoint) {
return outsideOf(codepoint, Integer.MAX_VALUE);
}
@@ -69,7 +67,7 @@ public class NumericEntityEscaper extends CodePointTranslator {
* @param codepoint above which to escape
* @return the newly created {@code NumericEntityEscaper} instance
*/
- public static org.apache.commons.text.translate.NumericEntityEscaper above(final int codepoint) {
+ public static NumericEntityEscaper above(final int codepoint) {
return outsideOf(0, codepoint);
}
@@ -80,8 +78,8 @@ public class NumericEntityEscaper extends CodePointTranslator {
* @param codepointHigh below which to escape
* @return the newly created {@code NumericEntityEscaper} instance
*/
- public static org.apache.commons.text.translate.NumericEntityEscaper between(final int codepointLow, final int codepointHigh) {
- return new org.apache.commons.text.translate.NumericEntityEscaper(codepointLow, codepointHigh, true);
+ public static NumericEntityEscaper between(final int codepointLow, final int codepointHigh) {
+ return new NumericEntityEscaper(codepointLow, codepointHigh, true);
}
/**
@@ -91,8 +89,8 @@ public class NumericEntityEscaper extends CodePointTranslator {
* @param codepointHigh above which to escape
* @return the newly created {@code NumericEntityEscaper} instance
*/
- public static org.apache.commons.text.translate.NumericEntityEscaper outsideOf(final int codepointLow, final int codepointHigh) {
- return new org.apache.commons.text.translate.NumericEntityEscaper(codepointLow, codepointHigh, false);
+ public static NumericEntityEscaper outsideOf(final int codepointLow, final int codepointHigh) {
+ return new NumericEntityEscaper(codepointLow, codepointHigh, false);
}
/**
http://git-wip-us.apache.org/repos/asf/commons-text/blob/6f24aa45/src/main/java/org/apache/commons/text/translate/NumericEntityUnescaper.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/translate/NumericEntityUnescaper.java b/src/main/java/org/apache/commons/text/translate/NumericEntityUnescaper.java
index cd4605d..6e2016e 100644
--- a/src/main/java/org/apache/commons/text/translate/NumericEntityUnescaper.java
+++ b/src/main/java/org/apache/commons/text/translate/NumericEntityUnescaper.java
@@ -26,8 +26,6 @@ import java.util.EnumSet;
* the specific codepoint.
*
* Note that the semi-colon is optional.
- *
- * @since 3.0
*/
public class NumericEntityUnescaper extends CharSequenceTranslator {
http://git-wip-us.apache.org/repos/asf/commons-text/blob/6f24aa45/src/main/java/org/apache/commons/text/translate/OctalUnescaper.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/translate/OctalUnescaper.java b/src/main/java/org/apache/commons/text/translate/OctalUnescaper.java
index 5801348..f9c0c14 100644
--- a/src/main/java/org/apache/commons/text/translate/OctalUnescaper.java
+++ b/src/main/java/org/apache/commons/text/translate/OctalUnescaper.java
@@ -26,8 +26,6 @@ import java.io.Writer;
*
* Note that this currently only supports the viable range of octal for Java; namely
* 1 to 377. This is because parsing Java is the main use case.
- *
- * @since 3.0
*/
public class OctalUnescaper extends CharSequenceTranslator {
http://git-wip-us.apache.org/repos/asf/commons-text/blob/6f24aa45/src/main/java/org/apache/commons/text/translate/UnicodeEscaper.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/translate/UnicodeEscaper.java b/src/main/java/org/apache/commons/text/translate/UnicodeEscaper.java
index 398a0e9..0c90f6d 100644
--- a/src/main/java/org/apache/commons/text/translate/UnicodeEscaper.java
+++ b/src/main/java/org/apache/commons/text/translate/UnicodeEscaper.java
@@ -21,8 +21,6 @@ import java.io.Writer;
/**
* Translates codepoints to their Unicode escaped value.
- *
- * @since 3.0
*/
public class UnicodeEscaper extends CodePointTranslator {
@@ -59,7 +57,7 @@ public class UnicodeEscaper extends CodePointTranslator {
* @param codepoint below which to escape
* @return the newly created {@code UnicodeEscaper} instance
*/
- public static org.apache.commons.text.translate.UnicodeEscaper below(final int codepoint) {
+ public static UnicodeEscaper below(final int codepoint) {
return outsideOf(codepoint, Integer.MAX_VALUE);
}
@@ -69,7 +67,7 @@ public class UnicodeEscaper extends CodePointTranslator {
* @param codepoint above which to escape
* @return the newly created {@code UnicodeEscaper} instance
*/
- public static org.apache.commons.text.translate.UnicodeEscaper above(final int codepoint) {
+ public static UnicodeEscaper above(final int codepoint) {
return outsideOf(0, codepoint);
}
@@ -80,8 +78,8 @@ public class UnicodeEscaper extends CodePointTranslator {
* @param codepointHigh above which to escape
* @return the newly created {@code UnicodeEscaper} instance
*/
- public static org.apache.commons.text.translate.UnicodeEscaper outsideOf(final int codepointLow, final int codepointHigh) {
- return new org.apache.commons.text.translate.UnicodeEscaper(codepointLow, codepointHigh, false);
+ public static UnicodeEscaper outsideOf(final int codepointLow, final int codepointHigh) {
+ return new UnicodeEscaper(codepointLow, codepointHigh, false);
}
/**
@@ -91,8 +89,8 @@ public class UnicodeEscaper extends CodePointTranslator {
* @param codepointHigh below which to escape
* @return the newly created {@code UnicodeEscaper} instance
*/
- public static org.apache.commons.text.translate.UnicodeEscaper between(final int codepointLow, final int codepointHigh) {
- return new org.apache.commons.text.translate.UnicodeEscaper(codepointLow, codepointHigh, true);
+ public static UnicodeEscaper between(final int codepointLow, final int codepointHigh) {
+ return new UnicodeEscaper(codepointLow, codepointHigh, true);
}
/**
http://git-wip-us.apache.org/repos/asf/commons-text/blob/6f24aa45/src/main/java/org/apache/commons/text/translate/UnicodeUnescaper.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/translate/UnicodeUnescaper.java b/src/main/java/org/apache/commons/text/translate/UnicodeUnescaper.java
index b92f421..e8fda68 100644
--- a/src/main/java/org/apache/commons/text/translate/UnicodeUnescaper.java
+++ b/src/main/java/org/apache/commons/text/translate/UnicodeUnescaper.java
@@ -23,8 +23,6 @@ import java.io.Writer;
* Translates escaped Unicode values of the form \\u+\d\d\d\d back to
* Unicode. It supports multiple 'u' characters and will work with or
* without the +.
- *
- * @since 3.0
*/
public class UnicodeUnescaper extends CharSequenceTranslator {
http://git-wip-us.apache.org/repos/asf/commons-text/blob/6f24aa45/src/main/java/org/apache/commons/text/translate/package-info.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/translate/package-info.java b/src/main/java/org/apache/commons/text/translate/package-info.java
index 722d7c9..3ef4d48 100644
--- a/src/main/java/org/apache/commons/text/translate/package-info.java
+++ b/src/main/java/org/apache/commons/text/translate/package-info.java
@@ -18,7 +18,5 @@
* <p> An API for creating text translation routines from a set of smaller building blocks. Initially created to make it
* possible for the user to customize the rules in the StringEscapeUtils class.</p>
* <p>These classes are immutable, and therefore thread-safe.</p>
- *
- * @since 3.0
*/
package org.apache.commons.text.translate;
http://git-wip-us.apache.org/repos/asf/commons-text/blob/6f24aa45/src/test/java/org/apache/commons/text/CompositeFormatTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/text/CompositeFormatTest.java b/src/test/java/org/apache/commons/text/CompositeFormatTest.java
new file mode 100644
index 0000000..e9b8064
--- /dev/null
+++ b/src/test/java/org/apache/commons/text/CompositeFormatTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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 org.junit.Test;
+import static org.junit.Assert.*;
+import java.text.FieldPosition;
+import java.text.Format;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.util.Locale;
+
+/**
+ * Unit tests for {@link org.apache.commons.text.CompositeFormat}.
+ */
+public class CompositeFormatTest {
+
+ /**
+ * Ensures that the parse/format separation is correctly maintained.
+ */
+ @Test
+ public void testCompositeFormat() {
+
+ final Format parser = new Format() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public Object parseObject(final String source, final ParsePosition pos) {
+ return null; // do nothing
+ }
+ };
+
+ final Format formatter = new Format() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) {
+ return null; // do nothing
+ }
+
+ @Override
+ public Object parseObject(final String source, final ParsePosition pos) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+ };
+
+ final CompositeFormat composite = new CompositeFormat(parser, formatter);
+
+ composite.parseObject("", null);
+ composite.format(new Object(), new StringBuffer(), null);
+ assertEquals( "Parser get method incorrectly implemented", parser, composite.getParser() );
+ assertEquals( "Formatter get method incorrectly implemented", formatter, composite.getFormatter() );
+ }
+
+ @Test
+ public void testUsage() throws Exception {
+ final Format f1 = new SimpleDateFormat("MMddyyyy", Locale.ENGLISH);
+ final Format f2 = new SimpleDateFormat("MMMM d, yyyy", Locale.ENGLISH);
+ final CompositeFormat c = new CompositeFormat(f1, f2);
+ final String testString = "January 3, 2005";
+ assertEquals(testString, c.format(c.parseObject("01032005")));
+ assertEquals(testString, c.reformat("01032005"));
+ }
+
+}
[05/14] [text] TEXT-23: Adding remainder of the o.a.c.l.text package
Posted by ch...@apache.org.
http://git-wip-us.apache.org/repos/asf/commons-text/blob/6f24aa45/src/test/java/org/apache/commons/text/StrBuilderTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/text/StrBuilderTest.java b/src/test/java/org/apache/commons/text/StrBuilderTest.java
new file mode 100644
index 0000000..1920984
--- /dev/null
+++ b/src/test/java/org/apache/commons/text/StrBuilderTest.java
@@ -0,0 +1,2009 @@
+/*
+ * 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 org.junit.Test;
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.nio.CharBuffer;
+import java.util.Arrays;
+
+import org.apache.commons.lang3.ArrayUtils;
+
+/**
+ * Unit tests for {@link StrBuilder}.
+ */
+public class StrBuilderTest {
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testConstructors() {
+ final StrBuilder sb0 = new StrBuilder();
+ assertEquals(32, sb0.capacity());
+ assertEquals(0, sb0.length());
+ assertEquals(0, sb0.size());
+
+ final StrBuilder sb1 = new StrBuilder(32);
+ assertEquals(32, sb1.capacity());
+ assertEquals(0, sb1.length());
+ assertEquals(0, sb1.size());
+
+ final StrBuilder sb2 = new StrBuilder(0);
+ assertEquals(32, sb2.capacity());
+ assertEquals(0, sb2.length());
+ assertEquals(0, sb2.size());
+
+ final StrBuilder sb3 = new StrBuilder(-1);
+ assertEquals(32, sb3.capacity());
+ assertEquals(0, sb3.length());
+ assertEquals(0, sb3.size());
+
+ final StrBuilder sb4 = new StrBuilder(1);
+ assertEquals(1, sb4.capacity());
+ assertEquals(0, sb4.length());
+ assertEquals(0, sb4.size());
+
+ final StrBuilder sb5 = new StrBuilder((String) null);
+ assertEquals(32, sb5.capacity());
+ assertEquals(0, sb5.length());
+ assertEquals(0, sb5.size());
+
+ final StrBuilder sb6 = new StrBuilder("");
+ assertEquals(32, sb6.capacity());
+ assertEquals(0, sb6.length());
+ assertEquals(0, sb6.size());
+
+ final StrBuilder sb7 = new StrBuilder("foo");
+ assertEquals(35, sb7.capacity());
+ assertEquals(3, sb7.length());
+ assertEquals(3, sb7.size());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testChaining() {
+ final StrBuilder sb = new StrBuilder();
+ assertSame(sb, sb.setNewLineText(null));
+ assertSame(sb, sb.setNullText(null));
+ assertSame(sb, sb.setLength(1));
+ assertSame(sb, sb.setCharAt(0, 'a'));
+ assertSame(sb, sb.ensureCapacity(0));
+ assertSame(sb, sb.minimizeCapacity());
+ assertSame(sb, sb.clear());
+ assertSame(sb, sb.reverse());
+ assertSame(sb, sb.trim());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testReadFromReader() throws Exception {
+ String s = "";
+ for (int i = 0; i < 100; ++i) {
+ final StrBuilder sb = new StrBuilder();
+ final int len = sb.readFrom(new StringReader(s));
+
+ assertEquals(s.length(), len);
+ assertEquals(s, sb.toString());
+
+ s += Integer.toString(i);
+ }
+ }
+
+ @Test
+ public void testReadFromReaderAppendsToEnd() throws Exception {
+ final StrBuilder sb = new StrBuilder("Test");
+ sb.readFrom(new StringReader(" 123"));
+ assertEquals("Test 123", sb.toString());
+ }
+
+ @Test
+ public void testReadFromCharBuffer() throws Exception {
+ String s = "";
+ for (int i = 0; i < 100; ++i) {
+ final StrBuilder sb = new StrBuilder();
+ final int len = sb.readFrom(CharBuffer.wrap(s));
+
+ assertEquals(s.length(), len);
+ assertEquals(s, sb.toString());
+
+ s += Integer.toString(i);
+ }
+ }
+
+ @Test
+ public void testReadFromCharBufferAppendsToEnd() throws Exception {
+ final StrBuilder sb = new StrBuilder("Test");
+ sb.readFrom(CharBuffer.wrap(" 123"));
+ assertEquals("Test 123", sb.toString());
+ }
+
+ @Test
+ public void testReadFromReadable() throws Exception {
+ String s = "";
+ for (int i = 0; i < 100; ++i) {
+ final StrBuilder sb = new StrBuilder();
+ final int len = sb.readFrom(new MockReadable(s));
+
+ assertEquals(s.length(), len);
+ assertEquals(s, sb.toString());
+
+ s += Integer.toString(i);
+ }
+ }
+
+ @Test
+ public void testReadFromReadableAppendsToEnd() throws Exception {
+ final StrBuilder sb = new StrBuilder("Test");
+ sb.readFrom(new MockReadable(" 123"));
+ assertEquals("Test 123", sb.toString());
+ }
+
+ private static class MockReadable implements Readable {
+
+ private final CharBuffer src;
+
+ public MockReadable(final String src) {
+ this.src = CharBuffer.wrap(src);
+ }
+
+ @Override
+ public int read(final CharBuffer cb) throws IOException {
+ return src.read(cb);
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testGetSetNewLineText() {
+ final StrBuilder sb = new StrBuilder();
+ assertEquals(null, sb.getNewLineText());
+
+ sb.setNewLineText("#");
+ assertEquals("#", sb.getNewLineText());
+
+ sb.setNewLineText("");
+ assertEquals("", sb.getNewLineText());
+
+ sb.setNewLineText((String) null);
+ assertEquals(null, sb.getNewLineText());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testGetSetNullText() {
+ final StrBuilder sb = new StrBuilder();
+ assertEquals(null, sb.getNullText());
+
+ sb.setNullText("null");
+ assertEquals("null", sb.getNullText());
+
+ sb.setNullText("");
+ assertEquals(null, sb.getNullText());
+
+ sb.setNullText("NULL");
+ assertEquals("NULL", sb.getNullText());
+
+ sb.setNullText((String) null);
+ assertEquals(null, sb.getNullText());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testCapacityAndLength() {
+ final StrBuilder sb = new StrBuilder();
+ assertEquals(32, sb.capacity());
+ assertEquals(0, sb.length());
+ assertEquals(0, sb.size());
+ assertTrue(sb.isEmpty());
+
+ sb.minimizeCapacity();
+ assertEquals(0, sb.capacity());
+ assertEquals(0, sb.length());
+ assertEquals(0, sb.size());
+ assertTrue(sb.isEmpty());
+
+ sb.ensureCapacity(32);
+ assertTrue(sb.capacity() >= 32);
+ assertEquals(0, sb.length());
+ assertEquals(0, sb.size());
+ assertTrue(sb.isEmpty());
+
+ sb.append("foo");
+ assertTrue(sb.capacity() >= 32);
+ assertEquals(3, sb.length());
+ assertEquals(3, sb.size());
+ assertTrue(sb.isEmpty() == false);
+
+ sb.clear();
+ assertTrue(sb.capacity() >= 32);
+ assertEquals(0, sb.length());
+ assertEquals(0, sb.size());
+ assertTrue(sb.isEmpty());
+
+ sb.append("123456789012345678901234567890123");
+ assertTrue(sb.capacity() > 32);
+ assertEquals(33, sb.length());
+ assertEquals(33, sb.size());
+ assertTrue(sb.isEmpty() == false);
+
+ sb.ensureCapacity(16);
+ assertTrue(sb.capacity() > 16);
+ assertEquals(33, sb.length());
+ assertEquals(33, sb.size());
+ assertTrue(sb.isEmpty() == false);
+
+ sb.minimizeCapacity();
+ assertEquals(33, sb.capacity());
+ assertEquals(33, sb.length());
+ assertEquals(33, sb.size());
+ assertTrue(sb.isEmpty() == false);
+
+ try {
+ sb.setLength(-1);
+ fail("setLength(-1) expected StringIndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ sb.setLength(33);
+ assertEquals(33, sb.capacity());
+ assertEquals(33, sb.length());
+ assertEquals(33, sb.size());
+ assertTrue(sb.isEmpty() == false);
+
+ sb.setLength(16);
+ assertTrue(sb.capacity() >= 16);
+ assertEquals(16, sb.length());
+ assertEquals(16, sb.size());
+ assertEquals("1234567890123456", sb.toString());
+ assertTrue(sb.isEmpty() == false);
+
+ sb.setLength(32);
+ assertTrue(sb.capacity() >= 32);
+ assertEquals(32, sb.length());
+ assertEquals(32, sb.size());
+ assertEquals("1234567890123456\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", sb.toString());
+ assertTrue(sb.isEmpty() == false);
+
+ sb.setLength(0);
+ assertTrue(sb.capacity() >= 32);
+ assertEquals(0, sb.length());
+ assertEquals(0, sb.size());
+ assertTrue(sb.isEmpty());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testLength() {
+ final StrBuilder sb = new StrBuilder();
+ assertEquals(0, sb.length());
+
+ sb.append("Hello");
+ assertEquals(5, sb.length());
+ }
+
+ @Test
+ public void testSetLength() {
+ final StrBuilder sb = new StrBuilder();
+ sb.append("Hello");
+ sb.setLength(2); // shorten
+ assertEquals("He", sb.toString());
+ sb.setLength(2); // no change
+ assertEquals("He", sb.toString());
+ sb.setLength(3); // lengthen
+ assertEquals("He\0", sb.toString());
+
+ try {
+ sb.setLength(-1);
+ fail("setLength(-1) expected StringIndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testCapacity() {
+ final StrBuilder sb = new StrBuilder();
+ assertEquals(sb.buffer.length, sb.capacity());
+
+ sb.append("HelloWorldHelloWorldHelloWorldHelloWorld");
+ assertEquals(sb.buffer.length, sb.capacity());
+ }
+
+ @Test
+ public void testEnsureCapacity() {
+ final StrBuilder sb = new StrBuilder();
+ sb.ensureCapacity(2);
+ assertTrue(sb.capacity() >= 2);
+
+ sb.ensureCapacity(-1);
+ assertTrue(sb.capacity() >= 0);
+
+ sb.append("HelloWorld");
+ sb.ensureCapacity(40);
+ assertTrue(sb.capacity() >= 40);
+ }
+
+ @Test
+ public void testMinimizeCapacity() {
+ final StrBuilder sb = new StrBuilder();
+ sb.minimizeCapacity();
+ assertEquals(0, sb.capacity());
+
+ sb.append("HelloWorld");
+ sb.minimizeCapacity();
+ assertEquals(10, sb.capacity());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testSize() {
+ final StrBuilder sb = new StrBuilder();
+ assertEquals(0, sb.size());
+
+ sb.append("Hello");
+ assertEquals(5, sb.size());
+ }
+
+ @Test
+ public void testIsEmpty() {
+ final StrBuilder sb = new StrBuilder();
+ assertTrue(sb.isEmpty());
+
+ sb.append("Hello");
+ assertFalse(sb.isEmpty());
+
+ sb.clear();
+ assertTrue(sb.isEmpty());
+ }
+
+ @Test
+ public void testClear() {
+ final StrBuilder sb = new StrBuilder();
+ sb.append("Hello");
+ sb.clear();
+ assertEquals(0, sb.length());
+ assertTrue(sb.buffer.length >= 5);
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testCharAt() {
+ final StrBuilder sb = new StrBuilder();
+ try {
+ sb.charAt(0);
+ fail("charAt(0) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+ try {
+ sb.charAt(-1);
+ fail("charAt(-1) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+ sb.append("foo");
+ assertEquals('f', sb.charAt(0));
+ assertEquals('o', sb.charAt(1));
+ assertEquals('o', sb.charAt(2));
+ try {
+ sb.charAt(-1);
+ fail("charAt(-1) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+ try {
+ sb.charAt(3);
+ fail("charAt(3) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testSetCharAt() {
+ final StrBuilder sb = new StrBuilder();
+ try {
+ sb.setCharAt(0, 'f');
+ fail("setCharAt(0,) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+ try {
+ sb.setCharAt(-1, 'f');
+ fail("setCharAt(-1,) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+ sb.append("foo");
+ sb.setCharAt(0, 'b');
+ sb.setCharAt(1, 'a');
+ sb.setCharAt(2, 'r');
+ try {
+ sb.setCharAt(3, '!');
+ fail("setCharAt(3,) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+ assertEquals("bar", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testDeleteCharAt() {
+ final StrBuilder sb = new StrBuilder("abc");
+ sb.deleteCharAt(0);
+ assertEquals("bc", sb.toString());
+
+ try {
+ sb.deleteCharAt(1000);
+ fail("Expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {}
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testToCharArray() {
+ final StrBuilder sb = new StrBuilder();
+ assertEquals(ArrayUtils.EMPTY_CHAR_ARRAY, sb.toCharArray());
+
+ char[] a = sb.toCharArray();
+ assertNotNull("toCharArray() result is null", a);
+ assertEquals("toCharArray() result is too large", 0, a.length);
+
+ sb.append("junit");
+ a = sb.toCharArray();
+ assertEquals("toCharArray() result incorrect length", 5, a.length);
+ assertTrue("toCharArray() result does not match", Arrays.equals("junit".toCharArray(), a));
+ }
+
+ @Test
+ public void testToCharArrayIntInt() {
+ final StrBuilder sb = new StrBuilder();
+ assertEquals(ArrayUtils.EMPTY_CHAR_ARRAY, sb.toCharArray(0, 0));
+
+ sb.append("junit");
+ char[] a = sb.toCharArray(0, 20); // too large test
+ assertEquals("toCharArray(int,int) result incorrect length", 5, a.length);
+ assertTrue("toCharArray(int,int) result does not match", Arrays.equals("junit".toCharArray(), a));
+
+ a = sb.toCharArray(0, 4);
+ assertEquals("toCharArray(int,int) result incorrect length", 4, a.length);
+ assertTrue("toCharArray(int,int) result does not match", Arrays.equals("juni".toCharArray(), a));
+
+ a = sb.toCharArray(0, 4);
+ assertEquals("toCharArray(int,int) result incorrect length", 4, a.length);
+ assertTrue("toCharArray(int,int) result does not match", Arrays.equals("juni".toCharArray(), a));
+
+ a = sb.toCharArray(0, 1);
+ assertNotNull("toCharArray(int,int) result is null", a);
+
+ try {
+ sb.toCharArray(-1, 5);
+ fail("no string index out of bound on -1");
+ } catch (final IndexOutOfBoundsException e) {
+ }
+
+ try {
+ sb.toCharArray(6, 5);
+ fail("no string index out of bound on -1");
+ } catch (final IndexOutOfBoundsException e) {
+ }
+ }
+
+ @Test
+ public void testGetChars ( ) {
+ final StrBuilder sb = new StrBuilder();
+
+ char[] input = new char[10];
+ char[] a = sb.getChars(input);
+ assertSame (input, a);
+ assertTrue(Arrays.equals(new char[10], a));
+
+ sb.append("junit");
+ a = sb.getChars(input);
+ assertSame(input, a);
+ assertTrue(Arrays.equals(new char[] {'j','u','n','i','t',0,0,0,0,0},a));
+
+ a = sb.getChars(null);
+ assertNotSame(input,a);
+ assertEquals(5,a.length);
+ assertTrue(Arrays.equals("junit".toCharArray(),a));
+
+ input = new char[5];
+ a = sb.getChars(input);
+ assertSame(input, a);
+
+ input = new char[4];
+ a = sb.getChars(input);
+ assertNotSame(input, a);
+ }
+
+ @Test
+ public void testGetCharsIntIntCharArrayInt( ) {
+ final StrBuilder sb = new StrBuilder();
+
+ sb.append("junit");
+ char[] a = new char[5];
+ sb.getChars(0,5,a,0);
+ assertTrue(Arrays.equals(new char[] {'j','u','n','i','t'},a));
+
+ a = new char[5];
+ sb.getChars(0,2,a,3);
+ assertTrue(Arrays.equals(new char[] {0,0,0,'j','u'},a));
+
+ try {
+ sb.getChars(-1,0,a,0);
+ fail("no exception");
+ }
+ catch (final IndexOutOfBoundsException e) {
+ }
+
+ try {
+ sb.getChars(0,-1,a,0);
+ fail("no exception");
+ }
+ catch (final IndexOutOfBoundsException e) {
+ }
+
+ try {
+ sb.getChars(0,20,a,0);
+ fail("no exception");
+ }
+ catch (final IndexOutOfBoundsException e) {
+ }
+
+ try {
+ sb.getChars(4,2,a,0);
+ fail("no exception");
+ }
+ catch (final IndexOutOfBoundsException e) {
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testDeleteIntInt() {
+ StrBuilder sb = new StrBuilder("abc");
+ sb.delete(0, 1);
+ assertEquals("bc", sb.toString());
+ sb.delete(1, 2);
+ assertEquals("b", sb.toString());
+ sb.delete(0, 1);
+ assertEquals("", sb.toString());
+ sb.delete(0, 1000);
+ assertEquals("", sb.toString());
+
+ try {
+ sb.delete(1, 2);
+ fail("Expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {}
+ try {
+ sb.delete(-1, 1);
+ fail("Expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {}
+
+ sb = new StrBuilder("anything");
+ try {
+ sb.delete(2, 1);
+ fail("Expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {}
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testDeleteAll_char() {
+ StrBuilder sb = new StrBuilder("abcbccba");
+ sb.deleteAll('X');
+ assertEquals("abcbccba", sb.toString());
+ sb.deleteAll('a');
+ assertEquals("bcbccb", sb.toString());
+ sb.deleteAll('c');
+ assertEquals("bbb", sb.toString());
+ sb.deleteAll('b');
+ assertEquals("", sb.toString());
+
+ sb = new StrBuilder("");
+ sb.deleteAll('b');
+ assertEquals("", sb.toString());
+ }
+
+ @Test
+ public void testDeleteFirst_char() {
+ StrBuilder sb = new StrBuilder("abcba");
+ sb.deleteFirst('X');
+ assertEquals("abcba", sb.toString());
+ sb.deleteFirst('a');
+ assertEquals("bcba", sb.toString());
+ sb.deleteFirst('c');
+ assertEquals("bba", sb.toString());
+ sb.deleteFirst('b');
+ assertEquals("ba", sb.toString());
+
+ sb = new StrBuilder("");
+ sb.deleteFirst('b');
+ assertEquals("", sb.toString());
+ }
+
+ // -----------------------------------------------------------------------
+ @Test
+ public void testDeleteAll_String() {
+ StrBuilder sb = new StrBuilder("abcbccba");
+ sb.deleteAll((String) null);
+ assertEquals("abcbccba", sb.toString());
+ sb.deleteAll("");
+ assertEquals("abcbccba", sb.toString());
+
+ sb.deleteAll("X");
+ assertEquals("abcbccba", sb.toString());
+ sb.deleteAll("a");
+ assertEquals("bcbccb", sb.toString());
+ sb.deleteAll("c");
+ assertEquals("bbb", sb.toString());
+ sb.deleteAll("b");
+ assertEquals("", sb.toString());
+
+ sb = new StrBuilder("abcbccba");
+ sb.deleteAll("bc");
+ assertEquals("acba", sb.toString());
+
+ sb = new StrBuilder("");
+ sb.deleteAll("bc");
+ assertEquals("", sb.toString());
+ }
+
+ @Test
+ public void testDeleteFirst_String() {
+ StrBuilder sb = new StrBuilder("abcbccba");
+ sb.deleteFirst((String) null);
+ assertEquals("abcbccba", sb.toString());
+ sb.deleteFirst("");
+ assertEquals("abcbccba", sb.toString());
+
+ sb.deleteFirst("X");
+ assertEquals("abcbccba", sb.toString());
+ sb.deleteFirst("a");
+ assertEquals("bcbccba", sb.toString());
+ sb.deleteFirst("c");
+ assertEquals("bbccba", sb.toString());
+ sb.deleteFirst("b");
+ assertEquals("bccba", sb.toString());
+
+ sb = new StrBuilder("abcbccba");
+ sb.deleteFirst("bc");
+ assertEquals("abccba", sb.toString());
+
+ sb = new StrBuilder("");
+ sb.deleteFirst("bc");
+ assertEquals("", sb.toString());
+ }
+
+ // -----------------------------------------------------------------------
+ @Test
+ public void testDeleteAll_StrMatcher() {
+ StrBuilder sb = new StrBuilder("A0xA1A2yA3");
+ sb.deleteAll((StrMatcher) null);
+ assertEquals("A0xA1A2yA3", sb.toString());
+ sb.deleteAll(A_NUMBER_MATCHER);
+ assertEquals("xy", sb.toString());
+
+ sb = new StrBuilder("Ax1");
+ sb.deleteAll(A_NUMBER_MATCHER);
+ assertEquals("Ax1", sb.toString());
+
+ sb = new StrBuilder("");
+ sb.deleteAll(A_NUMBER_MATCHER);
+ assertEquals("", sb.toString());
+ }
+
+ @Test
+ public void testDeleteFirst_StrMatcher() {
+ StrBuilder sb = new StrBuilder("A0xA1A2yA3");
+ sb.deleteFirst((StrMatcher) null);
+ assertEquals("A0xA1A2yA3", sb.toString());
+ sb.deleteFirst(A_NUMBER_MATCHER);
+ assertEquals("xA1A2yA3", sb.toString());
+
+ sb = new StrBuilder("Ax1");
+ sb.deleteFirst(A_NUMBER_MATCHER);
+ assertEquals("Ax1", sb.toString());
+
+ sb = new StrBuilder("");
+ sb.deleteFirst(A_NUMBER_MATCHER);
+ assertEquals("", sb.toString());
+ }
+
+ // -----------------------------------------------------------------------
+ @Test
+ public void testReplace_int_int_String() {
+ StrBuilder sb = new StrBuilder("abc");
+ sb.replace(0, 1, "d");
+ assertEquals("dbc", sb.toString());
+ sb.replace(0, 1, "aaa");
+ assertEquals("aaabc", sb.toString());
+ sb.replace(0, 3, "");
+ assertEquals("bc", sb.toString());
+ sb.replace(1, 2, (String) null);
+ assertEquals("b", sb.toString());
+ sb.replace(1, 1000, "text");
+ assertEquals("btext", sb.toString());
+ sb.replace(0, 1000, "text");
+ assertEquals("text", sb.toString());
+
+ sb = new StrBuilder("atext");
+ sb.replace(1, 1, "ny");
+ assertEquals("anytext", sb.toString());
+ try {
+ sb.replace(2, 1, "anything");
+ fail("Expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {}
+
+ sb = new StrBuilder();
+ try {
+ sb.replace(1, 2, "anything");
+ fail("Expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {}
+ try {
+ sb.replace(-1, 1, "anything");
+ fail("Expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {}
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testReplaceAll_char_char() {
+ final StrBuilder sb = new StrBuilder("abcbccba");
+ sb.replaceAll('x', 'y');
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceAll('a', 'd');
+ assertEquals("dbcbccbd", sb.toString());
+ sb.replaceAll('b', 'e');
+ assertEquals("dececced", sb.toString());
+ sb.replaceAll('c', 'f');
+ assertEquals("defeffed", sb.toString());
+ sb.replaceAll('d', 'd');
+ assertEquals("defeffed", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testReplaceFirst_char_char() {
+ final StrBuilder sb = new StrBuilder("abcbccba");
+ sb.replaceFirst('x', 'y');
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceFirst('a', 'd');
+ assertEquals("dbcbccba", sb.toString());
+ sb.replaceFirst('b', 'e');
+ assertEquals("decbccba", sb.toString());
+ sb.replaceFirst('c', 'f');
+ assertEquals("defbccba", sb.toString());
+ sb.replaceFirst('d', 'd');
+ assertEquals("defbccba", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testReplaceAll_String_String() {
+ StrBuilder sb = new StrBuilder("abcbccba");
+ sb.replaceAll((String) null, null);
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceAll((String) null, "anything");
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceAll("", null);
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceAll("", "anything");
+ assertEquals("abcbccba", sb.toString());
+
+ sb.replaceAll("x", "y");
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceAll("a", "d");
+ assertEquals("dbcbccbd", sb.toString());
+ sb.replaceAll("d", null);
+ assertEquals("bcbccb", sb.toString());
+ sb.replaceAll("cb", "-");
+ assertEquals("b-c-", sb.toString());
+
+ sb = new StrBuilder("abcba");
+ sb.replaceAll("b", "xbx");
+ assertEquals("axbxcxbxa", sb.toString());
+
+ sb = new StrBuilder("bb");
+ sb.replaceAll("b", "xbx");
+ assertEquals("xbxxbx", sb.toString());
+ }
+
+ @Test
+ public void testReplaceFirst_String_String() {
+ StrBuilder sb = new StrBuilder("abcbccba");
+ sb.replaceFirst((String) null, null);
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceFirst((String) null, "anything");
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceFirst("", null);
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceFirst("", "anything");
+ assertEquals("abcbccba", sb.toString());
+
+ sb.replaceFirst("x", "y");
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceFirst("a", "d");
+ assertEquals("dbcbccba", sb.toString());
+ sb.replaceFirst("d", null);
+ assertEquals("bcbccba", sb.toString());
+ sb.replaceFirst("cb", "-");
+ assertEquals("b-ccba", sb.toString());
+
+ sb = new StrBuilder("abcba");
+ sb.replaceFirst("b", "xbx");
+ assertEquals("axbxcba", sb.toString());
+
+ sb = new StrBuilder("bb");
+ sb.replaceFirst("b", "xbx");
+ assertEquals("xbxb", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testReplaceAll_StrMatcher_String() {
+ StrBuilder sb = new StrBuilder("abcbccba");
+ sb.replaceAll((StrMatcher) null, null);
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceAll((StrMatcher) null, "anything");
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceAll(StrMatcher.noneMatcher(), null);
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceAll(StrMatcher.noneMatcher(), "anything");
+ assertEquals("abcbccba", sb.toString());
+
+ sb.replaceAll(StrMatcher.charMatcher('x'), "y");
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceAll(StrMatcher.charMatcher('a'), "d");
+ assertEquals("dbcbccbd", sb.toString());
+ sb.replaceAll(StrMatcher.charMatcher('d'), null);
+ assertEquals("bcbccb", sb.toString());
+ sb.replaceAll(StrMatcher.stringMatcher("cb"), "-");
+ assertEquals("b-c-", sb.toString());
+
+ sb = new StrBuilder("abcba");
+ sb.replaceAll(StrMatcher.charMatcher('b'), "xbx");
+ assertEquals("axbxcxbxa", sb.toString());
+
+ sb = new StrBuilder("bb");
+ sb.replaceAll(StrMatcher.charMatcher('b'), "xbx");
+ assertEquals("xbxxbx", sb.toString());
+
+ sb = new StrBuilder("A1-A2A3-A4");
+ sb.replaceAll(A_NUMBER_MATCHER, "***");
+ assertEquals("***-******-***", sb.toString());
+
+ sb = new StrBuilder("Dear X, hello X.");
+ sb.replaceAll(StrMatcher.stringMatcher("X"), "012345678901234567");
+ assertEquals("Dear 012345678901234567, hello 012345678901234567.", sb.toString());
+ }
+
+ @Test
+ public void testReplaceFirst_StrMatcher_String() {
+ StrBuilder sb = new StrBuilder("abcbccba");
+ sb.replaceFirst((StrMatcher) null, null);
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceFirst((StrMatcher) null, "anything");
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceFirst(StrMatcher.noneMatcher(), null);
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceFirst(StrMatcher.noneMatcher(), "anything");
+ assertEquals("abcbccba", sb.toString());
+
+ sb.replaceFirst(StrMatcher.charMatcher('x'), "y");
+ assertEquals("abcbccba", sb.toString());
+ sb.replaceFirst(StrMatcher.charMatcher('a'), "d");
+ assertEquals("dbcbccba", sb.toString());
+ sb.replaceFirst(StrMatcher.charMatcher('d'), null);
+ assertEquals("bcbccba", sb.toString());
+ sb.replaceFirst(StrMatcher.stringMatcher("cb"), "-");
+ assertEquals("b-ccba", sb.toString());
+
+ sb = new StrBuilder("abcba");
+ sb.replaceFirst(StrMatcher.charMatcher('b'), "xbx");
+ assertEquals("axbxcba", sb.toString());
+
+ sb = new StrBuilder("bb");
+ sb.replaceFirst(StrMatcher.charMatcher('b'), "xbx");
+ assertEquals("xbxb", sb.toString());
+
+ sb = new StrBuilder("A1-A2A3-A4");
+ sb.replaceFirst(A_NUMBER_MATCHER, "***");
+ assertEquals("***-A2A3-A4", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testReplace_StrMatcher_String_int_int_int_VaryMatcher() {
+ StrBuilder sb = new StrBuilder("abcbccba");
+ sb.replace((StrMatcher) null, "x", 0, sb.length(), -1);
+ assertEquals("abcbccba", sb.toString());
+
+ sb.replace(StrMatcher.charMatcher('a'), "x", 0, sb.length(), -1);
+ assertEquals("xbcbccbx", sb.toString());
+
+ sb.replace(StrMatcher.stringMatcher("cb"), "x", 0, sb.length(), -1);
+ assertEquals("xbxcxx", sb.toString());
+
+ sb = new StrBuilder("A1-A2A3-A4");
+ sb.replace(A_NUMBER_MATCHER, "***", 0, sb.length(), -1);
+ assertEquals("***-******-***", sb.toString());
+
+ sb = new StrBuilder();
+ sb.replace(A_NUMBER_MATCHER, "***", 0, sb.length(), -1);
+ assertEquals("", sb.toString());
+ }
+
+ @Test
+ public void testReplace_StrMatcher_String_int_int_int_VaryReplace() {
+ StrBuilder sb = new StrBuilder("abcbccba");
+ sb.replace(StrMatcher.stringMatcher("cb"), "cb", 0, sb.length(), -1);
+ assertEquals("abcbccba", sb.toString());
+
+ sb = new StrBuilder("abcbccba");
+ sb.replace(StrMatcher.stringMatcher("cb"), "-", 0, sb.length(), -1);
+ assertEquals("ab-c-a", sb.toString());
+
+ sb = new StrBuilder("abcbccba");
+ sb.replace(StrMatcher.stringMatcher("cb"), "+++", 0, sb.length(), -1);
+ assertEquals("ab+++c+++a", sb.toString());
+
+ sb = new StrBuilder("abcbccba");
+ sb.replace(StrMatcher.stringMatcher("cb"), "", 0, sb.length(), -1);
+ assertEquals("abca", sb.toString());
+
+ sb = new StrBuilder("abcbccba");
+ sb.replace(StrMatcher.stringMatcher("cb"), null, 0, sb.length(), -1);
+ assertEquals("abca", sb.toString());
+ }
+
+ @Test
+ public void testReplace_StrMatcher_String_int_int_int_VaryStartIndex() {
+ StrBuilder sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, sb.length(), -1);
+ assertEquals("-x--y-", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 1, sb.length(), -1);
+ assertEquals("aax--y-", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 2, sb.length(), -1);
+ assertEquals("aax--y-", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 3, sb.length(), -1);
+ assertEquals("aax--y-", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 4, sb.length(), -1);
+ assertEquals("aaxa-ay-", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 5, sb.length(), -1);
+ assertEquals("aaxaa-y-", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 6, sb.length(), -1);
+ assertEquals("aaxaaaay-", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 7, sb.length(), -1);
+ assertEquals("aaxaaaay-", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 8, sb.length(), -1);
+ assertEquals("aaxaaaay-", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 9, sb.length(), -1);
+ assertEquals("aaxaaaayaa", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 10, sb.length(), -1);
+ assertEquals("aaxaaaayaa", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ try {
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 11, sb.length(), -1);
+ fail();
+ } catch (final IndexOutOfBoundsException ex) {}
+ assertEquals("aaxaaaayaa", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ try {
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", -1, sb.length(), -1);
+ fail();
+ } catch (final IndexOutOfBoundsException ex) {}
+ assertEquals("aaxaaaayaa", sb.toString());
+ }
+
+ @Test
+ public void testReplace_StrMatcher_String_int_int_int_VaryEndIndex() {
+ StrBuilder sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 0, -1);
+ assertEquals("aaxaaaayaa", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 2, -1);
+ assertEquals("-xaaaayaa", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 3, -1);
+ assertEquals("-xaaaayaa", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 4, -1);
+ assertEquals("-xaaaayaa", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 5, -1);
+ assertEquals("-x-aayaa", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 6, -1);
+ assertEquals("-x-aayaa", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 7, -1);
+ assertEquals("-x--yaa", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 8, -1);
+ assertEquals("-x--yaa", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 9, -1);
+ assertEquals("-x--yaa", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 10, -1);
+ assertEquals("-x--y-", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 1000, -1);
+ assertEquals("-x--y-", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ try {
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 2, 1, -1);
+ fail();
+ } catch (final IndexOutOfBoundsException ex) {}
+ assertEquals("aaxaaaayaa", sb.toString());
+ }
+
+ @Test
+ public void testReplace_StrMatcher_String_int_int_int_VaryCount() {
+ StrBuilder sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 10, -1);
+ assertEquals("-x--y-", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 10, 0);
+ assertEquals("aaxaaaayaa", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 10, 1);
+ assertEquals("-xaaaayaa", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 10, 2);
+ assertEquals("-x-aayaa", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 10, 3);
+ assertEquals("-x--yaa", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 10, 4);
+ assertEquals("-x--y-", sb.toString());
+
+ sb = new StrBuilder("aaxaaaayaa");
+ sb.replace(StrMatcher.stringMatcher("aa"), "-", 0, 10, 5);
+ assertEquals("-x--y-", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testReverse() {
+ final StrBuilder sb = new StrBuilder();
+ assertEquals("", sb.reverse().toString());
+
+ sb.clear().append(true);
+ assertEquals("eurt", sb.reverse().toString());
+ assertEquals("true", sb.reverse().toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testTrim() {
+ final StrBuilder sb = new StrBuilder();
+ assertEquals("", sb.reverse().toString());
+
+ sb.clear().append(" \u0000 ");
+ assertEquals("", sb.trim().toString());
+
+ sb.clear().append(" \u0000 a b c");
+ assertEquals("a b c", sb.trim().toString());
+
+ sb.clear().append("a b c \u0000 ");
+ assertEquals("a b c", sb.trim().toString());
+
+ sb.clear().append(" \u0000 a b c \u0000 ");
+ assertEquals("a b c", sb.trim().toString());
+
+ sb.clear().append("a b c");
+ assertEquals("a b c", sb.trim().toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testStartsWith() {
+ final StrBuilder sb = new StrBuilder();
+ assertFalse(sb.startsWith("a"));
+ assertFalse(sb.startsWith(null));
+ assertTrue(sb.startsWith(""));
+ sb.append("abc");
+ assertTrue(sb.startsWith("a"));
+ assertTrue(sb.startsWith("ab"));
+ assertTrue(sb.startsWith("abc"));
+ assertFalse(sb.startsWith("cba"));
+ }
+
+ @Test
+ public void testEndsWith() {
+ final StrBuilder sb = new StrBuilder();
+ assertFalse(sb.endsWith("a"));
+ assertFalse(sb.endsWith("c"));
+ assertTrue(sb.endsWith(""));
+ assertFalse(sb.endsWith(null));
+ sb.append("abc");
+ assertTrue(sb.endsWith("c"));
+ assertTrue(sb.endsWith("bc"));
+ assertTrue(sb.endsWith("abc"));
+ assertFalse(sb.endsWith("cba"));
+ assertFalse(sb.endsWith("abcd"));
+ assertFalse(sb.endsWith(" abc"));
+ assertFalse(sb.endsWith("abc "));
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testSubSequenceIntInt() {
+ final StrBuilder sb = new StrBuilder ("hello goodbye");
+ // Start index is negative
+ try {
+ sb.subSequence(-1, 5);
+ fail();
+ } catch (final IndexOutOfBoundsException e) {}
+
+ // End index is negative
+ try {
+ sb.subSequence(2, -1);
+ fail();
+ } catch (final IndexOutOfBoundsException e) {}
+
+ // End index greater than length()
+ try {
+ sb.subSequence(2, sb.length() + 1);
+ fail();
+ } catch (final IndexOutOfBoundsException e) {}
+
+ // Start index greater then end index
+ try {
+ sb.subSequence(3, 2);
+ fail();
+ } catch (final IndexOutOfBoundsException e) {}
+
+ // Normal cases
+ assertEquals ("hello", sb.subSequence(0, 5));
+ assertEquals ("hello goodbye".subSequence(0, 6), sb.subSequence(0, 6));
+ assertEquals ("goodbye", sb.subSequence(6, 13));
+ assertEquals ("hello goodbye".subSequence(6,13), sb.subSequence(6, 13));
+ }
+
+ @Test
+ public void testSubstringInt() {
+ final StrBuilder sb = new StrBuilder ("hello goodbye");
+ assertEquals ("goodbye", sb.substring(6));
+ assertEquals ("hello goodbye".substring(6), sb.substring(6));
+ assertEquals ("hello goodbye", sb.substring(0));
+ assertEquals ("hello goodbye".substring(0), sb.substring(0));
+ try {
+ sb.substring(-1);
+ fail ();
+ } catch (final IndexOutOfBoundsException e) {}
+
+ try {
+ sb.substring(15);
+ fail ();
+ } catch (final IndexOutOfBoundsException e) {}
+
+ }
+
+ @Test
+ public void testSubstringIntInt() {
+ final StrBuilder sb = new StrBuilder ("hello goodbye");
+ assertEquals ("hello", sb.substring(0, 5));
+ assertEquals ("hello goodbye".substring(0, 6), sb.substring(0, 6));
+
+ assertEquals ("goodbye", sb.substring(6, 13));
+ assertEquals ("hello goodbye".substring(6,13), sb.substring(6, 13));
+
+ assertEquals ("goodbye", sb.substring(6, 20));
+
+ try {
+ sb.substring(-1, 5);
+ fail();
+ } catch (final IndexOutOfBoundsException e) {}
+
+ try {
+ sb.substring(15, 20);
+ fail();
+ } catch (final IndexOutOfBoundsException e) {}
+ }
+
+ // -----------------------------------------------------------------------
+ @Test
+ public void testMidString() {
+ final StrBuilder sb = new StrBuilder("hello goodbye hello");
+ assertEquals("goodbye", sb.midString(6, 7));
+ assertEquals("hello", sb.midString(0, 5));
+ assertEquals("hello", sb.midString(-5, 5));
+ assertEquals("", sb.midString(0, -1));
+ assertEquals("", sb.midString(20, 2));
+ assertEquals("hello", sb.midString(14, 22));
+ }
+
+ @Test
+ public void testRightString() {
+ final StrBuilder sb = new StrBuilder("left right");
+ assertEquals("right", sb.rightString(5));
+ assertEquals("", sb.rightString(0));
+ assertEquals("", sb.rightString(-5));
+ assertEquals("left right", sb.rightString(15));
+ }
+
+ @Test
+ public void testLeftString() {
+ final StrBuilder sb = new StrBuilder("left right");
+ assertEquals("left", sb.leftString(4));
+ assertEquals("", sb.leftString(0));
+ assertEquals("", sb.leftString(-5));
+ assertEquals("left right", sb.leftString(15));
+ }
+
+ // -----------------------------------------------------------------------
+ @Test
+ public void testContains_char() {
+ final StrBuilder sb = new StrBuilder("abcdefghijklmnopqrstuvwxyz");
+ assertTrue(sb.contains('a'));
+ assertTrue(sb.contains('o'));
+ assertTrue(sb.contains('z'));
+ assertFalse(sb.contains('1'));
+ }
+
+ @Test
+ public void testContains_String() {
+ final StrBuilder sb = new StrBuilder("abcdefghijklmnopqrstuvwxyz");
+ assertTrue(sb.contains("a"));
+ assertTrue(sb.contains("pq"));
+ assertTrue(sb.contains("z"));
+ assertFalse(sb.contains("zyx"));
+ assertFalse(sb.contains((String) null));
+ }
+
+ @Test
+ public void testContains_StrMatcher() {
+ StrBuilder sb = new StrBuilder("abcdefghijklmnopqrstuvwxyz");
+ assertTrue(sb.contains(StrMatcher.charMatcher('a')));
+ assertTrue(sb.contains(StrMatcher.stringMatcher("pq")));
+ assertTrue(sb.contains(StrMatcher.charMatcher('z')));
+ assertFalse(sb.contains(StrMatcher.stringMatcher("zy")));
+ assertFalse(sb.contains((StrMatcher) null));
+
+ sb = new StrBuilder();
+ assertFalse(sb.contains(A_NUMBER_MATCHER));
+ sb.append("B A1 C");
+ assertTrue(sb.contains(A_NUMBER_MATCHER));
+ }
+
+ // -----------------------------------------------------------------------
+ @Test
+ public void testIndexOf_char() {
+ final StrBuilder sb = new StrBuilder("abab");
+ assertEquals(0, sb.indexOf('a'));
+
+ // should work like String#indexOf
+ assertEquals("abab".indexOf('a'), sb.indexOf('a'));
+
+ assertEquals(1, sb.indexOf('b'));
+ assertEquals("abab".indexOf('b'), sb.indexOf('b'));
+
+ assertEquals(-1, sb.indexOf('z'));
+ }
+
+ @Test
+ public void testIndexOf_char_int() {
+ StrBuilder sb = new StrBuilder("abab");
+ assertEquals(0, sb.indexOf('a', -1));
+ assertEquals(0, sb.indexOf('a', 0));
+ assertEquals(2, sb.indexOf('a', 1));
+ assertEquals(-1, sb.indexOf('a', 4));
+ assertEquals(-1, sb.indexOf('a', 5));
+
+ // should work like String#indexOf
+ assertEquals("abab".indexOf('a', 1), sb.indexOf('a', 1));
+
+ assertEquals(3, sb.indexOf('b', 2));
+ assertEquals("abab".indexOf('b', 2), sb.indexOf('b', 2));
+
+ assertEquals(-1, sb.indexOf('z', 2));
+
+ sb = new StrBuilder("xyzabc");
+ assertEquals(2, sb.indexOf('z', 0));
+ assertEquals(-1, sb.indexOf('z', 3));
+ }
+
+ @Test
+ public void testLastIndexOf_char() {
+ final StrBuilder sb = new StrBuilder("abab");
+
+ assertEquals (2, sb.lastIndexOf('a'));
+ //should work like String#lastIndexOf
+ assertEquals ("abab".lastIndexOf('a'), sb.lastIndexOf('a'));
+
+ assertEquals(3, sb.lastIndexOf('b'));
+ assertEquals ("abab".lastIndexOf('b'), sb.lastIndexOf('b'));
+
+ assertEquals (-1, sb.lastIndexOf('z'));
+ }
+
+ @Test
+ public void testLastIndexOf_char_int() {
+ StrBuilder sb = new StrBuilder("abab");
+ assertEquals(-1, sb.lastIndexOf('a', -1));
+ assertEquals(0, sb.lastIndexOf('a', 0));
+ assertEquals(0, sb.lastIndexOf('a', 1));
+
+ // should work like String#lastIndexOf
+ assertEquals("abab".lastIndexOf('a', 1), sb.lastIndexOf('a', 1));
+
+ assertEquals(1, sb.lastIndexOf('b', 2));
+ assertEquals("abab".lastIndexOf('b', 2), sb.lastIndexOf('b', 2));
+
+ assertEquals(-1, sb.lastIndexOf('z', 2));
+
+ sb = new StrBuilder("xyzabc");
+ assertEquals(2, sb.lastIndexOf('z', sb.length()));
+ assertEquals(-1, sb.lastIndexOf('z', 1));
+ }
+
+ // -----------------------------------------------------------------------
+ @Test
+ public void testIndexOf_String() {
+ final StrBuilder sb = new StrBuilder("abab");
+
+ assertEquals(0, sb.indexOf("a"));
+ //should work like String#indexOf
+ assertEquals("abab".indexOf("a"), sb.indexOf("a"));
+
+ assertEquals(0, sb.indexOf("ab"));
+ //should work like String#indexOf
+ assertEquals("abab".indexOf("ab"), sb.indexOf("ab"));
+
+ assertEquals(1, sb.indexOf("b"));
+ assertEquals("abab".indexOf("b"), sb.indexOf("b"));
+
+ assertEquals(1, sb.indexOf("ba"));
+ assertEquals("abab".indexOf("ba"), sb.indexOf("ba"));
+
+ assertEquals(-1, sb.indexOf("z"));
+
+ assertEquals(-1, sb.indexOf((String) null));
+ }
+
+ @Test
+ public void testIndexOf_String_int() {
+ StrBuilder sb = new StrBuilder("abab");
+ assertEquals(0, sb.indexOf("a", -1));
+ assertEquals(0, sb.indexOf("a", 0));
+ assertEquals(2, sb.indexOf("a", 1));
+ assertEquals(2, sb.indexOf("a", 2));
+ assertEquals(-1, sb.indexOf("a", 3));
+ assertEquals(-1, sb.indexOf("a", 4));
+ assertEquals(-1, sb.indexOf("a", 5));
+
+ assertEquals(-1, sb.indexOf("abcdef", 0));
+ assertEquals(0, sb.indexOf("", 0));
+ assertEquals(1, sb.indexOf("", 1));
+
+ //should work like String#indexOf
+ assertEquals ("abab".indexOf("a", 1), sb.indexOf("a", 1));
+
+ assertEquals(2, sb.indexOf("ab", 1));
+ //should work like String#indexOf
+ assertEquals("abab".indexOf("ab", 1), sb.indexOf("ab", 1));
+
+ assertEquals(3, sb.indexOf("b", 2));
+ assertEquals("abab".indexOf("b", 2), sb.indexOf("b", 2));
+
+ assertEquals(1, sb.indexOf("ba", 1));
+ assertEquals("abab".indexOf("ba", 2), sb.indexOf("ba", 2));
+
+ assertEquals(-1, sb.indexOf("z", 2));
+
+ sb = new StrBuilder("xyzabc");
+ assertEquals(2, sb.indexOf("za", 0));
+ assertEquals(-1, sb.indexOf("za", 3));
+
+ assertEquals(-1, sb.indexOf((String) null, 2));
+ }
+
+ @Test
+ public void testLastIndexOf_String() {
+ final StrBuilder sb = new StrBuilder("abab");
+
+ assertEquals(2, sb.lastIndexOf("a"));
+ //should work like String#lastIndexOf
+ assertEquals("abab".lastIndexOf("a"), sb.lastIndexOf("a"));
+
+ assertEquals(2, sb.lastIndexOf("ab"));
+ //should work like String#lastIndexOf
+ assertEquals("abab".lastIndexOf("ab"), sb.lastIndexOf("ab"));
+
+ assertEquals(3, sb.lastIndexOf("b"));
+ assertEquals("abab".lastIndexOf("b"), sb.lastIndexOf("b"));
+
+ assertEquals(1, sb.lastIndexOf("ba"));
+ assertEquals("abab".lastIndexOf("ba"), sb.lastIndexOf("ba"));
+
+ assertEquals(-1, sb.lastIndexOf("z"));
+
+ assertEquals(-1, sb.lastIndexOf((String) null));
+ }
+
+ @Test
+ public void testLastIndexOf_String_int() {
+ StrBuilder sb = new StrBuilder("abab");
+ assertEquals(-1, sb.lastIndexOf("a", -1));
+ assertEquals(0, sb.lastIndexOf("a", 0));
+ assertEquals(0, sb.lastIndexOf("a", 1));
+ assertEquals(2, sb.lastIndexOf("a", 2));
+ assertEquals(2, sb.lastIndexOf("a", 3));
+ assertEquals(2, sb.lastIndexOf("a", 4));
+ assertEquals(2, sb.lastIndexOf("a", 5));
+
+ assertEquals(-1, sb.lastIndexOf("abcdef", 3));
+ assertEquals("abab".lastIndexOf("", 3), sb.lastIndexOf("", 3));
+ assertEquals("abab".lastIndexOf("", 1), sb.lastIndexOf("", 1));
+
+ //should work like String#lastIndexOf
+ assertEquals("abab".lastIndexOf("a", 1), sb.lastIndexOf("a", 1));
+
+ assertEquals(0, sb.lastIndexOf("ab", 1));
+ //should work like String#lastIndexOf
+ assertEquals("abab".lastIndexOf("ab", 1), sb.lastIndexOf("ab", 1));
+
+ assertEquals(1, sb.lastIndexOf("b", 2));
+ assertEquals("abab".lastIndexOf("b", 2), sb.lastIndexOf("b", 2));
+
+ assertEquals(1, sb.lastIndexOf("ba", 2));
+ assertEquals("abab".lastIndexOf("ba", 2), sb.lastIndexOf("ba", 2));
+
+ assertEquals(-1, sb.lastIndexOf("z", 2));
+
+ sb = new StrBuilder("xyzabc");
+ assertEquals(2, sb.lastIndexOf("za", sb.length()));
+ assertEquals(-1, sb.lastIndexOf("za", 1));
+
+ assertEquals(-1, sb.lastIndexOf((String) null, 2));
+ }
+
+ // -----------------------------------------------------------------------
+ @Test
+ public void testIndexOf_StrMatcher() {
+ final StrBuilder sb = new StrBuilder();
+ assertEquals(-1, sb.indexOf((StrMatcher) null));
+ assertEquals(-1, sb.indexOf(StrMatcher.charMatcher('a')));
+
+ sb.append("ab bd");
+ assertEquals(0, sb.indexOf(StrMatcher.charMatcher('a')));
+ assertEquals(1, sb.indexOf(StrMatcher.charMatcher('b')));
+ assertEquals(2, sb.indexOf(StrMatcher.spaceMatcher()));
+ assertEquals(4, sb.indexOf(StrMatcher.charMatcher('d')));
+ assertEquals(-1, sb.indexOf(StrMatcher.noneMatcher()));
+ assertEquals(-1, sb.indexOf((StrMatcher) null));
+
+ sb.append(" A1 junction");
+ assertEquals(6, sb.indexOf(A_NUMBER_MATCHER));
+ }
+
+ @Test
+ public void testIndexOf_StrMatcher_int() {
+ final StrBuilder sb = new StrBuilder();
+ assertEquals(-1, sb.indexOf((StrMatcher) null, 2));
+ assertEquals(-1, sb.indexOf(StrMatcher.charMatcher('a'), 2));
+ assertEquals(-1, sb.indexOf(StrMatcher.charMatcher('a'), 0));
+
+ sb.append("ab bd");
+ assertEquals(0, sb.indexOf(StrMatcher.charMatcher('a'), -2));
+ assertEquals(0, sb.indexOf(StrMatcher.charMatcher('a'), 0));
+ assertEquals(-1, sb.indexOf(StrMatcher.charMatcher('a'), 2));
+ assertEquals(-1, sb.indexOf(StrMatcher.charMatcher('a'), 20));
+
+ assertEquals(1, sb.indexOf(StrMatcher.charMatcher('b'), -1));
+ assertEquals(1, sb.indexOf(StrMatcher.charMatcher('b'), 0));
+ assertEquals(1, sb.indexOf(StrMatcher.charMatcher('b'), 1));
+ assertEquals(3, sb.indexOf(StrMatcher.charMatcher('b'), 2));
+ assertEquals(3, sb.indexOf(StrMatcher.charMatcher('b'), 3));
+ assertEquals(-1, sb.indexOf(StrMatcher.charMatcher('b'), 4));
+ assertEquals(-1, sb.indexOf(StrMatcher.charMatcher('b'), 5));
+ assertEquals(-1, sb.indexOf(StrMatcher.charMatcher('b'), 6));
+
+ assertEquals(2, sb.indexOf(StrMatcher.spaceMatcher(), -2));
+ assertEquals(2, sb.indexOf(StrMatcher.spaceMatcher(), 0));
+ assertEquals(2, sb.indexOf(StrMatcher.spaceMatcher(), 2));
+ assertEquals(-1, sb.indexOf(StrMatcher.spaceMatcher(), 4));
+ assertEquals(-1, sb.indexOf(StrMatcher.spaceMatcher(), 20));
+
+ assertEquals(-1, sb.indexOf(StrMatcher.noneMatcher(), 0));
+ assertEquals(-1, sb.indexOf((StrMatcher) null, 0));
+
+ sb.append(" A1 junction with A2");
+ assertEquals(6, sb.indexOf(A_NUMBER_MATCHER, 5));
+ assertEquals(6, sb.indexOf(A_NUMBER_MATCHER, 6));
+ assertEquals(23, sb.indexOf(A_NUMBER_MATCHER, 7));
+ assertEquals(23, sb.indexOf(A_NUMBER_MATCHER, 22));
+ assertEquals(23, sb.indexOf(A_NUMBER_MATCHER, 23));
+ assertEquals(-1, sb.indexOf(A_NUMBER_MATCHER, 24));
+ }
+
+ @Test
+ public void testLastIndexOf_StrMatcher() {
+ final StrBuilder sb = new StrBuilder();
+ assertEquals(-1, sb.lastIndexOf((StrMatcher) null));
+ assertEquals(-1, sb.lastIndexOf(StrMatcher.charMatcher('a')));
+
+ sb.append("ab bd");
+ assertEquals(0, sb.lastIndexOf(StrMatcher.charMatcher('a')));
+ assertEquals(3, sb.lastIndexOf(StrMatcher.charMatcher('b')));
+ assertEquals(2, sb.lastIndexOf(StrMatcher.spaceMatcher()));
+ assertEquals(4, sb.lastIndexOf(StrMatcher.charMatcher('d')));
+ assertEquals(-1, sb.lastIndexOf(StrMatcher.noneMatcher()));
+ assertEquals(-1, sb.lastIndexOf((StrMatcher) null));
+
+ sb.append(" A1 junction");
+ assertEquals(6, sb.lastIndexOf(A_NUMBER_MATCHER));
+ }
+
+ @Test
+ public void testLastIndexOf_StrMatcher_int() {
+ final StrBuilder sb = new StrBuilder();
+ assertEquals(-1, sb.lastIndexOf((StrMatcher) null, 2));
+ assertEquals(-1, sb.lastIndexOf(StrMatcher.charMatcher('a'), 2));
+ assertEquals(-1, sb.lastIndexOf(StrMatcher.charMatcher('a'), 0));
+ assertEquals(-1, sb.lastIndexOf(StrMatcher.charMatcher('a'), -1));
+
+ sb.append("ab bd");
+ assertEquals(-1, sb.lastIndexOf(StrMatcher.charMatcher('a'), -2));
+ assertEquals(0, sb.lastIndexOf(StrMatcher.charMatcher('a'), 0));
+ assertEquals(0, sb.lastIndexOf(StrMatcher.charMatcher('a'), 2));
+ assertEquals(0, sb.lastIndexOf(StrMatcher.charMatcher('a'), 20));
+
+ assertEquals(-1, sb.lastIndexOf(StrMatcher.charMatcher('b'), -1));
+ assertEquals(-1, sb.lastIndexOf(StrMatcher.charMatcher('b'), 0));
+ assertEquals(1, sb.lastIndexOf(StrMatcher.charMatcher('b'), 1));
+ assertEquals(1, sb.lastIndexOf(StrMatcher.charMatcher('b'), 2));
+ assertEquals(3, sb.lastIndexOf(StrMatcher.charMatcher('b'), 3));
+ assertEquals(3, sb.lastIndexOf(StrMatcher.charMatcher('b'), 4));
+ assertEquals(3, sb.lastIndexOf(StrMatcher.charMatcher('b'), 5));
+ assertEquals(3, sb.lastIndexOf(StrMatcher.charMatcher('b'), 6));
+
+ assertEquals(-1, sb.lastIndexOf(StrMatcher.spaceMatcher(), -2));
+ assertEquals(-1, sb.lastIndexOf(StrMatcher.spaceMatcher(), 0));
+ assertEquals(2, sb.lastIndexOf(StrMatcher.spaceMatcher(), 2));
+ assertEquals(2, sb.lastIndexOf(StrMatcher.spaceMatcher(), 4));
+ assertEquals(2, sb.lastIndexOf(StrMatcher.spaceMatcher(), 20));
+
+ assertEquals(-1, sb.lastIndexOf(StrMatcher.noneMatcher(), 0));
+ assertEquals(-1, sb.lastIndexOf((StrMatcher) null, 0));
+
+ sb.append(" A1 junction with A2");
+ assertEquals(-1, sb.lastIndexOf(A_NUMBER_MATCHER, 5));
+ assertEquals(-1, sb.lastIndexOf(A_NUMBER_MATCHER, 6)); // A matches, 1 is outside bounds
+ assertEquals(6, sb.lastIndexOf(A_NUMBER_MATCHER, 7));
+ assertEquals(6, sb.lastIndexOf(A_NUMBER_MATCHER, 22));
+ assertEquals(6, sb.lastIndexOf(A_NUMBER_MATCHER, 23)); // A matches, 2 is outside bounds
+ assertEquals(23, sb.lastIndexOf(A_NUMBER_MATCHER, 24));
+ }
+
+ static final StrMatcher A_NUMBER_MATCHER = new StrMatcher() {
+ @Override
+ public int isMatch(final char[] buffer, int pos, final int bufferStart, final int bufferEnd) {
+ if (buffer[pos] == 'A') {
+ pos++;
+ if (pos < bufferEnd && buffer[pos] >= '0' && buffer[pos] <= '9') {
+ return 2;
+ }
+ }
+ return 0;
+ }
+ };
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAsTokenizer() throws Exception {
+ // from Javadoc
+ final StrBuilder b = new StrBuilder();
+ b.append("a b ");
+ final StrTokenizer t = b.asTokenizer();
+
+ final String[] tokens1 = t.getTokenArray();
+ assertEquals(2, tokens1.length);
+ assertEquals("a", tokens1[0]);
+ assertEquals("b", tokens1[1]);
+ assertEquals(2, t.size());
+
+ b.append("c d ");
+ final String[] tokens2 = t.getTokenArray();
+ assertEquals(2, tokens2.length);
+ assertEquals("a", tokens2[0]);
+ assertEquals("b", tokens2[1]);
+ assertEquals(2, t.size());
+ assertEquals("a", t.next());
+ assertEquals("b", t.next());
+
+ t.reset();
+ final String[] tokens3 = t.getTokenArray();
+ assertEquals(4, tokens3.length);
+ assertEquals("a", tokens3[0]);
+ assertEquals("b", tokens3[1]);
+ assertEquals("c", tokens3[2]);
+ assertEquals("d", tokens3[3]);
+ assertEquals(4, t.size());
+ assertEquals("a", t.next());
+ assertEquals("b", t.next());
+ assertEquals("c", t.next());
+ assertEquals("d", t.next());
+
+ assertEquals("a b c d ", t.getContent());
+ }
+
+ // -----------------------------------------------------------------------
+ @Test
+ public void testAsReader() throws Exception {
+ final StrBuilder sb = new StrBuilder("some text");
+ Reader reader = sb.asReader();
+ assertTrue(reader.ready());
+ final char[] buf = new char[40];
+ assertEquals(9, reader.read(buf));
+ assertEquals("some text", new String(buf, 0, 9));
+
+ assertEquals(-1, reader.read());
+ assertFalse(reader.ready());
+ assertEquals(0, reader.skip(2));
+ assertEquals(0, reader.skip(-1));
+
+ assertTrue(reader.markSupported());
+ reader = sb.asReader();
+ assertEquals('s', reader.read());
+ reader.mark(-1);
+ char[] array = new char[3];
+ assertEquals(3, reader.read(array, 0, 3));
+ assertEquals('o', array[0]);
+ assertEquals('m', array[1]);
+ assertEquals('e', array[2]);
+ reader.reset();
+ assertEquals(1, reader.read(array, 1, 1));
+ assertEquals('o', array[0]);
+ assertEquals('o', array[1]);
+ assertEquals('e', array[2]);
+ assertEquals(2, reader.skip(2));
+ assertEquals(' ', reader.read());
+
+ assertTrue(reader.ready());
+ reader.close();
+ assertTrue(reader.ready());
+
+ reader = sb.asReader();
+ array = new char[3];
+ try {
+ reader.read(array, -1, 0);
+ fail();
+ } catch (final IndexOutOfBoundsException ex) {}
+ try {
+ reader.read(array, 0, -1);
+ fail();
+ } catch (final IndexOutOfBoundsException ex) {}
+ try {
+ reader.read(array, 100, 1);
+ fail();
+ } catch (final IndexOutOfBoundsException ex) {}
+ try {
+ reader.read(array, 0, 100);
+ fail();
+ } catch (final IndexOutOfBoundsException ex) {}
+ try {
+ reader.read(array, Integer.MAX_VALUE, Integer.MAX_VALUE);
+ fail();
+ } catch (final IndexOutOfBoundsException ex) {}
+
+ assertEquals(0, reader.read(array, 0, 0));
+ assertEquals(0, array[0]);
+ assertEquals(0, array[1]);
+ assertEquals(0, array[2]);
+
+ reader.skip(9);
+ assertEquals(-1, reader.read(array, 0, 1));
+
+ reader.reset();
+ array = new char[30];
+ assertEquals(9, reader.read(array, 0, 30));
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAsWriter() throws Exception {
+ final StrBuilder sb = new StrBuilder("base");
+ final Writer writer = sb.asWriter();
+
+ writer.write('l');
+ assertEquals("basel", sb.toString());
+
+ writer.write(new char[] {'i', 'n'});
+ assertEquals("baselin", sb.toString());
+
+ writer.write(new char[] {'n', 'e', 'r'}, 1, 2);
+ assertEquals("baseliner", sb.toString());
+
+ writer.write(" rout");
+ assertEquals("baseliner rout", sb.toString());
+
+ writer.write("ping that server", 1, 3);
+ assertEquals("baseliner routing", sb.toString());
+
+ writer.flush(); // no effect
+ assertEquals("baseliner routing", sb.toString());
+
+ writer.close(); // no effect
+ assertEquals("baseliner routing", sb.toString());
+
+ writer.write(" hi"); // works after close
+ assertEquals("baseliner routing hi", sb.toString());
+
+ sb.setLength(4); // mix and match
+ writer.write('d');
+ assertEquals("based", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testEqualsIgnoreCase() {
+ final StrBuilder sb1 = new StrBuilder();
+ final StrBuilder sb2 = new StrBuilder();
+ assertTrue(sb1.equalsIgnoreCase(sb1));
+ assertTrue(sb1.equalsIgnoreCase(sb2));
+ assertTrue(sb2.equalsIgnoreCase(sb2));
+
+ sb1.append("abc");
+ assertFalse(sb1.equalsIgnoreCase(sb2));
+
+ sb2.append("ABC");
+ assertTrue(sb1.equalsIgnoreCase(sb2));
+
+ sb2.clear().append("abc");
+ assertTrue(sb1.equalsIgnoreCase(sb2));
+ assertTrue(sb1.equalsIgnoreCase(sb1));
+ assertTrue(sb2.equalsIgnoreCase(sb2));
+
+ sb2.clear().append("aBc");
+ assertTrue(sb1.equalsIgnoreCase(sb2));
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testEquals() {
+ final StrBuilder sb1 = new StrBuilder();
+ final StrBuilder sb2 = new StrBuilder();
+ assertTrue(sb1.equals(sb2));
+ assertTrue(sb1.equals(sb1));
+ assertTrue(sb2.equals(sb2));
+ assertTrue(sb1.equals((Object) sb2));
+
+ sb1.append("abc");
+ assertFalse(sb1.equals(sb2));
+ assertFalse(sb1.equals((Object) sb2));
+
+ sb2.append("ABC");
+ assertFalse(sb1.equals(sb2));
+ assertFalse(sb1.equals((Object) sb2));
+
+ sb2.clear().append("abc");
+ assertTrue(sb1.equals(sb2));
+ assertTrue(sb1.equals((Object) sb2));
+
+ assertFalse(sb1.equals(Integer.valueOf(1)));
+ assertFalse(sb1.equals("abc"));
+ }
+
+ @Test
+ public void test_LANG_1131_EqualsWithNullStrBuilder() throws Exception {
+ final StrBuilder sb = new StrBuilder();
+ final StrBuilder other = null;
+ assertFalse(sb.equals(other));
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testHashCode() {
+ final StrBuilder sb = new StrBuilder();
+ final int hc1a = sb.hashCode();
+ final int hc1b = sb.hashCode();
+ assertEquals(0, hc1a);
+ assertEquals(hc1a, hc1b);
+
+ sb.append("abc");
+ final int hc2a = sb.hashCode();
+ final int hc2b = sb.hashCode();
+ assertTrue(hc2a != 0);
+ assertEquals(hc2a, hc2b);
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testToString() {
+ final StrBuilder sb = new StrBuilder("abc");
+ assertEquals("abc", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testToStringBuffer() {
+ final StrBuilder sb = new StrBuilder();
+ assertEquals(new StringBuffer().toString(), sb.toStringBuffer().toString());
+
+ sb.append("junit");
+ assertEquals(new StringBuffer("junit").toString(), sb.toStringBuffer().toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testToStringBuilder() {
+ final StrBuilder sb = new StrBuilder();
+ assertEquals(new StringBuilder().toString(), sb.toStringBuilder().toString());
+
+ sb.append("junit");
+ assertEquals(new StringBuilder("junit").toString(), sb.toStringBuilder().toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testLang294() {
+ final StrBuilder sb = new StrBuilder("\n%BLAH%\nDo more stuff\neven more stuff\n%BLAH%\n");
+ sb.deleteAll("\n%BLAH%");
+ assertEquals("\nDo more stuff\neven more stuff\n", sb.toString());
+ }
+
+ @Test
+ public void testIndexOfLang294() {
+ final StrBuilder sb = new StrBuilder("onetwothree");
+ sb.deleteFirst("three");
+ assertEquals(-1, sb.indexOf("three"));
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testLang295() {
+ final StrBuilder sb = new StrBuilder("onetwothree");
+ sb.deleteFirst("three");
+ assertFalse( "The contains(char) method is looking beyond the end of the string", sb.contains('h'));
+ assertEquals( "The indexOf(char) method is looking beyond the end of the string", -1, sb.indexOf('h'));
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testLang412Right() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendFixedWidthPadRight(null, 10, '*');
+ assertEquals( "Failed to invoke appendFixedWidthPadRight correctly", "**********", sb.toString());
+ }
+
+ @Test
+ public void testLang412Left() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendFixedWidthPadLeft(null, 10, '*');
+ assertEquals( "Failed to invoke appendFixedWidthPadLeft correctly", "**********", sb.toString());
+ }
+
+ @Test
+ public void testAsBuilder() {
+ final StrBuilder sb = new StrBuilder().appendAll("Lorem", " ", "ipsum", " ", "dolor");
+ assertEquals(sb.toString(), sb.build());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendCharBuffer() {
+ final StrBuilder sb1 = new StrBuilder();
+ final CharBuffer buf = CharBuffer.allocate(10);
+ buf.append("0123456789");
+ buf.flip();
+ sb1.append(buf);
+ assertEquals("0123456789", sb1.toString());
+
+ final StrBuilder sb2 = new StrBuilder();
+ sb2.append(buf, 1, 8);
+ assertEquals("12345678", sb2.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendToWriter() throws Exception {
+ final StrBuilder sb = new StrBuilder("1234567890");
+ final StringWriter writer = new StringWriter();
+ writer.append("Test ");
+
+ sb.appendTo(writer);
+
+ assertEquals("Test 1234567890", writer.toString());
+ }
+
+ @Test
+ public void testAppendToStringBuilder() throws Exception {
+ final StrBuilder sb = new StrBuilder("1234567890");
+ final StringBuilder builder = new StringBuilder("Test ");
+
+ sb.appendTo(builder);
+
+ assertEquals("Test 1234567890", builder.toString());
+ }
+
+ @Test
+ public void testAppendToStringBuffer() throws Exception {
+ final StrBuilder sb = new StrBuilder("1234567890");
+ final StringBuffer buffer = new StringBuffer("Test ");
+
+ sb.appendTo(buffer);
+
+ assertEquals("Test 1234567890", buffer.toString());
+ }
+
+ @Test
+ public void testAppendToCharBuffer() throws Exception {
+ final StrBuilder sb = new StrBuilder("1234567890");
+ final String text = "Test ";
+ final CharBuffer buffer = CharBuffer.allocate(sb.size() + text.length());
+ buffer.put(text);
+
+ sb.appendTo(buffer);
+
+ buffer.flip();
+ assertEquals("Test 1234567890", buffer.toString());
+ }
+}
http://git-wip-us.apache.org/repos/asf/commons-text/blob/6f24aa45/src/test/java/org/apache/commons/text/StrLookupTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/text/StrLookupTest.java b/src/test/java/org/apache/commons/text/StrLookupTest.java
new file mode 100644
index 0000000..dfd8933
--- /dev/null
+++ b/src/test/java/org/apache/commons/text/StrLookupTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import org.junit.Test;
+
+/**
+ * Test class for {@link StrLookup}.
+ */
+public class StrLookupTest {
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testNoneLookup() {
+ assertEquals(null, StrLookup.noneLookup().lookup(null));
+ assertEquals(null, StrLookup.noneLookup().lookup(""));
+ assertEquals(null, StrLookup.noneLookup().lookup("any"));
+ }
+
+ @Test
+ public void testSystemProperiesLookup() {
+ assertEquals(System.getProperty("os.name"), StrLookup.systemPropertiesLookup().lookup("os.name"));
+ assertEquals(null, StrLookup.systemPropertiesLookup().lookup(""));
+ assertEquals(null, StrLookup.systemPropertiesLookup().lookup("other"));
+ try {
+ StrLookup.systemPropertiesLookup().lookup(null);
+ fail();
+ } catch (final NullPointerException ex) {
+ // expected
+ }
+ }
+
+ /**
+ * Tests that a lookup object for system properties can deal with a full
+ * replacement of the system properties object. This test is related to
+ * LANG-1055.
+ */
+ @Test
+ public void testSystemPropertiesLookupReplacedProperties() {
+ final Properties oldProperties = System.getProperties();
+ final String osName = "os.name";
+ final String newOsName = oldProperties.getProperty(osName) + "_changed";
+
+ final StrLookup<String> sysLookup = StrLookup.systemPropertiesLookup();
+ final Properties newProps = new Properties();
+ newProps.setProperty(osName, newOsName);
+ System.setProperties(newProps);
+ try {
+ assertEquals("Changed properties not detected", newOsName, sysLookup.lookup(osName));
+ } finally {
+ System.setProperties(oldProperties);
+ }
+ }
+
+ /**
+ * Tests that a lookup object for system properties sees changes on system
+ * properties. This test is related to LANG-1141.
+ */
+ @Test
+ public void testSystemPropertiesLookupUpdatedProperty() {
+ final String osName = "os.name";
+ final String oldOs = System.getProperty(osName);
+ final String newOsName = oldOs + "_changed";
+
+ final StrLookup<String> sysLookup = StrLookup.systemPropertiesLookup();
+ System.setProperty(osName, newOsName);
+ try {
+ assertEquals("Changed properties not detected", newOsName, sysLookup.lookup(osName));
+ } finally {
+ System.setProperty(osName, oldOs);
+ }
+ }
+
+ @Test
+ public void testMapLookup() {
+ final Map<String, Object> map = new HashMap<>();
+ map.put("key", "value");
+ map.put("number", Integer.valueOf(2));
+ assertEquals("value", StrLookup.mapLookup(map).lookup("key"));
+ assertEquals("2", StrLookup.mapLookup(map).lookup("number"));
+ assertEquals(null, StrLookup.mapLookup(map).lookup(null));
+ assertEquals(null, StrLookup.mapLookup(map).lookup(""));
+ assertEquals(null, StrLookup.mapLookup(map).lookup("other"));
+ }
+
+ @Test
+ public void testMapLookup_nullMap() {
+ final Map<String, ?> map = null;
+ assertEquals(null, StrLookup.mapLookup(map).lookup(null));
+ assertEquals(null, StrLookup.mapLookup(map).lookup(""));
+ assertEquals(null, StrLookup.mapLookup(map).lookup("any"));
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/commons-text/blob/6f24aa45/src/test/java/org/apache/commons/text/StrMatcherTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/text/StrMatcherTest.java b/src/test/java/org/apache/commons/text/StrMatcherTest.java
new file mode 100644
index 0000000..2b365e0
--- /dev/null
+++ b/src/test/java/org/apache/commons/text/StrMatcherTest.java
@@ -0,0 +1,214 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link StrMatcher}.
+ */
+public class StrMatcherTest {
+
+ private static final char[] BUFFER1 = "0,1\t2 3\n\r\f\u0000'\"".toCharArray();
+
+ private static final char[] BUFFER2 = "abcdef".toCharArray();
+
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testCommaMatcher() {
+ final StrMatcher matcher = StrMatcher.commaMatcher();
+ assertSame(matcher, StrMatcher.commaMatcher());
+ assertEquals(0, matcher.isMatch(BUFFER1, 0));
+ assertEquals(1, matcher.isMatch(BUFFER1, 1));
+ assertEquals(0, matcher.isMatch(BUFFER1, 2));
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testTabMatcher() {
+ final StrMatcher matcher = StrMatcher.tabMatcher();
+ assertSame(matcher, StrMatcher.tabMatcher());
+ assertEquals(0, matcher.isMatch(BUFFER1, 2));
+ assertEquals(1, matcher.isMatch(BUFFER1, 3));
+ assertEquals(0, matcher.isMatch(BUFFER1, 4));
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testSpaceMatcher() {
+ final StrMatcher matcher = StrMatcher.spaceMatcher();
+ assertSame(matcher, StrMatcher.spaceMatcher());
+ assertEquals(0, matcher.isMatch(BUFFER1, 4));
+ assertEquals(1, matcher.isMatch(BUFFER1, 5));
+ assertEquals(0, matcher.isMatch(BUFFER1, 6));
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testSplitMatcher() {
+ final StrMatcher matcher = StrMatcher.splitMatcher();
+ assertSame(matcher, StrMatcher.splitMatcher());
+ assertEquals(0, matcher.isMatch(BUFFER1, 2));
+ assertEquals(1, matcher.isMatch(BUFFER1, 3));
+ assertEquals(0, matcher.isMatch(BUFFER1, 4));
+ assertEquals(1, matcher.isMatch(BUFFER1, 5));
+ assertEquals(0, matcher.isMatch(BUFFER1, 6));
+ assertEquals(1, matcher.isMatch(BUFFER1, 7));
+ assertEquals(1, matcher.isMatch(BUFFER1, 8));
+ assertEquals(1, matcher.isMatch(BUFFER1, 9));
+ assertEquals(0, matcher.isMatch(BUFFER1, 10));
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testTrimMatcher() {
+ final StrMatcher matcher = StrMatcher.trimMatcher();
+ assertSame(matcher, StrMatcher.trimMatcher());
+ assertEquals(0, matcher.isMatch(BUFFER1, 2));
+ assertEquals(1, matcher.isMatch(BUFFER1, 3));
+ assertEquals(0, matcher.isMatch(BUFFER1, 4));
+ assertEquals(1, matcher.isMatch(BUFFER1, 5));
+ assertEquals(0, matcher.isMatch(BUFFER1, 6));
+ assertEquals(1, matcher.isMatch(BUFFER1, 7));
+ assertEquals(1, matcher.isMatch(BUFFER1, 8));
+ assertEquals(1, matcher.isMatch(BUFFER1, 9));
+ assertEquals(1, matcher.isMatch(BUFFER1, 10));
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testSingleQuoteMatcher() {
+ final StrMatcher matcher = StrMatcher.singleQuoteMatcher();
+ assertSame(matcher, StrMatcher.singleQuoteMatcher());
+ assertEquals(0, matcher.isMatch(BUFFER1, 10));
+ assertEquals(1, matcher.isMatch(BUFFER1, 11));
+ assertEquals(0, matcher.isMatch(BUFFER1, 12));
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testDoubleQuoteMatcher() {
+ final StrMatcher matcher = StrMatcher.doubleQuoteMatcher();
+ assertSame(matcher, StrMatcher.doubleQuoteMatcher());
+ assertEquals(0, matcher.isMatch(BUFFER1, 11));
+ assertEquals(1, matcher.isMatch(BUFFER1, 12));
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testQuoteMatcher() {
+ final StrMatcher matcher = StrMatcher.quoteMatcher();
+ assertSame(matcher, StrMatcher.quoteMatcher());
+ assertEquals(0, matcher.isMatch(BUFFER1, 10));
+ assertEquals(1, matcher.isMatch(BUFFER1, 11));
+ assertEquals(1, matcher.isMatch(BUFFER1, 12));
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testNoneMatcher() {
+ final StrMatcher matcher = StrMatcher.noneMatcher();
+ assertSame(matcher, StrMatcher.noneMatcher());
+ assertEquals(0, matcher.isMatch(BUFFER1, 0));
+ assertEquals(0, matcher.isMatch(BUFFER1, 1));
+ assertEquals(0, matcher.isMatch(BUFFER1, 2));
+ assertEquals(0, matcher.isMatch(BUFFER1, 3));
+ assertEquals(0, matcher.isMatch(BUFFER1, 4));
+ assertEquals(0, matcher.isMatch(BUFFER1, 5));
+ assertEquals(0, matcher.isMatch(BUFFER1, 6));
+ assertEquals(0, matcher.isMatch(BUFFER1, 7));
+ assertEquals(0, matcher.isMatch(BUFFER1, 8));
+ assertEquals(0, matcher.isMatch(BUFFER1, 9));
+ assertEquals(0, matcher.isMatch(BUFFER1, 10));
+ assertEquals(0, matcher.isMatch(BUFFER1, 11));
+ assertEquals(0, matcher.isMatch(BUFFER1, 12));
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testCharMatcher_char() {
+ final StrMatcher matcher = StrMatcher.charMatcher('c');
+ assertEquals(0, matcher.isMatch(BUFFER2, 0));
+ assertEquals(0, matcher.isMatch(BUFFER2, 1));
+ assertEquals(1, matcher.isMatch(BUFFER2, 2));
+ assertEquals(0, matcher.isMatch(BUFFER2, 3));
+ assertEquals(0, matcher.isMatch(BUFFER2, 4));
+ assertEquals(0, matcher.isMatch(BUFFER2, 5));
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testCharSetMatcher_String() {
+ final StrMatcher matcher = StrMatcher.charSetMatcher("ace");
+ assertEquals(1, matcher.isMatch(BUFFER2, 0));
+ assertEquals(0, matcher.isMatch(BUFFER2, 1));
+ assertEquals(1, matcher.isMatch(BUFFER2, 2));
+ assertEquals(0, matcher.isMatch(BUFFER2, 3));
+ assertEquals(1, matcher.isMatch(BUFFER2, 4));
+ assertEquals(0, matcher.isMatch(BUFFER2, 5));
+ assertSame(StrMatcher.noneMatcher(), StrMatcher.charSetMatcher(""));
+ assertSame(StrMatcher.noneMatcher(), StrMatcher.charSetMatcher((String) null));
+ assertTrue(StrMatcher.charSetMatcher("a") instanceof StrMatcher.CharMatcher);
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testCharSetMatcher_charArray() {
+ final StrMatcher matcher = StrMatcher.charSetMatcher("ace".toCharArray());
+ assertEquals(1, matcher.isMatch(BUFFER2, 0));
+ assertEquals(0, matcher.isMatch(BUFFER2, 1));
+ assertEquals(1, matcher.isMatch(BUFFER2, 2));
+ assertEquals(0, matcher.isMatch(BUFFER2, 3));
+ assertEquals(1, matcher.isMatch(BUFFER2, 4));
+ assertEquals(0, matcher.isMatch(BUFFER2, 5));
+ assertSame(StrMatcher.noneMatcher(), StrMatcher.charSetMatcher(new char[0]));
+ assertSame(StrMatcher.noneMatcher(), StrMatcher.charSetMatcher((char[]) null));
+ assertTrue(StrMatcher.charSetMatcher("a".toCharArray()) instanceof StrMatcher.CharMatcher);
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testStringMatcher_String() {
+ final StrMatcher matcher = StrMatcher.stringMatcher("bc");
+ assertEquals(0, matcher.isMatch(BUFFER2, 0));
+ assertEquals(2, matcher.isMatch(BUFFER2, 1));
+ assertEquals(0, matcher.isMatch(BUFFER2, 2));
+ assertEquals(0, matcher.isMatch(BUFFER2, 3));
+ assertEquals(0, matcher.isMatch(BUFFER2, 4));
+ assertEquals(0, matcher.isMatch(BUFFER2, 5));
+ assertSame(StrMatcher.noneMatcher(), StrMatcher.stringMatcher(""));
+ assertSame(StrMatcher.noneMatcher(), StrMatcher.stringMatcher((String) null));
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testMatcherIndices() {
+ // remember that the API contract is tight for the isMatch() method
+ // all the onus is on the caller, so invalid inputs are not
+ // the concern of StrMatcher, and are not bugs
+ final StrMatcher matcher = StrMatcher.stringMatcher("bc");
+ assertEquals(2, matcher.isMatch(BUFFER2, 1, 1, BUFFER2.length));
+ assertEquals(2, matcher.isMatch(BUFFER2, 1, 0, 3));
+ assertEquals(0, matcher.isMatch(BUFFER2, 1, 0, 2));
+ }
+
+}
[08/14] [text] TEXT-23: Adding remainder of the o.a.c.l.text package
Posted by ch...@apache.org.
http://git-wip-us.apache.org/repos/asf/commons-text/blob/6f24aa45/src/main/java/org/apache/commons/text/StrLookup.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/StrLookup.java b/src/main/java/org/apache/commons/text/StrLookup.java
new file mode 100644
index 0000000..4626713
--- /dev/null
+++ b/src/main/java/org/apache/commons/text/StrLookup.java
@@ -0,0 +1,178 @@
+/*
+ * 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.util.Map;
+
+/**
+ * Lookup a String key to a String value.
+ * <p>
+ * This class represents the simplest form of a string to string map.
+ * It has a benefit over a map in that it can create the result on
+ * demand based on the key.
+ * <p>
+ * This class comes complete with various factory methods.
+ * If these do not suffice, you can subclass and implement your own matcher.
+ * <p>
+ * For example, it would be possible to implement a lookup that used the
+ * key as a primary key, and looked up the value on demand from the database
+ */
+public abstract class StrLookup<V> {
+
+ /**
+ * Lookup that always returns null.
+ */
+ private static final StrLookup<String> NONE_LOOKUP = new MapStrLookup<>(null);
+
+ /**
+ * Lookup based on system properties.
+ */
+ private static final StrLookup<String> SYSTEM_PROPERTIES_LOOKUP = new SystemPropertiesStrLookup();
+
+ //-----------------------------------------------------------------------
+ /**
+ * Returns a lookup which always returns null.
+ *
+ * @return a lookup that always returns null, not null
+ */
+ public static StrLookup<?> noneLookup() {
+ return NONE_LOOKUP;
+ }
+
+ /**
+ * Returns a new lookup which uses a copy of the current
+ * {@link System#getProperties() System properties}.
+ * <p>
+ * If a security manager blocked access to system properties, then null will
+ * be returned from every lookup.
+ * <p>
+ * If a null key is used, this lookup will throw a NullPointerException.
+ *
+ * @return a lookup using system properties, not null
+ */
+ public static StrLookup<String> systemPropertiesLookup() {
+ return SYSTEM_PROPERTIES_LOOKUP;
+ }
+
+ /**
+ * Returns a lookup which looks up values using a map.
+ * <p>
+ * If the map is null, then null will be returned from every lookup.
+ * The map result object is converted to a string using toString().
+ *
+ * @param <V> the type of the values supported by the lookup
+ * @param map the map of keys to values, may be null
+ * @return a lookup using the map, not null
+ */
+ public static <V> StrLookup<V> mapLookup(final Map<String, V> map) {
+ return new MapStrLookup<>(map);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Constructor.
+ */
+ protected StrLookup() {
+ super();
+ }
+
+ /**
+ * Looks up a String key to a String value.
+ * <p>
+ * The internal implementation may use any mechanism to return the value.
+ * The simplest implementation is to use a Map. However, virtually any
+ * implementation is possible.
+ * <p>
+ * For example, it would be possible to implement a lookup that used the
+ * key as a primary key, and looked up the value on demand from the database
+ * Or, a numeric based implementation could be created that treats the key
+ * as an integer, increments the value and return the result as a string -
+ * converting 1 to 2, 15 to 16 etc.
+ * <p>
+ * The {@link #lookup(String)} method always returns a String, regardless of
+ * the underlying data, by converting it as necessary. For example:
+ * <pre>
+ * Map<String, Object> map = new HashMap<String, Object>();
+ * map.put("number", Integer.valueOf(2));
+ * assertEquals("2", StrLookup.mapLookup(map).lookup("number"));
+ * </pre>
+ * @param key the key to be looked up, may be null
+ * @return the matching value, null if no match
+ */
+ public abstract String lookup(String key);
+
+ //-----------------------------------------------------------------------
+ /**
+ * Lookup implementation that uses a Map.
+ */
+ static class MapStrLookup<V> extends StrLookup<V> {
+
+ /** Map keys are variable names and value. */
+ private final Map<String, V> map;
+
+ /**
+ * Creates a new instance backed by a Map.
+ *
+ * @param map the map of keys to values, may be null
+ */
+ MapStrLookup(final Map<String, V> map) {
+ this.map = map;
+ }
+
+ /**
+ * Looks up a String key to a String value using the map.
+ * <p>
+ * If the map is null, then null is returned.
+ * The map result object is converted to a string using toString().
+ *
+ * @param key the key to be looked up, may be null
+ * @return the matching value, null if no match
+ */
+ @Override
+ public String lookup(final String key) {
+ if (map == null) {
+ return null;
+ }
+ final Object obj = map.get(key);
+ if (obj == null) {
+ return null;
+ }
+ return obj.toString();
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Lookup implementation based on system properties.
+ */
+ private static class SystemPropertiesStrLookup extends StrLookup<String> {
+ /**
+ * {@inheritDoc} This implementation directly accesses system properties.
+ */
+ @Override
+ public String lookup(final String key) {
+ if (key.length() > 0) {
+ try {
+ return System.getProperty(key);
+ } catch (final SecurityException scex) {
+ // Squelched. All lookup(String) will return null.
+ }
+ }
+ return null;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/commons-text/blob/6f24aa45/src/main/java/org/apache/commons/text/StrMatcher.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/StrMatcher.java b/src/main/java/org/apache/commons/text/StrMatcher.java
new file mode 100644
index 0000000..a115a9d
--- /dev/null
+++ b/src/main/java/org/apache/commons/text/StrMatcher.java
@@ -0,0 +1,437 @@
+/*
+ * 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.util.Arrays;
+
+/**
+ * A matcher class that can be queried to determine if a character array
+ * portion matches.
+ * <p>
+ * This class comes complete with various factory methods.
+ * If these do not suffice, you can subclass and implement your own matcher.
+ */
+public abstract class StrMatcher {
+
+ /**
+ * Matches the comma character.
+ */
+ private static final StrMatcher COMMA_MATCHER = new CharMatcher(',');
+ /**
+ * Matches the tab character.
+ */
+ private static final StrMatcher TAB_MATCHER = new CharMatcher('\t');
+ /**
+ * Matches the space character.
+ */
+ private static final StrMatcher SPACE_MATCHER = new CharMatcher(' ');
+ /**
+ * Matches the same characters as StringTokenizer,
+ * namely space, tab, newline, formfeed.
+ */
+ private static final StrMatcher SPLIT_MATCHER = new CharSetMatcher(" \t\n\r\f".toCharArray());
+ /**
+ * Matches the String trim() whitespace characters.
+ */
+ private static final StrMatcher TRIM_MATCHER = new TrimMatcher();
+ /**
+ * Matches the double quote character.
+ */
+ private static final StrMatcher SINGLE_QUOTE_MATCHER = new CharMatcher('\'');
+ /**
+ * Matches the double quote character.
+ */
+ private static final StrMatcher DOUBLE_QUOTE_MATCHER = new CharMatcher('"');
+ /**
+ * Matches the single or double quote character.
+ */
+ private static final StrMatcher QUOTE_MATCHER = new CharSetMatcher("'\"".toCharArray());
+ /**
+ * Matches no characters.
+ */
+ private static final StrMatcher NONE_MATCHER = new NoMatcher();
+
+ // -----------------------------------------------------------------------
+
+ /**
+ * Returns a matcher which matches the comma character.
+ *
+ * @return a matcher for a comma
+ */
+ public static StrMatcher commaMatcher() {
+ return COMMA_MATCHER;
+ }
+
+ /**
+ * Returns a matcher which matches the tab character.
+ *
+ * @return a matcher for a tab
+ */
+ public static StrMatcher tabMatcher() {
+ return TAB_MATCHER;
+ }
+
+ /**
+ * Returns a matcher which matches the space character.
+ *
+ * @return a matcher for a space
+ */
+ public static StrMatcher spaceMatcher() {
+ return SPACE_MATCHER;
+ }
+
+ /**
+ * Matches the same characters as StringTokenizer,
+ * namely space, tab, newline and formfeed.
+ *
+ * @return the split matcher
+ */
+ public static StrMatcher splitMatcher() {
+ return SPLIT_MATCHER;
+ }
+
+ /**
+ * Matches the String trim() whitespace characters.
+ *
+ * @return the trim matcher
+ */
+ public static StrMatcher trimMatcher() {
+ return TRIM_MATCHER;
+ }
+
+ /**
+ * Returns a matcher which matches the single quote character.
+ *
+ * @return a matcher for a single quote
+ */
+ public static StrMatcher singleQuoteMatcher() {
+ return SINGLE_QUOTE_MATCHER;
+ }
+
+ /**
+ * Returns a matcher which matches the double quote character.
+ *
+ * @return a matcher for a double quote
+ */
+ public static StrMatcher doubleQuoteMatcher() {
+ return DOUBLE_QUOTE_MATCHER;
+ }
+
+ /**
+ * Returns a matcher which matches the single or double quote character.
+ *
+ * @return a matcher for a single or double quote
+ */
+ public static StrMatcher quoteMatcher() {
+ return QUOTE_MATCHER;
+ }
+
+ /**
+ * Matches no characters.
+ *
+ * @return a matcher that matches nothing
+ */
+ public static StrMatcher noneMatcher() {
+ return NONE_MATCHER;
+ }
+
+ /**
+ * Constructor that creates a matcher from a character.
+ *
+ * @param ch the character to match, must not be null
+ * @return a new Matcher for the given char
+ */
+ public static StrMatcher charMatcher(final char ch) {
+ return new CharMatcher(ch);
+ }
+
+ /**
+ * Constructor that creates a matcher from a set of characters.
+ *
+ * @param chars the characters to match, null or empty matches nothing
+ * @return a new matcher for the given char[]
+ */
+ public static StrMatcher charSetMatcher(final char... chars) {
+ if (chars == null || chars.length == 0) {
+ return NONE_MATCHER;
+ }
+ if (chars.length == 1) {
+ return new CharMatcher(chars[0]);
+ }
+ return new CharSetMatcher(chars);
+ }
+
+ /**
+ * Constructor that creates a matcher from a string representing a set of characters.
+ *
+ * @param chars the characters to match, null or empty matches nothing
+ * @return a new Matcher for the given characters
+ */
+ public static StrMatcher charSetMatcher(final String chars) {
+ if (chars == null || chars.length() == 0) {
+ return NONE_MATCHER;
+ }
+ if (chars.length() == 1) {
+ return new CharMatcher(chars.charAt(0));
+ }
+ return new CharSetMatcher(chars.toCharArray());
+ }
+
+ /**
+ * Constructor that creates a matcher from a string.
+ *
+ * @param str the string to match, null or empty matches nothing
+ * @return a new Matcher for the given String
+ */
+ public static StrMatcher stringMatcher(final String str) {
+ if (str == null || str.length() == 0) {
+ return NONE_MATCHER;
+ }
+ return new StringMatcher(str);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Constructor.
+ */
+ protected StrMatcher() {
+ super();
+ }
+
+ /**
+ * Returns the number of matching characters, zero for no match.
+ * <p>
+ * This method is called to check for a match.
+ * The parameter <code>pos</code> represents the current position to be
+ * checked in the string <code>buffer</code> (a character array which must
+ * not be changed).
+ * The API guarantees that <code>pos</code> is a valid index for <code>buffer</code>.
+ * <p>
+ * The character array may be larger than the active area to be matched.
+ * Only values in the buffer between the specified indices may be accessed.
+ * <p>
+ * The matching code may check one character or many.
+ * It may check characters preceding <code>pos</code> as well as those
+ * after, so long as no checks exceed the bounds specified.
+ * <p>
+ * It must return zero for no match, or a positive number if a match was found.
+ * The number indicates the number of characters that matched.
+ *
+ * @param buffer the text content to match against, do not change
+ * @param pos the starting position for the match, valid for buffer
+ * @param bufferStart the first active index in the buffer, valid for buffer
+ * @param bufferEnd the end index (exclusive) of the active buffer, valid for buffer
+ * @return the number of matching characters, zero for no match
+ */
+ public abstract int isMatch(char[] buffer, int pos, int bufferStart, int bufferEnd);
+
+ /**
+ * Returns the number of matching characters, zero for no match.
+ * <p>
+ * This method is called to check for a match.
+ * The parameter <code>pos</code> represents the current position to be
+ * checked in the string <code>buffer</code> (a character array which must
+ * not be changed).
+ * The API guarantees that <code>pos</code> is a valid index for <code>buffer</code>.
+ * <p>
+ * The matching code may check one character or many.
+ * It may check characters preceding <code>pos</code> as well as those after.
+ * <p>
+ * It must return zero for no match, or a positive number if a match was found.
+ * The number indicates the number of characters that matched.
+ *
+ * @param buffer the text content to match against, do not change
+ * @param pos the starting position for the match, valid for buffer
+ * @return the number of matching characters, zero for no match
+ * @since 2.4
+ */
+ public int isMatch(final char[] buffer, final int pos) {
+ return isMatch(buffer, pos, 0, buffer.length);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Class used to define a set of characters for matching purposes.
+ */
+ static final class CharSetMatcher extends StrMatcher {
+ /** The set of characters to match. */
+ private final char[] chars;
+
+ /**
+ * Constructor that creates a matcher from a character array.
+ *
+ * @param chars the characters to match, must not be null
+ */
+ CharSetMatcher(final char chars[]) {
+ super();
+ this.chars = chars.clone();
+ Arrays.sort(this.chars);
+ }
+
+ /**
+ * Returns whether or not the given character matches.
+ *
+ * @param buffer the text content to match against, do not change
+ * @param pos the starting position for the match, valid for buffer
+ * @param bufferStart the first active index in the buffer, valid for buffer
+ * @param bufferEnd the end index of the active buffer, valid for buffer
+ * @return the number of matching characters, zero for no match
+ */
+ @Override
+ public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) {
+ return Arrays.binarySearch(chars, buffer[pos]) >= 0 ? 1 : 0;
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Class used to define a character for matching purposes.
+ */
+ static final class CharMatcher extends StrMatcher {
+ /** The character to match. */
+ private final char ch;
+
+ /**
+ * Constructor that creates a matcher that matches a single character.
+ *
+ * @param ch the character to match
+ */
+ CharMatcher(final char ch) {
+ super();
+ this.ch = ch;
+ }
+
+ /**
+ * Returns whether or not the given character matches.
+ *
+ * @param buffer the text content to match against, do not change
+ * @param pos the starting position for the match, valid for buffer
+ * @param bufferStart the first active index in the buffer, valid for buffer
+ * @param bufferEnd the end index of the active buffer, valid for buffer
+ * @return the number of matching characters, zero for no match
+ */
+ @Override
+ public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) {
+ return ch == buffer[pos] ? 1 : 0;
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Class used to define a set of characters for matching purposes.
+ */
+ static final class StringMatcher extends StrMatcher {
+ /** The string to match, as a character array. */
+ private final char[] chars;
+
+ /**
+ * Constructor that creates a matcher from a String.
+ *
+ * @param str the string to match, must not be null
+ */
+ StringMatcher(final String str) {
+ super();
+ chars = str.toCharArray();
+ }
+
+ /**
+ * Returns whether or not the given text matches the stored string.
+ *
+ * @param buffer the text content to match against, do not change
+ * @param pos the starting position for the match, valid for buffer
+ * @param bufferStart the first active index in the buffer, valid for buffer
+ * @param bufferEnd the end index of the active buffer, valid for buffer
+ * @return the number of matching characters, zero for no match
+ */
+ @Override
+ public int isMatch(final char[] buffer, int pos, final int bufferStart, final int bufferEnd) {
+ final int len = chars.length;
+ if (pos + len > bufferEnd) {
+ return 0;
+ }
+ for (int i = 0; i < chars.length; i++, pos++) {
+ if (chars[i] != buffer[pos]) {
+ return 0;
+ }
+ }
+ return len;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + ' ' + Arrays.toString(chars);
+ }
+
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Class used to match no characters.
+ */
+ static final class NoMatcher extends StrMatcher {
+
+ /**
+ * Constructs a new instance of <code>NoMatcher</code>.
+ */
+ NoMatcher() {
+ super();
+ }
+
+ /**
+ * Always returns <code>false</code>.
+ *
+ * @param buffer the text content to match against, do not change
+ * @param pos the starting position for the match, valid for buffer
+ * @param bufferStart the first active index in the buffer, valid for buffer
+ * @param bufferEnd the end index of the active buffer, valid for buffer
+ * @return the number of matching characters, zero for no match
+ */
+ @Override
+ public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) {
+ return 0;
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Class used to match whitespace as per trim().
+ */
+ static final class TrimMatcher extends StrMatcher {
+
+ /**
+ * Constructs a new instance of <code>TrimMatcher</code>.
+ */
+ TrimMatcher() {
+ super();
+ }
+
+ /**
+ * Returns whether or not the given character matches.
+ *
+ * @param buffer the text content to match against, do not change
+ * @param pos the starting position for the match, valid for buffer
+ * @param bufferStart the first active index in the buffer, valid for buffer
+ * @param bufferEnd the end index of the active buffer, valid for buffer
+ * @return the number of matching characters, zero for no match
+ */
+ @Override
+ public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) {
+ return buffer[pos] <= 32 ? 1 : 0;
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/commons-text/blob/6f24aa45/src/main/java/org/apache/commons/text/StrSubstitutor.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/StrSubstitutor.java b/src/main/java/org/apache/commons/text/StrSubstitutor.java
new file mode 100644
index 0000000..73a3cc1
--- /dev/null
+++ b/src/main/java/org/apache/commons/text/StrSubstitutor.java
@@ -0,0 +1,1227 @@
+/*
+ * 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.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Substitutes variables within a string by values.
+ * <p>
+ * This class takes a piece of text and substitutes all the variables within it.
+ * The default definition of a variable is <code>${variableName}</code>.
+ * The prefix and suffix can be changed via constructors and set methods.
+ * <p>
+ * Variable values are typically resolved from a map, but could also be resolved
+ * from system properties, or by supplying a custom variable resolver.
+ * <p>
+ * The simplest example is to use this class to replace Java System properties. For example:
+ * <pre>
+ * StrSubstitutor.replaceSystemProperties(
+ * "You are running with java.version = ${java.version} and os.name = ${os.name}.");
+ * </pre>
+ * <p>
+ * Typical usage of this class follows the following pattern: First an instance is created
+ * and initialized with the map that contains the values for the available variables.
+ * If a prefix and/or suffix for variables should be used other than the default ones,
+ * the appropriate settings can be performed. After that the <code>replace()</code>
+ * method can be called passing in the source text for interpolation. In the returned
+ * text all variable references (as long as their values are known) will be resolved.
+ * The following example demonstrates this:
+ * <pre>
+ * Map valuesMap = HashMap();
+ * valuesMap.put("animal", "quick brown fox");
+ * valuesMap.put("target", "lazy dog");
+ * String templateString = "The ${animal} jumped over the ${target}.";
+ * StrSubstitutor sub = new StrSubstitutor(valuesMap);
+ * String resolvedString = sub.replace(templateString);
+ * </pre>
+ * yielding:
+ * <pre>
+ * The quick brown fox jumped over the lazy dog.
+ * </pre>
+ * <p>
+ * Also, this class allows to set a default value for unresolved variables.
+ * The default value for a variable can be appended to the variable name after the variable
+ * default value delimiter. The default value of the variable default value delimiter is ':-',
+ * as in bash and other *nix shells, as those are arguably where the default ${} delimiter set originated.
+ * The variable default value delimiter can be manually set by calling {@link #setValueDelimiterMatcher(StrMatcher)},
+ * {@link #setValueDelimiter(char)} or {@link #setValueDelimiter(String)}.
+ * The following shows an example with variable default value settings:
+ * <pre>
+ * Map valuesMap = HashMap();
+ * valuesMap.put("animal", "quick brown fox");
+ * valuesMap.put("target", "lazy dog");
+ * String templateString = "The ${animal} jumped over the ${target}. ${undefined.number:-1234567890}.";
+ * StrSubstitutor sub = new StrSubstitutor(valuesMap);
+ * String resolvedString = sub.replace(templateString);
+ * </pre>
+ * yielding:
+ * <pre>
+ * The quick brown fox jumped over the lazy dog. 1234567890.
+ * </pre>
+ * <p>
+ * In addition to this usage pattern there are some static convenience methods that
+ * cover the most common use cases. These methods can be used without the need of
+ * manually creating an instance. However if multiple replace operations are to be
+ * performed, creating and reusing an instance of this class will be more efficient.
+ * <p>
+ * Variable replacement works in a recursive way. Thus, if a variable value contains
+ * a variable then that variable will also be replaced. Cyclic replacements are
+ * detected and will cause an exception to be thrown.
+ * <p>
+ * Sometimes the interpolation's result must contain a variable prefix. As an example
+ * take the following source text:
+ * <pre>
+ * The variable ${${name}} must be used.
+ * </pre>
+ * Here only the variable's name referred to in the text should be replaced resulting
+ * in the text (assuming that the value of the <code>name</code> variable is <code>x</code>):
+ * <pre>
+ * The variable ${x} must be used.
+ * </pre>
+ * To achieve this effect there are two possibilities: Either set a different prefix
+ * and suffix for variables which do not conflict with the result text you want to
+ * produce. The other possibility is to use the escape character, by default '$'.
+ * If this character is placed before a variable reference, this reference is ignored
+ * and won't be replaced. For example:
+ * <pre>
+ * The variable $${${name}} must be used.
+ * </pre>
+ * <p>
+ * In some complex scenarios you might even want to perform substitution in the
+ * names of variables, for instance
+ * <pre>
+ * ${jre-${java.specification.version}}
+ * </pre>
+ * <code>StrSubstitutor</code> supports this recursive substitution in variable
+ * names, but it has to be enabled explicitly by setting the
+ * {@link #setEnableSubstitutionInVariables(boolean) enableSubstitutionInVariables}
+ * property to <b>true</b>.
+ * <p>This class is <b>not</b> thread safe.</p>
+ */
+public class StrSubstitutor {
+
+ /**
+ * Constant for the default escape character.
+ */
+ public static final char DEFAULT_ESCAPE = '$';
+ /**
+ * Constant for the default variable prefix.
+ */
+ public static final StrMatcher DEFAULT_PREFIX = StrMatcher.stringMatcher("${");
+ /**
+ * Constant for the default variable suffix.
+ */
+ public static final StrMatcher DEFAULT_SUFFIX = StrMatcher.stringMatcher("}");
+ /**
+ * Constant for the default value delimiter of a variable.
+ * @since 3.2
+ */
+ public static final StrMatcher DEFAULT_VALUE_DELIMITER = StrMatcher.stringMatcher(":-");
+
+ /**
+ * Stores the escape character.
+ */
+ private char escapeChar;
+ /**
+ * Stores the variable prefix.
+ */
+ private StrMatcher prefixMatcher;
+ /**
+ * Stores the variable suffix.
+ */
+ private StrMatcher suffixMatcher;
+ /**
+ * Stores the default variable value delimiter
+ */
+ private StrMatcher valueDelimiterMatcher;
+ /**
+ * Variable resolution is delegated to an implementor of VariableResolver.
+ */
+ private StrLookup<?> variableResolver;
+ /**
+ * The flag whether substitution in variable names is enabled.
+ */
+ private boolean enableSubstitutionInVariables;
+ /**
+ * Whether escapes should be preserved. Default is false;
+ */
+ private boolean preserveEscapes = false;
+
+ //-----------------------------------------------------------------------
+ /**
+ * Replaces all the occurrences of variables in the given source object with
+ * their matching values from the map.
+ *
+ * @param <V> the type of the values in the map
+ * @param source the source text containing the variables to substitute, null returns null
+ * @param valueMap the map with the values, may be null
+ * @return the result of the replace operation
+ */
+ public static <V> String replace(final Object source, final Map<String, V> valueMap) {
+ return new StrSubstitutor(valueMap).replace(source);
+ }
+
+ /**
+ * Replaces all the occurrences of variables in the given source object with
+ * their matching values from the map. This method allows to specifiy a
+ * custom variable prefix and suffix
+ *
+ * @param <V> the type of the values in the map
+ * @param source the source text containing the variables to substitute, null returns null
+ * @param valueMap the map with the values, may be null
+ * @param prefix the prefix of variables, not null
+ * @param suffix the suffix of variables, not null
+ * @return the result of the replace operation
+ * @throws IllegalArgumentException if the prefix or suffix is null
+ */
+ public static <V> String replace(final Object source, final Map<String, V> valueMap, final String prefix, final String suffix) {
+ return new StrSubstitutor(valueMap, prefix, suffix).replace(source);
+ }
+
+ /**
+ * Replaces all the occurrences of variables in the given source object with their matching
+ * values from the properties.
+ *
+ * @param source the source text containing the variables to substitute, null returns null
+ * @param valueProperties the properties with values, may be null
+ * @return the result of the replace operation
+ */
+ public static String replace(final Object source, final Properties valueProperties) {
+ if (valueProperties == null) {
+ return source.toString();
+ }
+ final Map<String,String> valueMap = new HashMap<>();
+ final Enumeration<?> propNames = valueProperties.propertyNames();
+ while (propNames.hasMoreElements()) {
+ final String propName = (String)propNames.nextElement();
+ final String propValue = valueProperties.getProperty(propName);
+ valueMap.put(propName, propValue);
+ }
+ return StrSubstitutor.replace(source, valueMap);
+ }
+
+ /**
+ * Replaces all the occurrences of variables in the given source object with
+ * their matching values from the system properties.
+ *
+ * @param source the source text containing the variables to substitute, null returns null
+ * @return the result of the replace operation
+ */
+ public static String replaceSystemProperties(final Object source) {
+ return new StrSubstitutor(StrLookup.systemPropertiesLookup()).replace(source);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Creates a new instance with defaults for variable prefix and suffix
+ * and the escaping character.
+ */
+ public StrSubstitutor() {
+ this((StrLookup<?>) null, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
+ }
+
+ /**
+ * Creates a new instance and initializes it. Uses defaults for variable
+ * prefix and suffix and the escaping character.
+ *
+ * @param <V> the type of the values in the map
+ * @param valueMap the map with the variables' values, may be null
+ */
+ public <V> StrSubstitutor(final Map<String, V> valueMap) {
+ this(StrLookup.mapLookup(valueMap), DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
+ }
+
+ /**
+ * Creates a new instance and initializes it. Uses a default escaping character.
+ *
+ * @param <V> the type of the values in the map
+ * @param valueMap the map with the variables' values, may be null
+ * @param prefix the prefix for variables, not null
+ * @param suffix the suffix for variables, not null
+ * @throws IllegalArgumentException if the prefix or suffix is null
+ */
+ public <V> StrSubstitutor(final Map<String, V> valueMap, final String prefix, final String suffix) {
+ this(StrLookup.mapLookup(valueMap), prefix, suffix, DEFAULT_ESCAPE);
+ }
+
+ /**
+ * Creates a new instance and initializes it.
+ *
+ * @param <V> the type of the values in the map
+ * @param valueMap the map with the variables' values, may be null
+ * @param prefix the prefix for variables, not null
+ * @param suffix the suffix for variables, not null
+ * @param escape the escape character
+ * @throws IllegalArgumentException if the prefix or suffix is null
+ */
+ public <V> StrSubstitutor(final Map<String, V> valueMap, final String prefix, final String suffix,
+ final char escape) {
+ this(StrLookup.mapLookup(valueMap), prefix, suffix, escape);
+ }
+
+ /**
+ * Creates a new instance and initializes it.
+ *
+ * @param <V> the type of the values in the map
+ * @param valueMap the map with the variables' values, may be null
+ * @param prefix the prefix for variables, not null
+ * @param suffix the suffix for variables, not null
+ * @param escape the escape character
+ * @param valueDelimiter the variable default value delimiter, may be null
+ * @throws IllegalArgumentException if the prefix or suffix is null
+ * @since 3.2
+ */
+ public <V> StrSubstitutor(final Map<String, V> valueMap, final String prefix, final String suffix,
+ final char escape, final String valueDelimiter) {
+ this(StrLookup.mapLookup(valueMap), prefix, suffix, escape, valueDelimiter);
+ }
+
+ /**
+ * Creates a new instance and initializes it.
+ *
+ * @param variableResolver the variable resolver, may be null
+ */
+ public StrSubstitutor(final StrLookup<?> variableResolver) {
+ this(variableResolver, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
+ }
+
+ /**
+ * Creates a new instance and initializes it.
+ *
+ * @param variableResolver the variable resolver, may be null
+ * @param prefix the prefix for variables, not null
+ * @param suffix the suffix for variables, not null
+ * @param escape the escape character
+ * @throws IllegalArgumentException if the prefix or suffix is null
+ */
+ public StrSubstitutor(final StrLookup<?> variableResolver, final String prefix, final String suffix,
+ final char escape) {
+ this.setVariableResolver(variableResolver);
+ this.setVariablePrefix(prefix);
+ this.setVariableSuffix(suffix);
+ this.setEscapeChar(escape);
+ this.setValueDelimiterMatcher(DEFAULT_VALUE_DELIMITER);
+ }
+
+ /**
+ * Creates a new instance and initializes it.
+ *
+ * @param variableResolver the variable resolver, may be null
+ * @param prefix the prefix for variables, not null
+ * @param suffix the suffix for variables, not null
+ * @param escape the escape character
+ * @param valueDelimiter the variable default value delimiter string, may be null
+ * @throws IllegalArgumentException if the prefix or suffix is null
+ * @since 3.2
+ */
+ public StrSubstitutor(final StrLookup<?> variableResolver, final String prefix, final String suffix,
+ final char escape, final String valueDelimiter) {
+ this.setVariableResolver(variableResolver);
+ this.setVariablePrefix(prefix);
+ this.setVariableSuffix(suffix);
+ this.setEscapeChar(escape);
+ this.setValueDelimiter(valueDelimiter);
+ }
+
+ /**
+ * Creates a new instance and initializes it.
+ *
+ * @param variableResolver the variable resolver, may be null
+ * @param prefixMatcher the prefix for variables, not null
+ * @param suffixMatcher the suffix for variables, not null
+ * @param escape the escape character
+ * @throws IllegalArgumentException if the prefix or suffix is null
+ */
+ public StrSubstitutor(
+ final StrLookup<?> variableResolver, final StrMatcher prefixMatcher, final StrMatcher suffixMatcher,
+ final char escape) {
+ this(variableResolver, prefixMatcher, suffixMatcher, escape, DEFAULT_VALUE_DELIMITER);
+ }
+
+ /**
+ * Creates a new instance and initializes it.
+ *
+ * @param variableResolver the variable resolver, may be null
+ * @param prefixMatcher the prefix for variables, not null
+ * @param suffixMatcher the suffix for variables, not null
+ * @param escape the escape character
+ * @param valueDelimiterMatcher the variable default value delimiter matcher, may be null
+ * @throws IllegalArgumentException if the prefix or suffix is null
+ * @since 3.2
+ */
+ public StrSubstitutor(
+ final StrLookup<?> variableResolver, final StrMatcher prefixMatcher, final StrMatcher suffixMatcher,
+ final char escape, final StrMatcher valueDelimiterMatcher) {
+ this.setVariableResolver(variableResolver);
+ this.setVariablePrefixMatcher(prefixMatcher);
+ this.setVariableSuffixMatcher(suffixMatcher);
+ this.setEscapeChar(escape);
+ this.setValueDelimiterMatcher(valueDelimiterMatcher);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Replaces all the occurrences of variables with their matching values
+ * from the resolver using the given source string as a template.
+ *
+ * @param source the string to replace in, null returns null
+ * @return the result of the replace operation
+ */
+ public String replace(final String source) {
+ if (source == null) {
+ return null;
+ }
+ final StrBuilder buf = new StrBuilder(source);
+ if (substitute(buf, 0, source.length()) == false) {
+ return source;
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Replaces all the occurrences of variables with their matching values
+ * from the resolver using the given source string as a template.
+ * <p>
+ * Only the specified portion of the string will be processed.
+ * The rest of the string is not processed, and is not returned.
+ *
+ * @param source the string to replace in, null returns null
+ * @param offset the start offset within the array, must be valid
+ * @param length the length within the array to be processed, must be valid
+ * @return the result of the replace operation
+ */
+ public String replace(final String source, final int offset, final int length) {
+ if (source == null) {
+ return null;
+ }
+ final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
+ if (substitute(buf, 0, length) == false) {
+ return source.substring(offset, offset + length);
+ }
+ return buf.toString();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Replaces all the occurrences of variables with their matching values
+ * from the resolver using the given source array as a template.
+ * The array is not altered by this method.
+ *
+ * @param source the character array to replace in, not altered, null returns null
+ * @return the result of the replace operation
+ */
+ public String replace(final char[] source) {
+ if (source == null) {
+ return null;
+ }
+ final StrBuilder buf = new StrBuilder(source.length).append(source);
+ substitute(buf, 0, source.length);
+ return buf.toString();
+ }
+
+ /**
+ * Replaces all the occurrences of variables with their matching values
+ * from the resolver using the given source array as a template.
+ * The array is not altered by this method.
+ * <p>
+ * Only the specified portion of the array will be processed.
+ * The rest of the array is not processed, and is not returned.
+ *
+ * @param source the character array to replace in, not altered, null returns null
+ * @param offset the start offset within the array, must be valid
+ * @param length the length within the array to be processed, must be valid
+ * @return the result of the replace operation
+ */
+ public String replace(final char[] source, final int offset, final int length) {
+ if (source == null) {
+ return null;
+ }
+ final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
+ substitute(buf, 0, length);
+ return buf.toString();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Replaces all the occurrences of variables with their matching values
+ * from the resolver using the given source buffer as a template.
+ * The buffer is not altered by this method.
+ *
+ * @param source the buffer to use as a template, not changed, null returns null
+ * @return the result of the replace operation
+ */
+ public String replace(final StringBuffer source) {
+ if (source == null) {
+ return null;
+ }
+ final StrBuilder buf = new StrBuilder(source.length()).append(source);
+ substitute(buf, 0, buf.length());
+ return buf.toString();
+ }
+
+ /**
+ * Replaces all the occurrences of variables with their matching values
+ * from the resolver using the given source buffer as a template.
+ * The buffer is not altered by this method.
+ * <p>
+ * Only the specified portion of the buffer will be processed.
+ * The rest of the buffer is not processed, and is not returned.
+ *
+ * @param source the buffer to use as a template, not changed, null returns null
+ * @param offset the start offset within the array, must be valid
+ * @param length the length within the array to be processed, must be valid
+ * @return the result of the replace operation
+ */
+ public String replace(final StringBuffer source, final int offset, final int length) {
+ if (source == null) {
+ return null;
+ }
+ final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
+ substitute(buf, 0, length);
+ return buf.toString();
+ }
+
+ /**
+ * Replaces all the occurrences of variables with their matching values
+ * from the resolver using the given source as a template.
+ * The source is not altered by this method.
+ *
+ * @param source the buffer to use as a template, not changed, null returns null
+ * @return the result of the replace operation
+ * @since 3.2
+ */
+ public String replace(final CharSequence source) {
+ if (source == null) {
+ return null;
+ }
+ return replace(source, 0, source.length());
+ }
+
+ /**
+ * Replaces all the occurrences of variables with their matching values
+ * from the resolver using the given source as a template.
+ * The source is not altered by this method.
+ * <p>
+ * Only the specified portion of the buffer will be processed.
+ * The rest of the buffer is not processed, and is not returned.
+ *
+ * @param source the buffer to use as a template, not changed, null returns null
+ * @param offset the start offset within the array, must be valid
+ * @param length the length within the array to be processed, must be valid
+ * @return the result of the replace operation
+ * @since 3.2
+ */
+ public String replace(final CharSequence source, final int offset, final int length) {
+ if (source == null) {
+ return null;
+ }
+ final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
+ substitute(buf, 0, length);
+ return buf.toString();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Replaces all the occurrences of variables with their matching values
+ * from the resolver using the given source builder as a template.
+ * The builder is not altered by this method.
+ *
+ * @param source the builder to use as a template, not changed, null returns null
+ * @return the result of the replace operation
+ */
+ public String replace(final StrBuilder source) {
+ if (source == null) {
+ return null;
+ }
+ final StrBuilder buf = new StrBuilder(source.length()).append(source);
+ substitute(buf, 0, buf.length());
+ return buf.toString();
+ }
+
+ /**
+ * Replaces all the occurrences of variables with their matching values
+ * from the resolver using the given source builder as a template.
+ * The builder is not altered by this method.
+ * <p>
+ * Only the specified portion of the builder will be processed.
+ * The rest of the builder is not processed, and is not returned.
+ *
+ * @param source the builder to use as a template, not changed, null returns null
+ * @param offset the start offset within the array, must be valid
+ * @param length the length within the array to be processed, must be valid
+ * @return the result of the replace operation
+ */
+ public String replace(final StrBuilder source, final int offset, final int length) {
+ if (source == null) {
+ return null;
+ }
+ final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
+ substitute(buf, 0, length);
+ return buf.toString();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Replaces all the occurrences of variables in the given source object with
+ * their matching values from the resolver. The input source object is
+ * converted to a string using <code>toString</code> and is not altered.
+ *
+ * @param source the source to replace in, null returns null
+ * @return the result of the replace operation
+ */
+ public String replace(final Object source) {
+ if (source == null) {
+ return null;
+ }
+ final StrBuilder buf = new StrBuilder().append(source);
+ substitute(buf, 0, buf.length());
+ return buf.toString();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Replaces all the occurrences of variables within the given source buffer
+ * with their matching values from the resolver.
+ * The buffer is updated with the result.
+ *
+ * @param source the buffer to replace in, updated, null returns zero
+ * @return true if altered
+ */
+ public boolean replaceIn(final StringBuffer source) {
+ if (source == null) {
+ return false;
+ }
+ return replaceIn(source, 0, source.length());
+ }
+
+ /**
+ * Replaces all the occurrences of variables within the given source buffer
+ * with their matching values from the resolver.
+ * The buffer is updated with the result.
+ * <p>
+ * Only the specified portion of the buffer will be processed.
+ * The rest of the buffer is not processed, but it is not deleted.
+ *
+ * @param source the buffer to replace in, updated, null returns zero
+ * @param offset the start offset within the array, must be valid
+ * @param length the length within the buffer to be processed, must be valid
+ * @return true if altered
+ */
+ public boolean replaceIn(final StringBuffer source, final int offset, final int length) {
+ if (source == null) {
+ return false;
+ }
+ final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
+ if (substitute(buf, 0, length) == false) {
+ return false;
+ }
+ source.replace(offset, offset + length, buf.toString());
+ return true;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Replaces all the occurrences of variables within the given source buffer
+ * with their matching values from the resolver.
+ * The buffer is updated with the result.
+ *
+ * @param source the buffer to replace in, updated, null returns zero
+ * @return true if altered
+ * @since 3.2
+ */
+ public boolean replaceIn(final StringBuilder source) {
+ if (source == null) {
+ return false;
+ }
+ return replaceIn(source, 0, source.length());
+ }
+
+ /**
+ * Replaces all the occurrences of variables within the given source builder
+ * with their matching values from the resolver.
+ * The builder is updated with the result.
+ * <p>
+ * Only the specified portion of the buffer will be processed.
+ * The rest of the buffer is not processed, but it is not deleted.
+ *
+ * @param source the buffer to replace in, updated, null returns zero
+ * @param offset the start offset within the array, must be valid
+ * @param length the length within the buffer to be processed, must be valid
+ * @return true if altered
+ * @since 3.2
+ */
+ public boolean replaceIn(final StringBuilder source, final int offset, final int length) {
+ if (source == null) {
+ return false;
+ }
+ final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
+ if (substitute(buf, 0, length) == false) {
+ return false;
+ }
+ source.replace(offset, offset + length, buf.toString());
+ return true;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Replaces all the occurrences of variables within the given source
+ * builder with their matching values from the resolver.
+ *
+ * @param source the builder to replace in, updated, null returns zero
+ * @return true if altered
+ */
+ public boolean replaceIn(final StrBuilder source) {
+ if (source == null) {
+ return false;
+ }
+ return substitute(source, 0, source.length());
+ }
+
+ /**
+ * Replaces all the occurrences of variables within the given source
+ * builder with their matching values from the resolver.
+ * <p>
+ * Only the specified portion of the builder will be processed.
+ * The rest of the builder is not processed, but it is not deleted.
+ *
+ * @param source the builder to replace in, null returns zero
+ * @param offset the start offset within the array, must be valid
+ * @param length the length within the builder to be processed, must be valid
+ * @return true if altered
+ */
+ public boolean replaceIn(final StrBuilder source, final int offset, final int length) {
+ if (source == null) {
+ return false;
+ }
+ return substitute(source, offset, length);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Internal method that substitutes the variables.
+ * <p>
+ * Most users of this class do not need to call this method. This method will
+ * be called automatically by another (public) method.
+ * <p>
+ * Writers of subclasses can override this method if they need access to
+ * the substitution process at the start or end.
+ *
+ * @param buf the string builder to substitute into, not null
+ * @param offset the start offset within the builder, must be valid
+ * @param length the length within the builder to be processed, must be valid
+ * @return true if altered
+ */
+ protected boolean substitute(final StrBuilder buf, final int offset, final int length) {
+ return substitute(buf, offset, length, null) > 0;
+ }
+
+ /**
+ * Recursive handler for multiple levels of interpolation. This is the main
+ * interpolation method, which resolves the values of all variable references
+ * contained in the passed in text.
+ *
+ * @param buf the string builder to substitute into, not null
+ * @param offset the start offset within the builder, must be valid
+ * @param length the length within the builder to be processed, must be valid
+ * @param priorVariables the stack keeping track of the replaced variables, may be null
+ * @return the length change that occurs, unless priorVariables is null when the int
+ * represents a boolean flag as to whether any change occurred.
+ */
+ private int substitute(final StrBuilder buf, final int offset, final int length, List<String> priorVariables) {
+ final StrMatcher pfxMatcher = getVariablePrefixMatcher();
+ final StrMatcher suffMatcher = getVariableSuffixMatcher();
+ final char escape = getEscapeChar();
+ final StrMatcher valueDelimMatcher = getValueDelimiterMatcher();
+ final boolean substitutionInVariablesEnabled = isEnableSubstitutionInVariables();
+
+ final boolean top = priorVariables == null;
+ boolean altered = false;
+ int lengthChange = 0;
+ char[] chars = buf.buffer;
+ int bufEnd = offset + length;
+ int pos = offset;
+ while (pos < bufEnd) {
+ final int startMatchLen = pfxMatcher.isMatch(chars, pos, offset,
+ bufEnd);
+ if (startMatchLen == 0) {
+ pos++;
+ } else {
+ // found variable start marker
+ if (pos > offset && chars[pos - 1] == escape) {
+ // escaped
+ if (preserveEscapes) {
+ pos++;
+ continue;
+ }
+ buf.deleteCharAt(pos - 1);
+ chars = buf.buffer; // in case buffer was altered
+ lengthChange--;
+ altered = true;
+ bufEnd--;
+ } else {
+ // find suffix
+ final int startPos = pos;
+ pos += startMatchLen;
+ int endMatchLen = 0;
+ int nestedVarCount = 0;
+ while (pos < bufEnd) {
+ if (substitutionInVariablesEnabled
+ && (endMatchLen = pfxMatcher.isMatch(chars,
+ pos, offset, bufEnd)) != 0) {
+ // found a nested variable start
+ nestedVarCount++;
+ pos += endMatchLen;
+ continue;
+ }
+
+ endMatchLen = suffMatcher.isMatch(chars, pos, offset,
+ bufEnd);
+ if (endMatchLen == 0) {
+ pos++;
+ } else {
+ // found variable end marker
+ if (nestedVarCount == 0) {
+ String varNameExpr = new String(chars, startPos
+ + startMatchLen, pos - startPos
+ - startMatchLen);
+ if (substitutionInVariablesEnabled) {
+ final StrBuilder bufName = new StrBuilder(varNameExpr);
+ substitute(bufName, 0, bufName.length());
+ varNameExpr = bufName.toString();
+ }
+ pos += endMatchLen;
+ final int endPos = pos;
+
+ String varName = varNameExpr;
+ String varDefaultValue = null;
+
+ if (valueDelimMatcher != null) {
+ final char [] varNameExprChars = varNameExpr.toCharArray();
+ int valueDelimiterMatchLen = 0;
+ for (int i = 0; i < varNameExprChars.length; i++) {
+ // if there's any nested variable when nested variable substitution disabled, then stop resolving name and default value.
+ if (!substitutionInVariablesEnabled
+ && pfxMatcher.isMatch(varNameExprChars, i, i, varNameExprChars.length) != 0) {
+ break;
+ }
+ if ((valueDelimiterMatchLen = valueDelimMatcher.isMatch(varNameExprChars, i)) != 0) {
+ varName = varNameExpr.substring(0, i);
+ varDefaultValue = varNameExpr.substring(i + valueDelimiterMatchLen);
+ break;
+ }
+ }
+ }
+
+ // on the first call initialize priorVariables
+ if (priorVariables == null) {
+ priorVariables = new ArrayList<>();
+ priorVariables.add(new String(chars,
+ offset, length));
+ }
+
+ // handle cyclic substitution
+ checkCyclicSubstitution(varName, priorVariables);
+ priorVariables.add(varName);
+
+ // resolve the variable
+ String varValue = resolveVariable(varName, buf,
+ startPos, endPos);
+ if (varValue == null) {
+ varValue = varDefaultValue;
+ }
+ if (varValue != null) {
+ // recursive replace
+ final int varLen = varValue.length();
+ buf.replace(startPos, endPos, varValue);
+ altered = true;
+ int change = substitute(buf, startPos,
+ varLen, priorVariables);
+ change = change
+ + varLen - (endPos - startPos);
+ pos += change;
+ bufEnd += change;
+ lengthChange += change;
+ chars = buf.buffer; // in case buffer was
+ // altered
+ }
+
+ // remove variable from the cyclic stack
+ priorVariables
+ .remove(priorVariables.size() - 1);
+ break;
+ }
+ nestedVarCount--;
+ pos += endMatchLen;
+ }
+ }
+ }
+ }
+ }
+ if (top) {
+ return altered ? 1 : 0;
+ }
+ return lengthChange;
+ }
+
+ /**
+ * Checks if the specified variable is already in the stack (list) of variables.
+ *
+ * @param varName the variable name to check
+ * @param priorVariables the list of prior variables
+ */
+ private void checkCyclicSubstitution(final String varName, final List<String> priorVariables) {
+ if (priorVariables.contains(varName) == false) {
+ return;
+ }
+ final StrBuilder buf = new StrBuilder(256);
+ buf.append("Infinite loop in property interpolation of ");
+ buf.append(priorVariables.remove(0));
+ buf.append(": ");
+ buf.appendWithSeparators(priorVariables, "->");
+ throw new IllegalStateException(buf.toString());
+ }
+
+ /**
+ * Internal method that resolves the value of a variable.
+ * <p>
+ * Most users of this class do not need to call this method. This method is
+ * called automatically by the substitution process.
+ * <p>
+ * Writers of subclasses can override this method if they need to alter
+ * how each substitution occurs. The method is passed the variable's name
+ * and must return the corresponding value. This implementation uses the
+ * {@link #getVariableResolver()} with the variable's name as the key.
+ *
+ * @param variableName the name of the variable, not null
+ * @param buf the buffer where the substitution is occurring, not null
+ * @param startPos the start position of the variable including the prefix, valid
+ * @param endPos the end position of the variable including the suffix, valid
+ * @return the variable's value or <b>null</b> if the variable is unknown
+ */
+ protected String resolveVariable(final String variableName, final StrBuilder buf, final int startPos, final int endPos) {
+ final StrLookup<?> resolver = getVariableResolver();
+ if (resolver == null) {
+ return null;
+ }
+ return resolver.lookup(variableName);
+ }
+
+ // Escape
+ //-----------------------------------------------------------------------
+ /**
+ * Returns the escape character.
+ *
+ * @return the character used for escaping variable references
+ */
+ public char getEscapeChar() {
+ return this.escapeChar;
+ }
+
+ /**
+ * Sets the escape character.
+ * If this character is placed before a variable reference in the source
+ * text, this variable will be ignored.
+ *
+ * @param escapeCharacter the escape character (0 for disabling escaping)
+ */
+ public void setEscapeChar(final char escapeCharacter) {
+ this.escapeChar = escapeCharacter;
+ }
+
+ // Prefix
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the variable prefix matcher currently in use.
+ * <p>
+ * The variable prefix is the characer or characters that identify the
+ * start of a variable. This prefix is expressed in terms of a matcher
+ * allowing advanced prefix matches.
+ *
+ * @return the prefix matcher in use
+ */
+ public StrMatcher getVariablePrefixMatcher() {
+ return prefixMatcher;
+ }
+
+ /**
+ * Sets the variable prefix matcher currently in use.
+ * <p>
+ * The variable prefix is the characer or characters that identify the
+ * start of a variable. This prefix is expressed in terms of a matcher
+ * allowing advanced prefix matches.
+ *
+ * @param prefixMatcher the prefix matcher to use, null ignored
+ * @return this, to enable chaining
+ * @throws IllegalArgumentException if the prefix matcher is null
+ */
+ public StrSubstitutor setVariablePrefixMatcher(final StrMatcher prefixMatcher) {
+ if (prefixMatcher == null) {
+ throw new IllegalArgumentException("Variable prefix matcher must not be null!");
+ }
+ this.prefixMatcher = prefixMatcher;
+ return this;
+ }
+
+ /**
+ * Sets the variable prefix to use.
+ * <p>
+ * The variable prefix is the character or characters that identify the
+ * start of a variable. This method allows a single character prefix to
+ * be easily set.
+ *
+ * @param prefix the prefix character to use
+ * @return this, to enable chaining
+ */
+ public StrSubstitutor setVariablePrefix(final char prefix) {
+ return setVariablePrefixMatcher(StrMatcher.charMatcher(prefix));
+ }
+
+ /**
+ * Sets the variable prefix to use.
+ * <p>
+ * The variable prefix is the characer or characters that identify the
+ * start of a variable. This method allows a string prefix to be easily set.
+ *
+ * @param prefix the prefix for variables, not null
+ * @return this, to enable chaining
+ * @throws IllegalArgumentException if the prefix is null
+ */
+ public StrSubstitutor setVariablePrefix(final String prefix) {
+ if (prefix == null) {
+ throw new IllegalArgumentException("Variable prefix must not be null!");
+ }
+ return setVariablePrefixMatcher(StrMatcher.stringMatcher(prefix));
+ }
+
+ // Suffix
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the variable suffix matcher currently in use.
+ * <p>
+ * The variable suffix is the characer or characters that identify the
+ * end of a variable. This suffix is expressed in terms of a matcher
+ * allowing advanced suffix matches.
+ *
+ * @return the suffix matcher in use
+ */
+ public StrMatcher getVariableSuffixMatcher() {
+ return suffixMatcher;
+ }
+
+ /**
+ * Sets the variable suffix matcher currently in use.
+ * <p>
+ * The variable suffix is the characer or characters that identify the
+ * end of a variable. This suffix is expressed in terms of a matcher
+ * allowing advanced suffix matches.
+ *
+ * @param suffixMatcher the suffix matcher to use, null ignored
+ * @return this, to enable chaining
+ * @throws IllegalArgumentException if the suffix matcher is null
+ */
+ public StrSubstitutor setVariableSuffixMatcher(final StrMatcher suffixMatcher) {
+ if (suffixMatcher == null) {
+ throw new IllegalArgumentException("Variable suffix matcher must not be null!");
+ }
+ this.suffixMatcher = suffixMatcher;
+ return this;
+ }
+
+ /**
+ * Sets the variable suffix to use.
+ * <p>
+ * The variable suffix is the characer or characters that identify the
+ * end of a variable. This method allows a single character suffix to
+ * be easily set.
+ *
+ * @param suffix the suffix character to use
+ * @return this, to enable chaining
+ */
+ public StrSubstitutor setVariableSuffix(final char suffix) {
+ return setVariableSuffixMatcher(StrMatcher.charMatcher(suffix));
+ }
+
+ /**
+ * Sets the variable suffix to use.
+ * <p>
+ * The variable suffix is the character or characters that identify the
+ * end of a variable. This method allows a string suffix to be easily set.
+ *
+ * @param suffix the suffix for variables, not null
+ * @return this, to enable chaining
+ * @throws IllegalArgumentException if the suffix is null
+ */
+ public StrSubstitutor setVariableSuffix(final String suffix) {
+ if (suffix == null) {
+ throw new IllegalArgumentException("Variable suffix must not be null!");
+ }
+ return setVariableSuffixMatcher(StrMatcher.stringMatcher(suffix));
+ }
+
+ // Variable Default Value Delimiter
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the variable default value delimiter matcher currently in use.
+ * <p>
+ * The variable default value delimiter is the characer or characters that delimite the
+ * variable name and the variable default value. This delimiter is expressed in terms of a matcher
+ * allowing advanced variable default value delimiter matches.
+ * <p>
+ * If it returns null, then the variable default value resolution is disabled.
+ *
+ * @return the variable default value delimiter matcher in use, may be null
+ * @since 3.2
+ */
+ public StrMatcher getValueDelimiterMatcher() {
+ return valueDelimiterMatcher;
+ }
+
+ /**
+ * Sets the variable default value delimiter matcher to use.
+ * <p>
+ * The variable default value delimiter is the characer or characters that delimite the
+ * variable name and the variable default value. This delimiter is expressed in terms of a matcher
+ * allowing advanced variable default value delimiter matches.
+ * <p>
+ * If the <code>valueDelimiterMatcher</code> is null, then the variable default value resolution
+ * becomes disabled.
+ *
+ * @param valueDelimiterMatcher variable default value delimiter matcher to use, may be null
+ * @return this, to enable chaining
+ * @since 3.2
+ */
+ public StrSubstitutor setValueDelimiterMatcher(final StrMatcher valueDelimiterMatcher) {
+ this.valueDelimiterMatcher = valueDelimiterMatcher;
+ return this;
+ }
+
+ /**
+ * Sets the variable default value delimiter to use.
+ * <p>
+ * The variable default value delimiter is the characer or characters that delimite the
+ * variable name and the variable default value. This method allows a single character
+ * variable default value delimiter to be easily set.
+ *
+ * @param valueDelimiter the variable default value delimiter character to use
+ * @return this, to enable chaining
+ * @since 3.2
+ */
+ public StrSubstitutor setValueDelimiter(final char valueDelimiter) {
+ return setValueDelimiterMatcher(StrMatcher.charMatcher(valueDelimiter));
+ }
+
+ /**
+ * Sets the variable default value delimiter to use.
+ * <p>
+ * The variable default value delimiter is the characer or characters that delimite the
+ * variable name and the variable default value. This method allows a string
+ * variable default value delimiter to be easily set.
+ * <p>
+ * If the <code>valueDelimiter</code> is null or empty string, then the variable default
+ * value resolution becomes disabled.
+ *
+ * @param valueDelimiter the variable default value delimiter string to use, may be null or empty
+ * @return this, to enable chaining
+ * @since 3.2
+ */
+ public StrSubstitutor setValueDelimiter(final String valueDelimiter) {
+ if (valueDelimiter == null || valueDelimiter.length() == 0) {
+ setValueDelimiterMatcher(null);
+ return this;
+ }
+ return setValueDelimiterMatcher(StrMatcher.stringMatcher(valueDelimiter));
+ }
+
+ // Resolver
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the VariableResolver that is used to lookup variables.
+ *
+ * @return the VariableResolver
+ */
+ public StrLookup<?> getVariableResolver() {
+ return this.variableResolver;
+ }
+
+ /**
+ * Sets the VariableResolver that is used to lookup variables.
+ *
+ * @param variableResolver the VariableResolver
+ */
+ public void setVariableResolver(final StrLookup<?> variableResolver) {
+ this.variableResolver = variableResolver;
+ }
+
+ // Substitution support in variable names
+ //-----------------------------------------------------------------------
+ /**
+ * Returns a flag whether substitution is done in variable names.
+ *
+ * @return the substitution in variable names flag
+ * @since 3.0
+ */
+ public boolean isEnableSubstitutionInVariables() {
+ return enableSubstitutionInVariables;
+ }
+
+ /**
+ * Sets a flag whether substitution is done in variable names. If set to
+ * <b>true</b>, the names of variables can contain other variables which are
+ * processed first before the original variable is evaluated, e.g.
+ * <code>${jre-${java.version}}</code>. The default value is <b>false</b>.
+ *
+ * @param enableSubstitutionInVariables the new value of the flag
+ * @since 3.0
+ */
+ public void setEnableSubstitutionInVariables(
+ final boolean enableSubstitutionInVariables) {
+ this.enableSubstitutionInVariables = enableSubstitutionInVariables;
+ }
+
+ /**
+ * Returns the flag controlling whether escapes are preserved during
+ * substitution.
+ *
+ * @return the preserve escape flag
+ * @since 3.5
+ */
+ public boolean isPreserveEscapes() {
+ return preserveEscapes;
+ }
+
+ /**
+ * Sets a flag controlling whether escapes are preserved during
+ * substitution. If set to <b>true</b>, the escape character is retained
+ * during substitution (e.g. <code>$${this-is-escaped}</code> remains
+ * <code>$${this-is-escaped}</code>). If set to <b>false</b>, the escape
+ * character is removed during substitution (e.g.
+ * <code>$${this-is-escaped}</code> becomes
+ * <code>${this-is-escaped}</code>). The default value is <b>false</b>
+ *
+ * @param preserveEscapes true if escapes are to be preserved
+ * @since 3.5
+ */
+ public void setPreserveEscapes(final boolean preserveEscapes) {
+ this.preserveEscapes = preserveEscapes;
+ }
+}
[13/14] [text] TEXT-23: Resolving merge conflict in changes.xml
Posted by ch...@apache.org.
TEXT-23: Resolving merge conflict in changes.xml
Project: http://git-wip-us.apache.org/repos/asf/commons-text/repo
Commit: http://git-wip-us.apache.org/repos/asf/commons-text/commit/56c0a072
Tree: http://git-wip-us.apache.org/repos/asf/commons-text/tree/56c0a072
Diff: http://git-wip-us.apache.org/repos/asf/commons-text/diff/56c0a072
Branch: refs/heads/master
Commit: 56c0a0728f80241d550a070e43fc60ec80d0b806
Parents: 3aa96d4 c8a803c
Author: Rob Tompkins <ch...@capitalone.com>
Authored: Wed Nov 9 20:46:18 2016 -0500
Committer: Rob Tompkins <ch...@capitalone.com>
Committed: Wed Nov 9 20:46:18 2016 -0500
----------------------------------------------------------------------
src/changes/changes.xml | 1 +
.../similarity/LevenshteinDetailedDistance.java | 513 +++++++++++++++++++
.../text/similarity/LevenshteinResults.java | 125 +++++
.../LevenshteinDetailedDistanceTest.java | 402 +++++++++++++++
4 files changed, 1041 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/commons-text/blob/56c0a072/src/changes/changes.xml
----------------------------------------------------------------------
diff --cc src/changes/changes.xml
index c3d78da,45825e7..e60b364
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@@ -22,7 -22,7 +22,8 @@@
<body>
<release version="1.0" date="tba" description="tba">
+ <action issue="TEXT-23" type="add" dev="chtompki">Moving from commons-lang, the package org.apache.commons.lang3.text</action>
+ <action issue="TEXT-10" type="add" dev="kinow" due-to="Don Jeba">A more complex Levenshtein distance</action>
<action issue="TEXT-24" type="add" dev="chtompki">Add coveralls and Travis.ci integration</action>
<action issue="TEXT-19" type="add" dev="kinow" due-to="Eyal Allweil">Add alphabet converter</action>
<action issue="TEXT-13" type="add" dev="kinow">Create Commons Text logo</action>
[02/14] [text] TEXT-23: Adding JavaUnicodeEscaperTest
Posted by ch...@apache.org.
TEXT-23: Adding JavaUnicodeEscaperTest
Project: http://git-wip-us.apache.org/repos/asf/commons-text/repo
Commit: http://git-wip-us.apache.org/repos/asf/commons-text/commit/32196b18
Tree: http://git-wip-us.apache.org/repos/asf/commons-text/tree/32196b18
Diff: http://git-wip-us.apache.org/repos/asf/commons-text/diff/32196b18
Branch: refs/heads/master
Commit: 32196b189ac7e4909f8b4dab7ed5a34ef65e532c
Parents: 2f55bd1
Author: Rob Tompkins <ch...@capitalone.com>
Authored: Wed Nov 9 10:28:59 2016 -0500
Committer: Rob Tompkins <ch...@capitalone.com>
Committed: Wed Nov 9 10:28:59 2016 -0500
----------------------------------------------------------------------
.../text/translate/JavaUnicodeEscaperTest.java | 65 ++++++++++++++++++++
1 file changed, 65 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/commons-text/blob/32196b18/src/test/java/org/apache/commons/text/translate/JavaUnicodeEscaperTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/text/translate/JavaUnicodeEscaperTest.java b/src/test/java/org/apache/commons/text/translate/JavaUnicodeEscaperTest.java
new file mode 100644
index 0000000..aab7599
--- /dev/null
+++ b/src/test/java/org/apache/commons/text/translate/JavaUnicodeEscaperTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.translate;
+
+import org.junit.Test;
+
+import java.io.UnsupportedEncodingException;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Unit tests for {@link org.apache.commons.text.translate.JavaUnicodeEscaper}.
+ */
+public class JavaUnicodeEscaperTest {
+
+ @Test
+ public void testBelow() {
+ final org.apache.commons.text.translate.JavaUnicodeEscaper jue = org.apache.commons.text.translate.JavaUnicodeEscaper.below('F');
+
+ final String input = "ADFGZ";
+ final String result = jue.translate(input);
+ assertEquals("Failed to escape Unicode characters via the below method", "\\u0041\\u0044FGZ", result);
+ }
+
+ @Test
+ public void testBetween() {
+ final org.apache.commons.text.translate.JavaUnicodeEscaper jue = org.apache.commons.text.translate.JavaUnicodeEscaper.between('F', 'L');
+
+ final String input = "ADFGZ";
+ final String result = jue.translate(input);
+ assertEquals("Failed to escape Unicode characters via the between method", "AD\\u0046\\u0047Z", result);
+ }
+
+ @Test
+ public void testAbove() {
+ final org.apache.commons.text.translate.JavaUnicodeEscaper jue = JavaUnicodeEscaper.above('F');
+
+ final String input = "ADFGZ";
+ final String result = jue.translate(input);
+ assertEquals("Failed to escape Unicode characters via the above method", "ADF\\u0047\\u005A", result);
+ }
+
+ @Test
+ public void testToUtf16Escape() throws UnsupportedEncodingException {
+ final org.apache.commons.text.translate.JavaUnicodeEscaper jue = org.apache.commons.text.translate.JavaUnicodeEscaper.below('F');
+ // According to https://en.wikipedia.org/wiki/UTF-16#Code_points_U.2B10000..U.2B10FFFF,
+ // Character ?, U+24B62, Binary Code Point 0010 0100 1011 0110 0010, Binary UTF-167 1101 1000 0101 0010 1101 1111 0110 0010, UTF-16 Hex Code Units D852 DF62
+ String encoding = jue.toUtf16Escape(Integer.parseInt("024B62", 16));
+ assertEquals("\\uD852\\uDF62",encoding);
+ }
+}
[12/14] [text] TEXT-23: adding documentation to changes.xml
Posted by ch...@apache.org.
TEXT-23: adding documentation to changes.xml
Project: http://git-wip-us.apache.org/repos/asf/commons-text/repo
Commit: http://git-wip-us.apache.org/repos/asf/commons-text/commit/3aa96d44
Tree: http://git-wip-us.apache.org/repos/asf/commons-text/tree/3aa96d44
Diff: http://git-wip-us.apache.org/repos/asf/commons-text/diff/3aa96d44
Branch: refs/heads/master
Commit: 3aa96d4485cee6726592b2a50bb94de4aeb35f22
Parents: 7adf1d5
Author: Rob Tompkins <ch...@capitalone.com>
Authored: Wed Nov 9 20:07:26 2016 -0500
Committer: Rob Tompkins <ch...@capitalone.com>
Committed: Wed Nov 9 20:07:26 2016 -0500
----------------------------------------------------------------------
src/changes/changes.xml | 1 +
1 file changed, 1 insertion(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/commons-text/blob/3aa96d44/src/changes/changes.xml
----------------------------------------------------------------------
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 05f0bb6..c3d78da 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -22,6 +22,7 @@
<body>
<release version="1.0" date="tba" description="tba">
+ <action issue="TEXT-23" type="add" dev="chtompki">Moving from commons-lang, the package org.apache.commons.lang3.text</action>
<action issue="TEXT-24" type="add" dev="chtompki">Add coveralls and Travis.ci integration</action>
<action issue="TEXT-19" type="add" dev="kinow" due-to="Eyal Allweil">Add alphabet converter</action>
<action issue="TEXT-13" type="add" dev="kinow">Create Commons Text logo</action>
[06/14] [text] TEXT-23: Adding remainder of the o.a.c.l.text package
Posted by ch...@apache.org.
http://git-wip-us.apache.org/repos/asf/commons-text/blob/6f24aa45/src/test/java/org/apache/commons/text/ExtendedMessageFormatTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/text/ExtendedMessageFormatTest.java b/src/test/java/org/apache/commons/text/ExtendedMessageFormatTest.java
new file mode 100644
index 0000000..6b91643
--- /dev/null
+++ b/src/test/java/org/apache/commons/text/ExtendedMessageFormatTest.java
@@ -0,0 +1,484 @@
+/*
+ * 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 org.junit.Test;
+import org.junit.Before;
+import static org.junit.Assert.*;
+import static org.apache.commons.lang3.JavaVersion.JAVA_1_4;
+
+import java.text.ChoiceFormat;
+import java.text.DateFormat;
+import java.text.FieldPosition;
+import java.text.Format;
+import java.text.MessageFormat;
+import java.text.NumberFormat;
+import java.text.ParsePosition;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.commons.lang3.SystemUtils;
+
+/**
+ * Test case for {@link ExtendedMessageFormat}.
+ *
+ * @since 2.4
+ */
+public class ExtendedMessageFormatTest {
+
+ private final Map<String, FormatFactory> registry = new HashMap<>();
+
+ @Before
+ public void setUp() throws Exception {
+ registry.put("lower", new LowerCaseFormatFactory());
+ registry.put("upper", new UpperCaseFormatFactory());
+ }
+
+ /**
+ * Test extended formats.
+ */
+ @Test
+ public void testExtendedFormats() {
+ final String pattern = "Lower: {0,lower} Upper: {1,upper}";
+ final ExtendedMessageFormat emf = new ExtendedMessageFormat(pattern, registry);
+ assertPatternsEqual("TOPATTERN", pattern, emf.toPattern());
+ assertEquals("Lower: foo Upper: BAR", emf.format(new Object[] {"foo", "bar"}));
+ assertEquals("Lower: foo Upper: BAR", emf.format(new Object[] {"Foo", "Bar"}));
+ assertEquals("Lower: foo Upper: BAR", emf.format(new Object[] {"FOO", "BAR"}));
+ assertEquals("Lower: foo Upper: BAR", emf.format(new Object[] {"FOO", "bar"}));
+ assertEquals("Lower: foo Upper: BAR", emf.format(new Object[] {"foo", "BAR"}));
+ }
+
+ /**
+ * Test Bug LANG-477 - out of memory error with escaped quote
+ */
+ @Test
+ public void testEscapedQuote_LANG_477() {
+ final String pattern = "it''s a {0,lower} 'test'!";
+ final ExtendedMessageFormat emf = new ExtendedMessageFormat(pattern, registry);
+ assertEquals("it's a dummy test!", emf.format(new Object[] {"DUMMY"}));
+ }
+
+ /**
+ * Test Bug LANG-917 - IndexOutOfBoundsException and/or infinite loop when using a choice pattern
+ */
+ @Test
+ public void testEmbeddedPatternInChoice() {
+ final String pattern = "Hi {0,lower}, got {1,choice,0#none|1#one|1<{1,number}}, {2,upper}!";
+ final ExtendedMessageFormat emf = new ExtendedMessageFormat(pattern, registry);
+ assertEquals(emf.format(new Object[] {"there", 3, "great"}), "Hi there, got 3, GREAT!");
+ }
+
+ /**
+ * Test Bug LANG-948 - Exception while using ExtendedMessageFormat and escaping braces
+ */
+ @Test
+ public void testEscapedBraces_LANG_948() {
+ // message without placeholder because braces are escaped by quotes
+ final String pattern = "Message without placeholders '{}'";
+ final ExtendedMessageFormat emf = new ExtendedMessageFormat(pattern, registry);
+ assertEquals("Message without placeholders {}", emf.format(new Object[] {"DUMMY"}));
+
+ // message with placeholder because quotes are escaped by quotes
+ final String pattern2 = "Message with placeholder ''{0}''";
+ final ExtendedMessageFormat emf2 = new ExtendedMessageFormat(pattern2, registry);
+ assertEquals("Message with placeholder 'DUMMY'", emf2.format(new Object[] {"DUMMY"}));
+ }
+
+ /**
+ * Test extended and built in formats.
+ */
+ @Test
+ public void testExtendedAndBuiltInFormats() {
+ final Calendar cal = Calendar.getInstance();
+ cal.set(2007, Calendar.JANUARY, 23, 18, 33, 05);
+ final Object[] args = new Object[] {"John Doe", cal.getTime(), Double.valueOf("12345.67")};
+ final String builtinsPattern = "DOB: {1,date,short} Salary: {2,number,currency}";
+ final String extendedPattern = "Name: {0,upper} ";
+ final String pattern = extendedPattern + builtinsPattern;
+
+ final HashSet<Locale> testLocales = new HashSet<>();
+ testLocales.addAll(Arrays.asList(DateFormat.getAvailableLocales()));
+ testLocales.retainAll(Arrays.asList(NumberFormat.getAvailableLocales()));
+ testLocales.add(null);
+
+ for (final Locale locale : testLocales) {
+ final MessageFormat builtins = createMessageFormat(builtinsPattern, locale);
+ final String expectedPattern = extendedPattern + builtins.toPattern();
+ DateFormat df = null;
+ NumberFormat nf = null;
+ ExtendedMessageFormat emf = null;
+ if (locale == null) {
+ df = DateFormat.getDateInstance(DateFormat.SHORT);
+ nf = NumberFormat.getCurrencyInstance();
+ emf = new ExtendedMessageFormat(pattern, registry);
+ } else {
+ df = DateFormat.getDateInstance(DateFormat.SHORT, locale);
+ nf = NumberFormat.getCurrencyInstance(locale);
+ emf = new ExtendedMessageFormat(pattern, locale, registry);
+ }
+ final StringBuilder expected = new StringBuilder();
+ expected.append("Name: ");
+ expected.append(args[0].toString().toUpperCase());
+ expected.append(" DOB: ");
+ expected.append(df.format(args[1]));
+ expected.append(" Salary: ");
+ expected.append(nf.format(args[2]));
+ assertPatternsEqual("pattern comparison for locale " + locale, expectedPattern, emf.toPattern());
+ assertEquals(String.valueOf(locale), expected.toString(), emf.format(args));
+ }
+ }
+
+// /**
+// * Test extended formats with choice format.
+// *
+// * NOTE: FAILING - currently sub-formats not supported
+// */
+// public void testExtendedWithChoiceFormat() {
+// String pattern = "Choice: {0,choice,1.0#{1,lower}|2.0#{1,upper}}";
+// ExtendedMessageFormat emf = new ExtendedMessageFormat(pattern, registry);
+// assertPatterns(null, pattern, emf.toPattern());
+// try {
+// assertEquals("one", emf.format(new Object[] {Integer.valueOf(1), "ONE"}));
+// assertEquals("TWO", emf.format(new Object[] {Integer.valueOf(2), "two"}));
+// } catch (IllegalArgumentException e) {
+// // currently sub-formats not supported
+// }
+// }
+
+// /**
+// * Test mixed extended and built-in formats with choice format.
+// *
+// * NOTE: FAILING - currently sub-formats not supported
+// */
+// public void testExtendedAndBuiltInWithChoiceFormat() {
+// String pattern = "Choice: {0,choice,1.0#{0} {1,lower} {2,number}|2.0#{0} {1,upper} {2,number,currency}}";
+// Object[] lowArgs = new Object[] {Integer.valueOf(1), "Low", Double.valueOf("1234.56")};
+// Object[] highArgs = new Object[] {Integer.valueOf(2), "High", Double.valueOf("9876.54")};
+// Locale[] availableLocales = ChoiceFormat.getAvailableLocales();
+// Locale[] testLocales = new Locale[availableLocales.length + 1];
+// testLocales[0] = null;
+// System.arraycopy(availableLocales, 0, testLocales, 1, availableLocales.length);
+// for (int i = 0; i < testLocales.length; i++) {
+// NumberFormat nf = null;
+// NumberFormat cf = null;
+// ExtendedMessageFormat emf = null;
+// if (testLocales[i] == null) {
+// nf = NumberFormat.getNumberInstance();
+// cf = NumberFormat.getCurrencyInstance();
+// emf = new ExtendedMessageFormat(pattern, registry);
+// } else {
+// nf = NumberFormat.getNumberInstance(testLocales[i]);
+// cf = NumberFormat.getCurrencyInstance(testLocales[i]);
+// emf = new ExtendedMessageFormat(pattern, testLocales[i], registry);
+// }
+// assertPatterns(null, pattern, emf.toPattern());
+// try {
+// String lowExpected = lowArgs[0] + " low " + nf.format(lowArgs[2]);
+// String highExpected = highArgs[0] + " HIGH " + cf.format(highArgs[2]);
+// assertEquals(lowExpected, emf.format(lowArgs));
+// assertEquals(highExpected, emf.format(highArgs));
+// } catch (IllegalArgumentException e) {
+// // currently sub-formats not supported
+// }
+// }
+// }
+
+ /**
+ * Test the built in choice format.
+ */
+ @Test
+ public void testBuiltInChoiceFormat() {
+ final Object[] values = new Number[] {Integer.valueOf(1), Double.valueOf("2.2"), Double.valueOf("1234.5")};
+ String choicePattern = null;
+ final Locale[] availableLocales = ChoiceFormat.getAvailableLocales();
+
+ choicePattern = "{0,choice,1#One|2#Two|3#Many {0,number}}";
+ for (final Object value : values) {
+ checkBuiltInFormat(value + ": " + choicePattern, new Object[] {value}, availableLocales);
+ }
+
+ choicePattern = "{0,choice,1#''One''|2#\"Two\"|3#''{Many}'' {0,number}}";
+ for (final Object value : values) {
+ checkBuiltInFormat(value + ": " + choicePattern, new Object[] {value}, availableLocales);
+ }
+ }
+
+ /**
+ * Test the built in date/time formats
+ */
+ @Test
+ public void testBuiltInDateTimeFormat() {
+ final Calendar cal = Calendar.getInstance();
+ cal.set(2007, Calendar.JANUARY, 23, 18, 33, 05);
+ final Object[] args = new Object[] {cal.getTime()};
+ final Locale[] availableLocales = DateFormat.getAvailableLocales();
+
+ checkBuiltInFormat("1: {0,date,short}", args, availableLocales);
+ checkBuiltInFormat("2: {0,date,medium}", args, availableLocales);
+ checkBuiltInFormat("3: {0,date,long}", args, availableLocales);
+ checkBuiltInFormat("4: {0,date,full}", args, availableLocales);
+ checkBuiltInFormat("5: {0,date,d MMM yy}", args, availableLocales);
+ checkBuiltInFormat("6: {0,time,short}", args, availableLocales);
+ checkBuiltInFormat("7: {0,time,medium}", args, availableLocales);
+ checkBuiltInFormat("8: {0,time,long}", args, availableLocales);
+ checkBuiltInFormat("9: {0,time,full}", args, availableLocales);
+ checkBuiltInFormat("10: {0,time,HH:mm}", args, availableLocales);
+ checkBuiltInFormat("11: {0,date}", args, availableLocales);
+ checkBuiltInFormat("12: {0,time}", args, availableLocales);
+ }
+
+ @Test
+ public void testOverriddenBuiltinFormat() {
+ final Calendar cal = Calendar.getInstance();
+ cal.set(2007, Calendar.JANUARY, 23);
+ final Object[] args = new Object[] {cal.getTime()};
+ final Locale[] availableLocales = DateFormat.getAvailableLocales();
+ final Map<String, ? extends FormatFactory> dateRegistry = Collections.singletonMap("date", new OverrideShortDateFormatFactory());
+
+ //check the non-overridden builtins:
+ checkBuiltInFormat("1: {0,date}", dateRegistry, args, availableLocales);
+ checkBuiltInFormat("2: {0,date,medium}", dateRegistry, args, availableLocales);
+ checkBuiltInFormat("3: {0,date,long}", dateRegistry, args, availableLocales);
+ checkBuiltInFormat("4: {0,date,full}", dateRegistry, args, availableLocales);
+ checkBuiltInFormat("5: {0,date,d MMM yy}", dateRegistry, args, availableLocales);
+
+ //check the overridden format:
+ for (int i = -1; i < availableLocales.length; i++) {
+ final Locale locale = i < 0 ? null : availableLocales[i];
+ final MessageFormat dateDefault = createMessageFormat("{0,date}", locale);
+ final String pattern = "{0,date,short}";
+ final ExtendedMessageFormat dateShort = new ExtendedMessageFormat(pattern, locale, dateRegistry);
+ assertEquals("overridden date,short format", dateDefault.format(args), dateShort.format(args));
+ assertEquals("overridden date,short pattern", pattern, dateShort.toPattern());
+ }
+ }
+
+ /**
+ * Test the built in number formats.
+ */
+ @Test
+ public void testBuiltInNumberFormat() {
+ final Object[] args = new Object[] {Double.valueOf("6543.21")};
+ final Locale[] availableLocales = NumberFormat.getAvailableLocales();
+ checkBuiltInFormat("1: {0,number}", args, availableLocales);
+ checkBuiltInFormat("2: {0,number,integer}", args, availableLocales);
+ checkBuiltInFormat("3: {0,number,currency}", args, availableLocales);
+ checkBuiltInFormat("4: {0,number,percent}", args, availableLocales);
+ checkBuiltInFormat("5: {0,number,00000.000}", args, availableLocales);
+ }
+
+ /**
+ * Test equals() and hashcode.
+ */
+ @Test
+ public void testEqualsHashcode() {
+ final Map<String, ? extends FormatFactory> fmtRegistry = Collections.singletonMap("testfmt", new LowerCaseFormatFactory());
+ final Map<String, ? extends FormatFactory> otherRegitry = Collections.singletonMap("testfmt", new UpperCaseFormatFactory());
+
+ final String pattern = "Pattern: {0,testfmt}";
+ final ExtendedMessageFormat emf = new ExtendedMessageFormat(pattern, Locale.US, fmtRegistry);
+
+ ExtendedMessageFormat other = null;
+
+ // Same object
+ assertTrue("same, equals()", emf.equals(emf));
+ assertTrue("same, hashcode()", emf.hashCode() == emf.hashCode());
+
+ // Equal Object
+ other = new ExtendedMessageFormat(pattern, Locale.US, fmtRegistry);
+ assertTrue("equal, equals()", emf.equals(other));
+ assertTrue("equal, hashcode()", emf.hashCode() == other.hashCode());
+
+ // Different Class
+ other = new OtherExtendedMessageFormat(pattern, Locale.US, fmtRegistry);
+ assertFalse("class, equals()", emf.equals(other));
+ assertTrue("class, hashcode()", emf.hashCode() == other.hashCode()); // same hashcode
+
+ // Different pattern
+ other = new ExtendedMessageFormat("X" + pattern, Locale.US, fmtRegistry);
+ assertFalse("pattern, equals()", emf.equals(other));
+ assertFalse("pattern, hashcode()", emf.hashCode() == other.hashCode());
+
+ // Different registry
+ other = new ExtendedMessageFormat(pattern, Locale.US, otherRegitry);
+ assertFalse("registry, equals()", emf.equals(other));
+ assertFalse("registry, hashcode()", emf.hashCode() == other.hashCode());
+
+ // Different Locale
+ other = new ExtendedMessageFormat(pattern, Locale.FRANCE, fmtRegistry);
+ assertFalse("locale, equals()", emf.equals(other));
+ assertTrue("locale, hashcode()", emf.hashCode() == other.hashCode()); // same hashcode
+ }
+
+ /**
+ * Test a built in format for the specified Locales, plus <code>null</code> Locale.
+ * @param pattern MessageFormat pattern
+ * @param args MessageFormat arguments
+ * @param locales to test
+ */
+ private void checkBuiltInFormat(final String pattern, final Object[] args, final Locale[] locales) {
+ checkBuiltInFormat(pattern, null, args, locales);
+ }
+
+ /**
+ * Test a built in format for the specified Locales, plus <code>null</code> Locale.
+ * @param pattern MessageFormat pattern
+ * @param fmtRegistry FormatFactory registry to use
+ * @param args MessageFormat arguments
+ * @param locales to test
+ */
+ private void checkBuiltInFormat(final String pattern, final Map<String, ?> fmtRegistry, final Object[] args, final Locale[] locales) {
+ checkBuiltInFormat(pattern, fmtRegistry, args, (Locale) null);
+ for (final Locale locale : locales) {
+ checkBuiltInFormat(pattern, fmtRegistry, args, locale);
+ }
+ }
+
+ /**
+ * Create an ExtendedMessageFormat for the specified pattern and locale and check the
+ * formated output matches the expected result for the parameters.
+ * @param pattern string
+ * @param registryUnused map (currently unused)
+ * @param args Object[]
+ * @param locale Locale
+ */
+ private void checkBuiltInFormat(final String pattern, final Map<String, ?> registryUnused, final Object[] args, final Locale locale) {
+ final StringBuilder buffer = new StringBuilder();
+ buffer.append("Pattern=[");
+ buffer.append(pattern);
+ buffer.append("], locale=[");
+ buffer.append(locale);
+ buffer.append("]");
+ final MessageFormat mf = createMessageFormat(pattern, locale);
+ // System.out.println(buffer + ", result=[" + mf.format(args) +"]");
+ ExtendedMessageFormat emf = null;
+ if (locale == null) {
+ emf = new ExtendedMessageFormat(pattern);
+ } else {
+ emf = new ExtendedMessageFormat(pattern, locale);
+ }
+ assertEquals("format " + buffer.toString(), mf.format(args), emf.format(args));
+ assertPatternsEqual("toPattern " + buffer.toString(), mf.toPattern(), emf.toPattern());
+ }
+
+ //can't trust what MessageFormat does with toPattern() pre 1.4:
+ private void assertPatternsEqual(final String message, final String expected, final String actual) {
+ if (SystemUtils.isJavaVersionAtLeast(JAVA_1_4)) {
+ assertEquals(message, expected, actual);
+ }
+ }
+
+ /**
+ * Replace MessageFormat(String, Locale) constructor (not available until JDK 1.4).
+ * @param pattern string
+ * @param locale Locale
+ * @return MessageFormat
+ */
+ private MessageFormat createMessageFormat(final String pattern, final Locale locale) {
+ final MessageFormat result = new MessageFormat(pattern);
+ if (locale != null) {
+ result.setLocale(locale);
+ result.applyPattern(pattern);
+ }
+ return result;
+ }
+
+ // ------------------------ Test Formats ------------------------
+
+ /**
+ * {@link Format} implementation which converts to lower case.
+ */
+ private static class LowerCaseFormat extends Format {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) {
+ return toAppendTo.append(((String)obj).toLowerCase());
+ }
+ @Override
+ public Object parseObject(final String source, final ParsePosition pos) {throw new UnsupportedOperationException();}
+ }
+
+ /**
+ * {@link Format} implementation which converts to upper case.
+ */
+ private static class UpperCaseFormat extends Format {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public StringBuffer format(final Object obj, final StringBuffer toAppendTo, final FieldPosition pos) {
+ return toAppendTo.append(((String)obj).toUpperCase());
+ }
+ @Override
+ public Object parseObject(final String source, final ParsePosition pos) {throw new UnsupportedOperationException();}
+ }
+
+
+ // ------------------------ Test Format Factories ---------------
+ /**
+ * {@link FormatFactory} implementation for lower case format.
+ */
+ private static class LowerCaseFormatFactory implements FormatFactory {
+ private static final Format LOWER_INSTANCE = new LowerCaseFormat();
+ @Override
+ public Format getFormat(final String name, final String arguments, final Locale locale) {
+ return LOWER_INSTANCE;
+ }
+ }
+ /**
+ * {@link FormatFactory} implementation for upper case format.
+ */
+ private static class UpperCaseFormatFactory implements FormatFactory {
+ private static final Format UPPER_INSTANCE = new UpperCaseFormat();
+ @Override
+ public Format getFormat(final String name, final String arguments, final Locale locale) {
+ return UPPER_INSTANCE;
+ }
+ }
+ /**
+ * {@link FormatFactory} implementation to override date format "short" to "default".
+ */
+ private static class OverrideShortDateFormatFactory implements FormatFactory {
+ @Override
+ public Format getFormat(final String name, final String arguments, final Locale locale) {
+ return !"short".equals(arguments) ? null
+ : locale == null ? DateFormat
+ .getDateInstance(DateFormat.DEFAULT) : DateFormat
+ .getDateInstance(DateFormat.DEFAULT, locale);
+ }
+ }
+
+ /**
+ * Alternative ExtendedMessageFormat impl.
+ */
+ private static class OtherExtendedMessageFormat extends ExtendedMessageFormat {
+ private static final long serialVersionUID = 1L;
+
+ public OtherExtendedMessageFormat(final String pattern, final Locale locale,
+ final Map<String, ? extends FormatFactory> registry) {
+ super(pattern, locale, registry);
+ }
+
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/commons-text/blob/6f24aa45/src/test/java/org/apache/commons/text/FormattableUtilsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/text/FormattableUtilsTest.java b/src/test/java/org/apache/commons/text/FormattableUtilsTest.java
new file mode 100644
index 0000000..d23dba6
--- /dev/null
+++ b/src/test/java/org/apache/commons/text/FormattableUtilsTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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 static java.util.FormattableFlags.LEFT_JUSTIFY;
+import static org.junit.Assert.assertEquals;
+
+import java.util.Formatter;
+
+import org.junit.Test;
+
+/**
+ * Unit tests {@link FormattableUtils}.
+ */
+public class FormattableUtilsTest {
+
+ @Test
+ public void testDefaultAppend() {
+ assertEquals("foo", FormattableUtils.append("foo", new Formatter(), 0, -1, -1).toString());
+ assertEquals("fo", FormattableUtils.append("foo", new Formatter(), 0, -1, 2).toString());
+ assertEquals(" foo", FormattableUtils.append("foo", new Formatter(), 0, 4, -1).toString());
+ assertEquals(" foo", FormattableUtils.append("foo", new Formatter(), 0, 6, -1).toString());
+ assertEquals(" fo", FormattableUtils.append("foo", new Formatter(), 0, 3, 2).toString());
+ assertEquals(" fo", FormattableUtils.append("foo", new Formatter(), 0, 5, 2).toString());
+ assertEquals("foo ", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 4, -1).toString());
+ assertEquals("foo ", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 6, -1).toString());
+ assertEquals("fo ", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 3, 2).toString());
+ assertEquals("fo ", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 5, 2).toString());
+ }
+
+ @Test
+ public void testAlternatePadCharacter() {
+ final char pad='_';
+ assertEquals("foo", FormattableUtils.append("foo", new Formatter(), 0, -1, -1, pad).toString());
+ assertEquals("fo", FormattableUtils.append("foo", new Formatter(), 0, -1, 2, pad).toString());
+ assertEquals("_foo", FormattableUtils.append("foo", new Formatter(), 0, 4, -1, pad).toString());
+ assertEquals("___foo", FormattableUtils.append("foo", new Formatter(), 0, 6, -1, pad).toString());
+ assertEquals("_fo", FormattableUtils.append("foo", new Formatter(), 0, 3, 2, pad).toString());
+ assertEquals("___fo", FormattableUtils.append("foo", new Formatter(), 0, 5, 2, pad).toString());
+ assertEquals("foo_", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 4, -1, pad).toString());
+ assertEquals("foo___", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 6, -1, pad).toString());
+ assertEquals("fo_", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 3, 2, pad).toString());
+ assertEquals("fo___", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 5, 2, pad).toString());
+ }
+
+ @Test
+ public void testEllipsis() {
+ assertEquals("foo", FormattableUtils.append("foo", new Formatter(), 0, -1, -1, "*").toString());
+ assertEquals("f*", FormattableUtils.append("foo", new Formatter(), 0, -1, 2, "*").toString());
+ assertEquals(" foo", FormattableUtils.append("foo", new Formatter(), 0, 4, -1, "*").toString());
+ assertEquals(" foo", FormattableUtils.append("foo", new Formatter(), 0, 6, -1, "*").toString());
+ assertEquals(" f*", FormattableUtils.append("foo", new Formatter(), 0, 3, 2, "*").toString());
+ assertEquals(" f*", FormattableUtils.append("foo", new Formatter(), 0, 5, 2, "*").toString());
+ assertEquals("foo ", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 4, -1, "*").toString());
+ assertEquals("foo ", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 6, -1, "*").toString());
+ assertEquals("f* ", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 3, 2, "*").toString());
+ assertEquals("f* ", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 5, 2, "*").toString());
+
+ assertEquals("foo", FormattableUtils.append("foo", new Formatter(), 0, -1, -1, "+*").toString());
+ assertEquals("+*", FormattableUtils.append("foo", new Formatter(), 0, -1, 2, "+*").toString());
+ assertEquals(" foo", FormattableUtils.append("foo", new Formatter(), 0, 4, -1, "+*").toString());
+ assertEquals(" foo", FormattableUtils.append("foo", new Formatter(), 0, 6, -1, "+*").toString());
+ assertEquals(" +*", FormattableUtils.append("foo", new Formatter(), 0, 3, 2, "+*").toString());
+ assertEquals(" +*", FormattableUtils.append("foo", new Formatter(), 0, 5, 2, "+*").toString());
+ assertEquals("foo ", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 4, -1, "+*").toString());
+ assertEquals("foo ", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 6, -1, "+*").toString());
+ assertEquals("+* ", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 3, 2, "+*").toString());
+ assertEquals("+* ", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 5, 2, "+*").toString());
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void testIllegalEllipsis() {
+ FormattableUtils.append("foo", new Formatter(), 0, -1, 1, "xx");
+ }
+
+ @Test
+ public void testAlternatePadCharAndEllipsis() {
+ assertEquals("foo", FormattableUtils.append("foo", new Formatter(), 0, -1, -1, '_', "*").toString());
+ assertEquals("f*", FormattableUtils.append("foo", new Formatter(), 0, -1, 2, '_', "*").toString());
+ assertEquals("_foo", FormattableUtils.append("foo", new Formatter(), 0, 4, -1, '_', "*").toString());
+ assertEquals("___foo", FormattableUtils.append("foo", new Formatter(), 0, 6, -1, '_', "*").toString());
+ assertEquals("_f*", FormattableUtils.append("foo", new Formatter(), 0, 3, 2, '_', "*").toString());
+ assertEquals("___f*", FormattableUtils.append("foo", new Formatter(), 0, 5, 2, '_', "*").toString());
+ assertEquals("foo_", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 4, -1, '_', "*").toString());
+ assertEquals("foo___", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 6, -1, '_', "*").toString());
+ assertEquals("f*_", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 3, 2, '_', "*").toString());
+ assertEquals("f*___", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 5, 2, '_', "*").toString());
+
+ assertEquals("foo", FormattableUtils.append("foo", new Formatter(), 0, -1, -1, '_', "+*").toString());
+ assertEquals("+*", FormattableUtils.append("foo", new Formatter(), 0, -1, 2, '_', "+*").toString());
+ assertEquals("_foo", FormattableUtils.append("foo", new Formatter(), 0, 4, -1, '_', "+*").toString());
+ assertEquals("___foo", FormattableUtils.append("foo", new Formatter(), 0, 6, -1, '_', "+*").toString());
+ assertEquals("_+*", FormattableUtils.append("foo", new Formatter(), 0, 3, 2, '_', "+*").toString());
+ assertEquals("___+*", FormattableUtils.append("foo", new Formatter(), 0, 5, 2, '_', "+*").toString());
+ assertEquals("foo_", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 4, -1, '_', "+*").toString());
+ assertEquals("foo___", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 6, -1, '_', "+*").toString());
+ assertEquals("+*_", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 3, 2, '_', "+*").toString());
+ assertEquals("+*___", FormattableUtils.append("foo", new Formatter(), LEFT_JUSTIFY, 5, 2, '_', "+*").toString());
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/commons-text/blob/6f24aa45/src/test/java/org/apache/commons/text/StrBuilderAppendInsertTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/text/StrBuilderAppendInsertTest.java b/src/test/java/org/apache/commons/text/StrBuilderAppendInsertTest.java
new file mode 100644
index 0000000..228c8df
--- /dev/null
+++ b/src/test/java/org/apache/commons/text/StrBuilderAppendInsertTest.java
@@ -0,0 +1,1607 @@
+/*
+ * 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 org.junit.Test;
+import static org.junit.Assert.*;
+
+import java.text.DecimalFormatSymbols;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+
+import org.apache.commons.lang3.SystemUtils;
+
+/**
+ * Unit tests for {@link StrBuilder}.
+ */
+public class StrBuilderAppendInsertTest {
+
+ /** The system line separator. */
+ private static final String SEP = SystemUtils.LINE_SEPARATOR;
+
+ /** Test subclass of Object, with a toString method. */
+ private static final Object FOO = new Object() {
+ @Override
+ public String toString() {
+ return "foo";
+ }
+ };
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendNewLine() {
+ StrBuilder sb = new StrBuilder("---");
+ sb.appendNewLine().append("+++");
+ assertEquals("---" + SEP + "+++", sb.toString());
+
+ sb = new StrBuilder("---");
+ sb.setNewLineText("#").appendNewLine().setNewLineText(null).appendNewLine();
+ assertEquals("---#" + SEP, sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendWithNullText() {
+ final StrBuilder sb = new StrBuilder();
+ sb.setNullText("NULL");
+ assertEquals("", sb.toString());
+
+ sb.appendNull();
+ assertEquals("NULL", sb.toString());
+
+ sb.append((Object) null);
+ assertEquals("NULLNULL", sb.toString());
+
+ sb.append(FOO);
+ assertEquals("NULLNULLfoo", sb.toString());
+
+ sb.append((String) null);
+ assertEquals("NULLNULLfooNULL", sb.toString());
+
+ sb.append("");
+ assertEquals("NULLNULLfooNULL", sb.toString());
+
+ sb.append("bar");
+ assertEquals("NULLNULLfooNULLbar", sb.toString());
+
+ sb.append((StringBuffer) null);
+ assertEquals("NULLNULLfooNULLbarNULL", sb.toString());
+
+ sb.append(new StringBuffer("baz"));
+ assertEquals("NULLNULLfooNULLbarNULLbaz", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppend_Object() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendNull();
+ assertEquals("", sb.toString());
+
+ sb.append((Object) null);
+ assertEquals("", sb.toString());
+
+ sb.append(FOO);
+ assertEquals("foo", sb.toString());
+
+ sb.append((StringBuffer) null);
+ assertEquals("foo", sb.toString());
+
+ sb.append(new StringBuffer("baz"));
+ assertEquals("foobaz", sb.toString());
+
+ sb.append(new StrBuilder("yes"));
+ assertEquals("foobazyes", sb.toString());
+
+ sb.append((CharSequence) "Seq");
+ assertEquals("foobazyesSeq", sb.toString());
+
+ sb.append(new StringBuilder("bld")); // Check it supports StringBuilder
+ assertEquals("foobazyesSeqbld", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppend_StringBuilder() {
+ StrBuilder sb = new StrBuilder();
+ sb.setNullText("NULL").append((String) null);
+ assertEquals("NULL", sb.toString());
+
+ sb = new StrBuilder();
+ sb.append(new StringBuilder("foo"));
+ assertEquals("foo", sb.toString());
+
+ sb.append(new StringBuilder(""));
+ assertEquals("foo", sb.toString());
+
+ sb.append(new StringBuilder("bar"));
+ assertEquals("foobar", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppend_String() {
+ StrBuilder sb = new StrBuilder();
+ sb.setNullText("NULL").append((String) null);
+ assertEquals("NULL", sb.toString());
+
+ sb = new StrBuilder();
+ sb.append("foo");
+ assertEquals("foo", sb.toString());
+
+ sb.append("");
+ assertEquals("foo", sb.toString());
+
+ sb.append("bar");
+ assertEquals("foobar", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppend_String_int_int() {
+ StrBuilder sb = new StrBuilder();
+ sb.setNullText("NULL").append((String) null, 0, 1);
+ assertEquals("NULL", sb.toString());
+
+ sb = new StrBuilder();
+ sb.append("foo", 0, 3);
+ assertEquals("foo", sb.toString());
+
+ try {
+ sb.append("bar", -1, 1);
+ fail("append(char[], -1,) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.append("bar", 3, 1);
+ fail("append(char[], 3,) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.append("bar", 1, -1);
+ fail("append(char[],, -1) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.append("bar", 1, 3);
+ fail("append(char[], 1, 3) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.append("bar", -1, 3);
+ fail("append(char[], -1, 3) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.append("bar", 4, 0);
+ fail("append(char[], 4, 0) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ sb.append("bar", 3, 0);
+ assertEquals("foo", sb.toString());
+
+ sb.append("abcbardef", 3, 3);
+ assertEquals("foobar", sb.toString());
+
+ sb.append( (CharSequence)"abcbardef", 4, 3);
+ assertEquals("foobarard", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppend_StringBuilder_int_int() {
+ StrBuilder sb = new StrBuilder();
+ sb.setNullText("NULL").append((String) null, 0, 1);
+ assertEquals("NULL", sb.toString());
+
+ sb = new StrBuilder();
+ sb.append(new StringBuilder("foo"), 0, 3);
+ assertEquals("foo", sb.toString());
+
+ try {
+ sb.append(new StringBuilder("bar"), -1, 1);
+ fail("append(StringBuilder, -1,) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.append(new StringBuilder("bar"), 3, 1);
+ fail("append(StringBuilder, 3,) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.append(new StringBuilder("bar"), 1, -1);
+ fail("append(StringBuilder,, -1) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.append(new StringBuilder("bar"), 1, 3);
+ fail("append(StringBuilder, 1, 3) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.append(new StringBuilder("bar"), -1, 3);
+ fail("append(StringBuilder, -1, 3) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.append(new StringBuilder("bar"), 4, 0);
+ fail("append(StringBuilder, 4, 0) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ sb.append(new StringBuilder("bar"), 3, 0);
+ assertEquals("foo", sb.toString());
+
+ sb.append(new StringBuilder("abcbardef"), 3, 3);
+ assertEquals("foobar", sb.toString());
+
+ sb.append( new StringBuilder("abcbardef"), 4, 3);
+ assertEquals("foobarard", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppend_StringBuffer() {
+ StrBuilder sb = new StrBuilder();
+ sb.setNullText("NULL").append((StringBuffer) null);
+ assertEquals("NULL", sb.toString());
+
+ sb = new StrBuilder();
+ sb.append(new StringBuffer("foo"));
+ assertEquals("foo", sb.toString());
+
+ sb.append(new StringBuffer(""));
+ assertEquals("foo", sb.toString());
+
+ sb.append(new StringBuffer("bar"));
+ assertEquals("foobar", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppend_StringBuffer_int_int() {
+ StrBuilder sb = new StrBuilder();
+ sb.setNullText("NULL").append((StringBuffer) null, 0, 1);
+ assertEquals("NULL", sb.toString());
+
+ sb = new StrBuilder();
+ sb.append(new StringBuffer("foo"), 0, 3);
+ assertEquals("foo", sb.toString());
+
+ try {
+ sb.append(new StringBuffer("bar"), -1, 1);
+ fail("append(char[], -1,) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.append(new StringBuffer("bar"), 3, 1);
+ fail("append(char[], 3,) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.append(new StringBuffer("bar"), 1, -1);
+ fail("append(char[],, -1) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.append(new StringBuffer("bar"), 1, 3);
+ fail("append(char[], 1, 3) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.append(new StringBuffer("bar"), -1, 3);
+ fail("append(char[], -1, 3) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.append(new StringBuffer("bar"), 4, 0);
+ fail("append(char[], 4, 0) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ sb.append(new StringBuffer("bar"), 3, 0);
+ assertEquals("foo", sb.toString());
+
+ sb.append(new StringBuffer("abcbardef"), 3, 3);
+ assertEquals("foobar", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppend_StrBuilder() {
+ StrBuilder sb = new StrBuilder();
+ sb.setNullText("NULL").append((StrBuilder) null);
+ assertEquals("NULL", sb.toString());
+
+ sb = new StrBuilder();
+ sb.append(new StrBuilder("foo"));
+ assertEquals("foo", sb.toString());
+
+ sb.append(new StrBuilder(""));
+ assertEquals("foo", sb.toString());
+
+ sb.append(new StrBuilder("bar"));
+ assertEquals("foobar", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppend_StrBuilder_int_int() {
+ StrBuilder sb = new StrBuilder();
+ sb.setNullText("NULL").append((StrBuilder) null, 0, 1);
+ assertEquals("NULL", sb.toString());
+
+ sb = new StrBuilder();
+ sb.append(new StrBuilder("foo"), 0, 3);
+ assertEquals("foo", sb.toString());
+
+ try {
+ sb.append(new StrBuilder("bar"), -1, 1);
+ fail("append(char[], -1,) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.append(new StrBuilder("bar"), 3, 1);
+ fail("append(char[], 3,) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.append(new StrBuilder("bar"), 1, -1);
+ fail("append(char[],, -1) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.append(new StrBuilder("bar"), 1, 3);
+ fail("append(char[], 1, 3) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.append(new StrBuilder("bar"), -1, 3);
+ fail("append(char[], -1, 3) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.append(new StrBuilder("bar"), 4, 0);
+ fail("append(char[], 4, 0) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ sb.append(new StrBuilder("bar"), 3, 0);
+ assertEquals("foo", sb.toString());
+
+ sb.append(new StrBuilder("abcbardef"), 3, 3);
+ assertEquals("foobar", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppend_CharArray() {
+ StrBuilder sb = new StrBuilder();
+ sb.setNullText("NULL").append((char[]) null);
+ assertEquals("NULL", sb.toString());
+
+ sb = new StrBuilder();
+ sb.append(new char[0]);
+ assertEquals("", sb.toString());
+
+ sb.append(new char[]{'f', 'o', 'o'});
+ assertEquals("foo", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppend_CharArray_int_int() {
+ StrBuilder sb = new StrBuilder();
+ sb.setNullText("NULL").append((char[]) null, 0, 1);
+ assertEquals("NULL", sb.toString());
+
+ sb = new StrBuilder();
+ sb.append(new char[]{'f', 'o', 'o'}, 0, 3);
+ assertEquals("foo", sb.toString());
+
+ try {
+ sb.append(new char[]{'b', 'a', 'r'}, -1, 1);
+ fail("append(char[], -1,) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.append(new char[]{'b', 'a', 'r'}, 3, 1);
+ fail("append(char[], 3,) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.append(new char[]{'b', 'a', 'r'}, 1, -1);
+ fail("append(char[],, -1) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.append(new char[]{'b', 'a', 'r'}, 1, 3);
+ fail("append(char[], 1, 3) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.append(new char[]{'b', 'a', 'r'}, -1, 3);
+ fail("append(char[], -1, 3) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.append(new char[]{'b', 'a', 'r'}, 4, 0);
+ fail("append(char[], 4, 0) expected IndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ sb.append(new char[]{'b', 'a', 'r'}, 3, 0);
+ assertEquals("foo", sb.toString());
+
+ sb.append(new char[]{'a', 'b', 'c', 'b', 'a', 'r', 'd', 'e', 'f'}, 3, 3);
+ assertEquals("foobar", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppend_Boolean() {
+ final StrBuilder sb = new StrBuilder();
+ sb.append(true);
+ assertEquals("true", sb.toString());
+
+ sb.append(false);
+ assertEquals("truefalse", sb.toString());
+
+ sb.append('!');
+ assertEquals("truefalse!", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppend_PrimitiveNumber() {
+ final StrBuilder sb = new StrBuilder();
+ sb.append(0);
+ assertEquals("0", sb.toString());
+
+ sb.append(1L);
+ assertEquals("01", sb.toString());
+
+ sb.append(2.3f);
+ assertEquals("012.3", sb.toString());
+
+ sb.append(4.5d);
+ assertEquals("012.34.5", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendln_FormattedString() {
+ final int[] count = new int[2];
+ final StrBuilder sb = new StrBuilder() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public StrBuilder append(final String str) {
+ count[0]++;
+ return super.append(str);
+ }
+ @Override
+ public StrBuilder appendNewLine() {
+ count[1]++;
+ return super.appendNewLine();
+ }
+ };
+ sb.appendln("Hello %s", "Alice");
+ assertEquals("Hello Alice" + SEP, sb.toString());
+ assertEquals(2, count[0]); // appendNewLine() calls append(String)
+ assertEquals(1, count[1]);
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendln_Object() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendln((Object) null);
+ assertEquals("" + SEP, sb.toString());
+
+ sb.appendln(FOO);
+ assertEquals(SEP + "foo" + SEP, sb.toString());
+
+ sb.appendln(Integer.valueOf(6));
+ assertEquals(SEP + "foo" + SEP + "6" + SEP, sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendln_String() {
+ final int[] count = new int[2];
+ final StrBuilder sb = new StrBuilder() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public StrBuilder append(final String str) {
+ count[0]++;
+ return super.append(str);
+ }
+ @Override
+ public StrBuilder appendNewLine() {
+ count[1]++;
+ return super.appendNewLine();
+ }
+ };
+ sb.appendln("foo");
+ assertEquals("foo" + SEP, sb.toString());
+ assertEquals(2, count[0]); // appendNewLine() calls append(String)
+ assertEquals(1, count[1]);
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendln_String_int_int() {
+ final int[] count = new int[2];
+ final StrBuilder sb = new StrBuilder() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public StrBuilder append(final String str, final int startIndex, final int length) {
+ count[0]++;
+ return super.append(str, startIndex, length);
+ }
+ @Override
+ public StrBuilder appendNewLine() {
+ count[1]++;
+ return super.appendNewLine();
+ }
+ };
+ sb.appendln("foo", 0, 3);
+ assertEquals("foo" + SEP, sb.toString());
+ assertEquals(1, count[0]);
+ assertEquals(1, count[1]);
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendln_StringBuffer() {
+ final int[] count = new int[2];
+ final StrBuilder sb = new StrBuilder() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public StrBuilder append(final StringBuffer str) {
+ count[0]++;
+ return super.append(str);
+ }
+ @Override
+ public StrBuilder appendNewLine() {
+ count[1]++;
+ return super.appendNewLine();
+ }
+ };
+ sb.appendln(new StringBuffer("foo"));
+ assertEquals("foo" + SEP, sb.toString());
+ assertEquals(1, count[0]);
+ assertEquals(1, count[1]);
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendln_StringBuilder() {
+ final int[] count = new int[2];
+ final StrBuilder sb = new StrBuilder() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public StrBuilder append(final StringBuilder str) {
+ count[0]++;
+ return super.append(str);
+ }
+ @Override
+ public StrBuilder appendNewLine() {
+ count[1]++;
+ return super.appendNewLine();
+ }
+ };
+ sb.appendln(new StringBuilder("foo"));
+ assertEquals("foo" + SEP, sb.toString());
+ assertEquals(1, count[0]);
+ assertEquals(1, count[1]);
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendln_StringBuffer_int_int() {
+ final int[] count = new int[2];
+ final StrBuilder sb = new StrBuilder() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public StrBuilder append(final StringBuffer str, final int startIndex, final int length) {
+ count[0]++;
+ return super.append(str, startIndex, length);
+ }
+ @Override
+ public StrBuilder appendNewLine() {
+ count[1]++;
+ return super.appendNewLine();
+ }
+ };
+ sb.appendln(new StringBuffer("foo"), 0, 3);
+ assertEquals("foo" + SEP, sb.toString());
+ assertEquals(1, count[0]);
+ assertEquals(1, count[1]);
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendln_StringBuilder_int_int() {
+ final int[] count = new int[2];
+ final StrBuilder sb = new StrBuilder() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public StrBuilder append(final StringBuilder str, final int startIndex, final int length) {
+ count[0]++;
+ return super.append(str, startIndex, length);
+ }
+ @Override
+ public StrBuilder appendNewLine() {
+ count[1]++;
+ return super.appendNewLine();
+ }
+ };
+ sb.appendln(new StringBuilder("foo"), 0, 3);
+ assertEquals("foo" + SEP, sb.toString());
+ assertEquals(1, count[0]);
+ assertEquals(1, count[1]);
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendln_StrBuilder() {
+ final int[] count = new int[2];
+ final StrBuilder sb = new StrBuilder() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public StrBuilder append(final StrBuilder str) {
+ count[0]++;
+ return super.append(str);
+ }
+ @Override
+ public StrBuilder appendNewLine() {
+ count[1]++;
+ return super.appendNewLine();
+ }
+ };
+ sb.appendln(new StrBuilder("foo"));
+ assertEquals("foo" + SEP, sb.toString());
+ assertEquals(1, count[0]);
+ assertEquals(1, count[1]);
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendln_StrBuilder_int_int() {
+ final int[] count = new int[2];
+ final StrBuilder sb = new StrBuilder() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public StrBuilder append(final StrBuilder str, final int startIndex, final int length) {
+ count[0]++;
+ return super.append(str, startIndex, length);
+ }
+ @Override
+ public StrBuilder appendNewLine() {
+ count[1]++;
+ return super.appendNewLine();
+ }
+ };
+ sb.appendln(new StrBuilder("foo"), 0, 3);
+ assertEquals("foo" + SEP, sb.toString());
+ assertEquals(1, count[0]);
+ assertEquals(1, count[1]);
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendln_CharArray() {
+ final int[] count = new int[2];
+ final StrBuilder sb = new StrBuilder() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public StrBuilder append(final char[] str) {
+ count[0]++;
+ return super.append(str);
+ }
+ @Override
+ public StrBuilder appendNewLine() {
+ count[1]++;
+ return super.appendNewLine();
+ }
+ };
+ sb.appendln("foo".toCharArray());
+ assertEquals("foo" + SEP, sb.toString());
+ assertEquals(1, count[0]);
+ assertEquals(1, count[1]);
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendln_CharArray_int_int() {
+ final int[] count = new int[2];
+ final StrBuilder sb = new StrBuilder() {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public StrBuilder append(final char[] str, final int startIndex, final int length) {
+ count[0]++;
+ return super.append(str, startIndex, length);
+ }
+ @Override
+ public StrBuilder appendNewLine() {
+ count[1]++;
+ return super.appendNewLine();
+ }
+ };
+ sb.appendln("foo".toCharArray(), 0, 3);
+ assertEquals("foo" + SEP, sb.toString());
+ assertEquals(1, count[0]);
+ assertEquals(1, count[1]);
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendln_Boolean() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendln(true);
+ assertEquals("true" + SEP, sb.toString());
+
+ sb.clear();
+ sb.appendln(false);
+ assertEquals("false" + SEP, sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendln_PrimitiveNumber() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendln(0);
+ assertEquals("0" + SEP, sb.toString());
+
+ sb.clear();
+ sb.appendln(1L);
+ assertEquals("1" + SEP, sb.toString());
+
+ sb.clear();
+ sb.appendln(2.3f);
+ assertEquals("2.3" + SEP, sb.toString());
+
+ sb.clear();
+ sb.appendln(4.5d);
+ assertEquals("4.5" + SEP, sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendPadding() {
+ final StrBuilder sb = new StrBuilder();
+ sb.append("foo");
+ assertEquals("foo", sb.toString());
+
+ sb.appendPadding(-1, '-');
+ assertEquals("foo", sb.toString());
+
+ sb.appendPadding(0, '-');
+ assertEquals("foo", sb.toString());
+
+ sb.appendPadding(1, '-');
+ assertEquals("foo-", sb.toString());
+
+ sb.appendPadding(16, '-');
+ assertEquals(20, sb.length());
+ // 12345678901234567890
+ assertEquals("foo-----------------", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendFixedWidthPadLeft() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendFixedWidthPadLeft("foo", -1, '-');
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadLeft("foo", 0, '-');
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadLeft("foo", 1, '-');
+ assertEquals("o", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadLeft("foo", 2, '-');
+ assertEquals("oo", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadLeft("foo", 3, '-');
+ assertEquals("foo", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadLeft("foo", 4, '-');
+ assertEquals("-foo", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadLeft("foo", 10, '-');
+ assertEquals(10, sb.length());
+ // 1234567890
+ assertEquals("-------foo", sb.toString());
+
+ sb.clear();
+ sb.setNullText("null");
+ sb.appendFixedWidthPadLeft(null, 5, '-');
+ assertEquals("-null", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendFixedWidthPadLeft_int() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendFixedWidthPadLeft(123, -1, '-');
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadLeft(123, 0, '-');
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadLeft(123, 1, '-');
+ assertEquals("3", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadLeft(123, 2, '-');
+ assertEquals("23", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadLeft(123, 3, '-');
+ assertEquals("123", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadLeft(123, 4, '-');
+ assertEquals("-123", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadLeft(123, 10, '-');
+ assertEquals(10, sb.length());
+ // 1234567890
+ assertEquals("-------123", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendFixedWidthPadRight() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendFixedWidthPadRight("foo", -1, '-');
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadRight("foo", 0, '-');
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadRight("foo", 1, '-');
+ assertEquals("f", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadRight("foo", 2, '-');
+ assertEquals("fo", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadRight("foo", 3, '-');
+ assertEquals("foo", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadRight("foo", 4, '-');
+ assertEquals("foo-", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadRight("foo", 10, '-');
+ assertEquals(10, sb.length());
+ // 1234567890
+ assertEquals("foo-------", sb.toString());
+
+ sb.clear();
+ sb.setNullText("null");
+ sb.appendFixedWidthPadRight(null, 5, '-');
+ assertEquals("null-", sb.toString());
+ }
+
+ // See: http://issues.apache.org/jira/browse/LANG-299
+ @Test
+ public void testLang299() {
+ final StrBuilder sb = new StrBuilder(1);
+ sb.appendFixedWidthPadRight("foo", 1, '-');
+ assertEquals("f", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendFixedWidthPadRight_int() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendFixedWidthPadRight(123, -1, '-');
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadRight(123, 0, '-');
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadRight(123, 1, '-');
+ assertEquals("1", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadRight(123, 2, '-');
+ assertEquals("12", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadRight(123, 3, '-');
+ assertEquals("123", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadRight(123, 4, '-');
+ assertEquals("123-", sb.toString());
+
+ sb.clear();
+ sb.appendFixedWidthPadRight(123, 10, '-');
+ assertEquals(10, sb.length());
+ // 1234567890
+ assertEquals("123-------", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppend_FormattedString() {
+ StrBuilder sb;
+
+ sb = new StrBuilder();
+ sb.append("Hi", (Object[]) null);
+ assertEquals("Hi", sb.toString());
+
+ sb = new StrBuilder();
+ sb.append("Hi", "Alice");
+ assertEquals("Hi", sb.toString());
+
+ sb = new StrBuilder();
+ sb.append("Hi %s", "Alice");
+ assertEquals("Hi Alice", sb.toString());
+
+ sb = new StrBuilder();
+ sb.append("Hi %s %,d", "Alice", 5000);
+ // group separator depends on system locale
+ final char groupingSeparator = DecimalFormatSymbols.getInstance().getGroupingSeparator();
+ final String expected = "Hi Alice 5" + groupingSeparator + "000";
+ assertEquals(expected, sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendAll_Array() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendAll((Object[]) null);
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendAll(new Object[0]);
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendAll(new Object[]{"foo", "bar", "baz"});
+ assertEquals("foobarbaz", sb.toString());
+
+ sb.clear();
+ sb.appendAll("foo", "bar", "baz");
+ assertEquals("foobarbaz", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendAll_Collection() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendAll((Collection<?>) null);
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendAll(Collections.EMPTY_LIST);
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendAll(Arrays.asList(new Object[]{"foo", "bar", "baz"}));
+ assertEquals("foobarbaz", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendAll_Iterator() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendAll((Iterator<?>) null);
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendAll(Collections.EMPTY_LIST.iterator());
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendAll(Arrays.asList(new Object[]{"foo", "bar", "baz"}).iterator());
+ assertEquals("foobarbaz", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendWithSeparators_Array() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendWithSeparators((Object[]) null, ",");
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendWithSeparators(new Object[0], ",");
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendWithSeparators(new Object[]{"foo", "bar", "baz"}, ",");
+ assertEquals("foo,bar,baz", sb.toString());
+
+ sb.clear();
+ sb.appendWithSeparators(new Object[]{"foo", "bar", "baz"}, null);
+ assertEquals("foobarbaz", sb.toString());
+
+ sb.clear();
+ sb.appendWithSeparators(new Object[]{"foo", null, "baz"}, ",");
+ assertEquals("foo,,baz", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendWithSeparators_Collection() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendWithSeparators((Collection<?>) null, ",");
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendWithSeparators(Collections.EMPTY_LIST, ",");
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendWithSeparators(Arrays.asList(new Object[]{"foo", "bar", "baz"}), ",");
+ assertEquals("foo,bar,baz", sb.toString());
+
+ sb.clear();
+ sb.appendWithSeparators(Arrays.asList(new Object[]{"foo", "bar", "baz"}), null);
+ assertEquals("foobarbaz", sb.toString());
+
+ sb.clear();
+ sb.appendWithSeparators(Arrays.asList(new Object[]{"foo", null, "baz"}), ",");
+ assertEquals("foo,,baz", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendWithSeparators_Iterator() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendWithSeparators((Iterator<?>) null, ",");
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendWithSeparators(Collections.EMPTY_LIST.iterator(), ",");
+ assertEquals("", sb.toString());
+
+ sb.clear();
+ sb.appendWithSeparators(Arrays.asList(new Object[]{"foo", "bar", "baz"}).iterator(), ",");
+ assertEquals("foo,bar,baz", sb.toString());
+
+ sb.clear();
+ sb.appendWithSeparators(Arrays.asList(new Object[]{"foo", "bar", "baz"}).iterator(), null);
+ assertEquals("foobarbaz", sb.toString());
+
+ sb.clear();
+ sb.appendWithSeparators(Arrays.asList(new Object[]{"foo", null, "baz"}).iterator(), ",");
+ assertEquals("foo,,baz", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendWithSeparatorsWithNullText() {
+ final StrBuilder sb = new StrBuilder();
+ sb.setNullText("null");
+ sb.appendWithSeparators(new Object[]{"foo", null, "baz"}, ",");
+ assertEquals("foo,null,baz", sb.toString());
+
+ sb.clear();
+ sb.appendWithSeparators(Arrays.asList(new Object[]{"foo", null, "baz"}), ",");
+ assertEquals("foo,null,baz", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendSeparator_String() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendSeparator(","); // no effect
+ assertEquals("", sb.toString());
+ sb.append("foo");
+ assertEquals("foo", sb.toString());
+ sb.appendSeparator(",");
+ assertEquals("foo,", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendSeparator_String_String() {
+ final StrBuilder sb = new StrBuilder();
+ final String startSeparator = "order by ";
+ final String standardSeparator = ",";
+ final String foo = "foo";
+ sb.appendSeparator(null, null);
+ assertEquals("", sb.toString());
+ sb.appendSeparator(standardSeparator, null);
+ assertEquals("", sb.toString());
+ sb.appendSeparator(standardSeparator, startSeparator);
+ assertEquals(startSeparator, sb.toString());
+ sb.appendSeparator(null, null);
+ assertEquals(startSeparator, sb.toString());
+ sb.appendSeparator(null, startSeparator);
+ assertEquals(startSeparator, sb.toString());
+ sb.append(foo);
+ assertEquals(startSeparator + foo, sb.toString());
+ sb.appendSeparator(standardSeparator, startSeparator);
+ assertEquals(startSeparator + foo + standardSeparator, sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendSeparator_char() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendSeparator(','); // no effect
+ assertEquals("", sb.toString());
+ sb.append("foo");
+ assertEquals("foo", sb.toString());
+ sb.appendSeparator(',');
+ assertEquals("foo,", sb.toString());
+ }
+ @Test
+ public void testAppendSeparator_char_char() {
+ final StrBuilder sb = new StrBuilder();
+ final char startSeparator = ':';
+ final char standardSeparator = ',';
+ final String foo = "foo";
+ sb.appendSeparator(standardSeparator, startSeparator); // no effect
+ assertEquals(String.valueOf(startSeparator), sb.toString());
+ sb.append(foo);
+ assertEquals(String.valueOf(startSeparator) + foo, sb.toString());
+ sb.appendSeparator(standardSeparator, startSeparator);
+ assertEquals(String.valueOf(startSeparator) + foo + standardSeparator, sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendSeparator_String_int() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendSeparator(",", 0); // no effect
+ assertEquals("", sb.toString());
+ sb.append("foo");
+ assertEquals("foo", sb.toString());
+ sb.appendSeparator(",", 1);
+ assertEquals("foo,", sb.toString());
+
+ sb.appendSeparator(",", -1); // no effect
+ assertEquals("foo,", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testAppendSeparator_char_int() {
+ final StrBuilder sb = new StrBuilder();
+ sb.appendSeparator(',', 0); // no effect
+ assertEquals("", sb.toString());
+ sb.append("foo");
+ assertEquals("foo", sb.toString());
+ sb.appendSeparator(',', 1);
+ assertEquals("foo,", sb.toString());
+
+ sb.appendSeparator(',', -1); // no effect
+ assertEquals("foo,", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testInsert() {
+
+ final StrBuilder sb = new StrBuilder();
+ sb.append("barbaz");
+ assertEquals("barbaz", sb.toString());
+
+ try {
+ sb.insert(-1, FOO);
+ fail("insert(-1, Object) expected StringIndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.insert(7, FOO);
+ fail("insert(7, Object) expected StringIndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ sb.insert(0, (Object) null);
+ assertEquals("barbaz", sb.toString());
+
+ sb.insert(0, FOO);
+ assertEquals("foobarbaz", sb.toString());
+
+ sb.clear();
+ sb.append("barbaz");
+ assertEquals("barbaz", sb.toString());
+
+ try {
+ sb.insert(-1, "foo");
+ fail("insert(-1, String) expected StringIndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.insert(7, "foo");
+ fail("insert(7, String) expected StringIndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ sb.insert(0, (String) null);
+ assertEquals("barbaz", sb.toString());
+
+ sb.insert(0, "foo");
+ assertEquals("foobarbaz", sb.toString());
+
+ sb.clear();
+ sb.append("barbaz");
+ assertEquals("barbaz", sb.toString());
+
+ try {
+ sb.insert(-1, new char[]{'f', 'o', 'o'});
+ fail("insert(-1, char[]) expected StringIndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.insert(7, new char[]{'f', 'o', 'o'});
+ fail("insert(7, char[]) expected StringIndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ sb.insert(0, (char[]) null);
+ assertEquals("barbaz", sb.toString());
+
+ sb.insert(0, new char[0]);
+ assertEquals("barbaz", sb.toString());
+
+ sb.insert(0, new char[]{'f', 'o', 'o'});
+ assertEquals("foobarbaz", sb.toString());
+
+ sb.clear();
+ sb.append("barbaz");
+ assertEquals("barbaz", sb.toString());
+
+ try {
+ sb.insert(-1, new char[]{'a', 'b', 'c', 'f', 'o', 'o', 'd', 'e', 'f'}, 3, 3);
+ fail("insert(-1, char[], 3, 3) expected StringIndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.insert(7, new char[]{'a', 'b', 'c', 'f', 'o', 'o', 'd', 'e', 'f'}, 3, 3);
+ fail("insert(7, char[], 3, 3) expected StringIndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ sb.insert(0, (char[]) null, 0, 0);
+ assertEquals("barbaz", sb.toString());
+
+ sb.insert(0, new char[0], 0, 0);
+ assertEquals("barbaz", sb.toString());
+
+ try {
+ sb.insert(0, new char[]{'a', 'b', 'c', 'f', 'o', 'o', 'd', 'e', 'f'}, -1, 3);
+ fail("insert(0, char[], -1, 3) expected StringIndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.insert(0, new char[]{'a', 'b', 'c', 'f', 'o', 'o', 'd', 'e', 'f'}, 10, 3);
+ fail("insert(0, char[], 10, 3) expected StringIndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.insert(0, new char[]{'a', 'b', 'c', 'f', 'o', 'o', 'd', 'e', 'f'}, 0, -1);
+ fail("insert(0, char[], 0, -1) expected StringIndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.insert(0, new char[]{'a', 'b', 'c', 'f', 'o', 'o', 'd', 'e', 'f'}, 0, 10);
+ fail("insert(0, char[], 0, 10) expected StringIndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ sb.insert(0, new char[]{'a', 'b', 'c', 'f', 'o', 'o', 'd', 'e', 'f'}, 0, 0);
+ assertEquals("barbaz", sb.toString());
+
+ sb.insert(0, new char[]{'a', 'b', 'c', 'f', 'o', 'o', 'd', 'e', 'f'}, 3, 3);
+ assertEquals("foobarbaz", sb.toString());
+
+ sb.clear();
+ sb.append("barbaz");
+ assertEquals("barbaz", sb.toString());
+
+ try {
+ sb.insert(-1, true);
+ fail("insert(-1, boolean) expected StringIndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.insert(7, true);
+ fail("insert(7, boolean) expected StringIndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ sb.insert(0, true);
+ assertEquals("truebarbaz", sb.toString());
+
+ sb.insert(0, false);
+ assertEquals("falsetruebarbaz", sb.toString());
+
+ sb.clear();
+ sb.append("barbaz");
+ assertEquals("barbaz", sb.toString());
+
+ try {
+ sb.insert(-1, '!');
+ fail("insert(-1, char) expected StringIndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.insert(7, '!');
+ fail("insert(7, char) expected StringIndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ sb.insert(0, '!');
+ assertEquals("!barbaz", sb.toString());
+
+ sb.clear();
+ sb.append("barbaz");
+ assertEquals("barbaz", sb.toString());
+
+ try {
+ sb.insert(-1, 0);
+ fail("insert(-1, int) expected StringIndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.insert(7, 0);
+ fail("insert(7, int) expected StringIndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ sb.insert(0, '0');
+ assertEquals("0barbaz", sb.toString());
+
+ sb.clear();
+ sb.append("barbaz");
+ assertEquals("barbaz", sb.toString());
+
+ try {
+ sb.insert(-1, 1L);
+ fail("insert(-1, long) expected StringIndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.insert(7, 1L);
+ fail("insert(7, long) expected StringIndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ sb.insert(0, 1L);
+ assertEquals("1barbaz", sb.toString());
+
+ sb.clear();
+ sb.append("barbaz");
+ assertEquals("barbaz", sb.toString());
+
+ try {
+ sb.insert(-1, 2.3F);
+ fail("insert(-1, float) expected StringIndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.insert(7, 2.3F);
+ fail("insert(7, float) expected StringIndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ sb.insert(0, 2.3F);
+ assertEquals("2.3barbaz", sb.toString());
+
+ sb.clear();
+ sb.append("barbaz");
+ assertEquals("barbaz", sb.toString());
+
+ try {
+ sb.insert(-1, 4.5D);
+ fail("insert(-1, double) expected StringIndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.insert(7, 4.5D);
+ fail("insert(7, double) expected StringIndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ sb.insert(0, 4.5D);
+ assertEquals("4.5barbaz", sb.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testInsertWithNullText() {
+ final StrBuilder sb = new StrBuilder();
+ sb.setNullText("null");
+ sb.append("barbaz");
+ assertEquals("barbaz", sb.toString());
+
+ try {
+ sb.insert(-1, FOO);
+ fail("insert(-1, Object) expected StringIndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.insert(7, FOO);
+ fail("insert(7, Object) expected StringIndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ sb.insert(0, (Object) null);
+ assertEquals("nullbarbaz", sb.toString());
+
+ sb.insert(0, FOO);
+ assertEquals("foonullbarbaz", sb.toString());
+
+ sb.clear();
+ sb.append("barbaz");
+ assertEquals("barbaz", sb.toString());
+
+ try {
+ sb.insert(-1, "foo");
+ fail("insert(-1, String) expected StringIndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ try {
+ sb.insert(7, "foo");
+ fail("insert(7, String) expected StringIndexOutOfBoundsException");
+ } catch (final IndexOutOfBoundsException e) {
+ // expected
+ }
+
+ sb.insert(0, (String) null);
+ assertEquals("nullbarbaz", sb.toString());
+
+ sb.insert(0, "foo");
+ assertEquals("foonullbarbaz", sb.toString());
+
+ sb.insert(0, (char[]) null);
+ assertEquals("nullfoonullbarbaz", sb.toString());
+
+ sb.insert(0, (char[]) null, 0, 0);
+ assertEquals("nullnullfoonullbarbaz", sb.toString());
+ }
+}
[03/14] [text] TEXT-23: adding tests to EntitiyArraysTest to validate
escaping vs unescaping
Posted by ch...@apache.org.
TEXT-23: adding tests to EntitiyArraysTest to validate escaping vs unescaping
Project: http://git-wip-us.apache.org/repos/asf/commons-text/repo
Commit: http://git-wip-us.apache.org/repos/asf/commons-text/commit/e3c006e1
Tree: http://git-wip-us.apache.org/repos/asf/commons-text/tree/e3c006e1
Diff: http://git-wip-us.apache.org/repos/asf/commons-text/diff/e3c006e1
Branch: refs/heads/master
Commit: e3c006e13b202cf92f274d7b26ee5ffb2316dd2c
Parents: 32196b1
Author: Rob Tompkins <ch...@capitalone.com>
Authored: Wed Nov 9 12:34:05 2016 -0500
Committer: Rob Tompkins <ch...@capitalone.com>
Committed: Wed Nov 9 12:34:05 2016 -0500
----------------------------------------------------------------------
.../text/translate/EntityArraysTest.java | 37 +++++++++++++++++++-
1 file changed, 36 insertions(+), 1 deletion(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/commons-text/blob/e3c006e1/src/test/java/org/apache/commons/text/translate/EntityArraysTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/text/translate/EntityArraysTest.java b/src/test/java/org/apache/commons/text/translate/EntityArraysTest.java
index 9b3dd4d..f1451b5 100644
--- a/src/test/java/org/apache/commons/text/translate/EntityArraysTest.java
+++ b/src/test/java/org/apache/commons/text/translate/EntityArraysTest.java
@@ -22,6 +22,7 @@ import org.junit.Test;
import java.util.HashSet;
import java.util.Set;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
@@ -67,6 +68,40 @@ public class EntityArraysTest {
}
assertTrue("One or more errors detected",success);
}
-
+
+ @Test
+ public void testISO8859Arrays() {
+ testEscapeVsUnescapeArrays(EntityArrays.ISO8859_1_ESCAPE(), EntityArrays.ISO8859_1_UNESCAPE());
+ }
+
+ @Test
+ public void testHtml40ExtendedArrays() {
+ testEscapeVsUnescapeArrays(EntityArrays.HTML40_EXTENDED_ESCAPE(), EntityArrays.HTML40_EXTENDED_UNESCAPE());
+ }
+
+ @Test
+ public void testAposArrays() {
+ testEscapeVsUnescapeArrays(EntityArrays.APOS_ESCAPE(), EntityArrays.APOS_UNESCAPE());
+ }
+
+ @Test
+ public void testBasicArrays() {
+ testEscapeVsUnescapeArrays(EntityArrays.BASIC_ESCAPE(), EntityArrays.BASIC_UNESCAPE());
+ }
+
+ @Test
+ public void testJavaCntrlCharsArrays() {
+ testEscapeVsUnescapeArrays(EntityArrays.JAVA_CTRL_CHARS_ESCAPE(), EntityArrays.JAVA_CTRL_CHARS_ESCAPE());
+ }
+
+ private void testEscapeVsUnescapeArrays(String[][] escapeArray, String[][] unescapeArray) {
+ for (String[] escapeElement : escapeArray) {
+ for (String[] unescapeElement : unescapeArray) {
+ if (escapeElement[0] == unescapeElement[1]) {
+ assertEquals(escapeElement[1], unescapeElement[0]);
+ }
+ }
+ }
+ }
}
[10/14] [text] TEXT-23: Adding remainder of the o.a.c.l.text package
Posted by ch...@apache.org.
TEXT-23: Adding remainder of the o.a.c.l.text package
Project: http://git-wip-us.apache.org/repos/asf/commons-text/repo
Commit: http://git-wip-us.apache.org/repos/asf/commons-text/commit/6f24aa45
Tree: http://git-wip-us.apache.org/repos/asf/commons-text/tree/6f24aa45
Diff: http://git-wip-us.apache.org/repos/asf/commons-text/diff/6f24aa45
Branch: refs/heads/master
Commit: 6f24aa4568fb62777d515625cd0146d4c36d5a6c
Parents: e3c006e
Author: Rob Tompkins <ch...@capitalone.com>
Authored: Wed Nov 9 15:35:38 2016 -0500
Committer: Rob Tompkins <ch...@capitalone.com>
Committed: Wed Nov 9 15:35:38 2016 -0500
----------------------------------------------------------------------
.../java/org/apache/commons/text/Builder.java | 87 +
.../apache/commons/text/CompositeFormat.java | 114 +
.../commons/text/ExtendedMessageFormat.java | 529 +++
.../org/apache/commons/text/FormatFactory.java | 39 +
.../apache/commons/text/FormattableUtils.java | 151 +
.../org/apache/commons/text/StrBuilder.java | 3117 ++++++++++++++++++
.../java/org/apache/commons/text/StrLookup.java | 178 +
.../org/apache/commons/text/StrMatcher.java | 437 +++
.../org/apache/commons/text/StrSubstitutor.java | 1227 +++++++
.../org/apache/commons/text/StrTokenizer.java | 1116 +++++++
.../java/org/apache/commons/text/WordUtils.java | 733 ++++
.../text/translate/AggregateTranslator.java | 2 -
.../text/translate/CharSequenceTranslator.java | 6 +-
.../text/translate/CodePointTranslator.java | 2 -
.../commons/text/translate/EntityArrays.java | 2 -
.../text/translate/JavaUnicodeEscaper.java | 14 +-
.../text/translate/LookupTranslator.java | 2 -
.../text/translate/NumericEntityEscaper.java | 14 +-
.../text/translate/NumericEntityUnescaper.java | 2 -
.../commons/text/translate/OctalUnescaper.java | 2 -
.../commons/text/translate/UnicodeEscaper.java | 14 +-
.../text/translate/UnicodeUnescaper.java | 2 -
.../commons/text/translate/package-info.java | 2 -
.../commons/text/CompositeFormatTest.java | 85 +
.../commons/text/ExtendedMessageFormatTest.java | 484 +++
.../commons/text/FormattableUtilsTest.java | 115 +
.../text/StrBuilderAppendInsertTest.java | 1607 +++++++++
.../org/apache/commons/text/StrBuilderTest.java | 2009 +++++++++++
.../org/apache/commons/text/StrLookupTest.java | 115 +
.../org/apache/commons/text/StrMatcherTest.java | 214 ++
.../apache/commons/text/StrSubstitutorTest.java | 740 +++++
.../apache/commons/text/StrTokenizerTest.java | 879 +++++
.../org/apache/commons/text/WordUtilsTest.java | 421 +++
33 files changed, 14417 insertions(+), 44 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/commons-text/blob/6f24aa45/src/main/java/org/apache/commons/text/Builder.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/Builder.java b/src/main/java/org/apache/commons/text/Builder.java
new file mode 100644
index 0000000..8d9a34d
--- /dev/null
+++ b/src/main/java/org/apache/commons/text/Builder.java
@@ -0,0 +1,87 @@
+/*
+ * 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;
+
+/**
+ * <p>
+ * The Builder interface is designed to designate a class as a <em>builder</em>
+ * object in the Builder design pattern. Builders are capable of creating and
+ * configuring objects or results that normally take multiple steps to construct
+ * or are very complex to derive.
+ * </p>
+ *
+ * <p>
+ * The builder interface defines a single method, {@link #build()}, that
+ * classes must implement. The result of this method should be the final
+ * configured object or result after all building operations are performed.
+ * </p>
+ *
+ * <p>
+ * It is a recommended practice that the methods supplied to configure the
+ * object or result being built return a reference to {@code this} so that
+ * method calls can be chained together.
+ * </p>
+ *
+ * <p>
+ * Example Builder:
+ * <pre><code>
+ * class FontBuilder implements Builder<Font> {
+ * private Font font;
+ *
+ * public FontBuilder(String fontName) {
+ * this.font = new Font(fontName, Font.PLAIN, 12);
+ * }
+ *
+ * public FontBuilder bold() {
+ * this.font = this.font.deriveFont(Font.BOLD);
+ * return this; // Reference returned so calls can be chained
+ * }
+ *
+ * public FontBuilder size(float pointSize) {
+ * this.font = this.font.deriveFont(pointSize);
+ * return this; // Reference returned so calls can be chained
+ * }
+ *
+ * // Other Font construction methods
+ *
+ * public Font build() {
+ * return this.font;
+ * }
+ * }
+ * </code></pre>
+ *
+ * Example Builder Usage:
+ * <pre><code>
+ * Font bold14ptSansSerifFont = new FontBuilder(Font.SANS_SERIF).bold()
+ * .size(14.0f)
+ * .build();
+ * </code></pre>
+ *
+ *
+ * @param <T> the type of object that the builder will construct or compute.
+ *
+ */
+public interface Builder<T> {
+
+ /**
+ * Returns a reference to the object being constructed or result being
+ * calculated by the builder.
+ *
+ * @return the object constructed or result calculated by the builder.
+ */
+ T build();
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/commons-text/blob/6f24aa45/src/main/java/org/apache/commons/text/CompositeFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/CompositeFormat.java b/src/main/java/org/apache/commons/text/CompositeFormat.java
new file mode 100644
index 0000000..6239561
--- /dev/null
+++ b/src/main/java/org/apache/commons/text/CompositeFormat.java
@@ -0,0 +1,114 @@
+/*
+ * 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.text.FieldPosition;
+import java.text.Format;
+import java.text.ParseException;
+import java.text.ParsePosition;
+
+/**
+ * Formats using one formatter and parses using a different formatter. An
+ * example of use for this would be a webapp where data is taken in one way and
+ * stored in a database another way.
+ */
+public class CompositeFormat extends Format {
+
+ /**
+ * Required for serialization support.
+ *
+ * @see java.io.Serializable
+ */
+ private static final long serialVersionUID = -4329119827877627683L;
+
+ /** The parser to use. */
+ private final Format parser;
+ /** The formatter to use. */
+ private final Format formatter;
+
+ /**
+ * Create a format that points its parseObject method to one implementation
+ * and its format method to another.
+ *
+ * @param parser implementation
+ * @param formatter implementation
+ */
+ public CompositeFormat(final Format parser, final Format formatter) {
+ this.parser = parser;
+ this.formatter = formatter;
+ }
+
+ /**
+ * Uses the formatter Format instance.
+ *
+ * @param obj the object to format
+ * @param toAppendTo the {@link StringBuffer} to append to
+ * @param pos the FieldPosition to use (or ignore).
+ * @return <code>toAppendTo</code>
+ * @see Format#format(Object, StringBuffer, FieldPosition)
+ */
+ @Override // Therefore has to use StringBuffer
+ public StringBuffer format(final Object obj, final StringBuffer toAppendTo,
+ final FieldPosition pos) {
+ return formatter.format(obj, toAppendTo, pos);
+ }
+
+ /**
+ * Uses the parser Format instance.
+ *
+ * @param source the String source
+ * @param pos the ParsePosition containing the position to parse from, will
+ * be updated according to parsing success (index) or failure
+ * (error index)
+ * @return the parsed Object
+ * @see Format#parseObject(String, ParsePosition)
+ */
+ @Override
+ public Object parseObject(final String source, final ParsePosition pos) {
+ return parser.parseObject(source, pos);
+ }
+
+ /**
+ * Provides access to the parser Format implementation.
+ *
+ * @return parser Format implementation
+ */
+ public Format getParser() {
+ return this.parser;
+ }
+
+ /**
+ * Provides access to the parser Format implementation.
+ *
+ * @return formatter Format implementation
+ */
+ public Format getFormatter() {
+ return this.formatter;
+ }
+
+ /**
+ * Utility method to parse and then reformat a String.
+ *
+ * @param input String to reformat
+ * @return A reformatted String
+ * @throws ParseException thrown by parseObject(String) call
+ */
+ public String reformat(final String input) throws ParseException {
+ return format(parseObject(input));
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/commons-text/blob/6f24aa45/src/main/java/org/apache/commons/text/ExtendedMessageFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/ExtendedMessageFormat.java b/src/main/java/org/apache/commons/text/ExtendedMessageFormat.java
new file mode 100644
index 0000000..c88752a
--- /dev/null
+++ b/src/main/java/org/apache/commons/text/ExtendedMessageFormat.java
@@ -0,0 +1,529 @@
+/*
+ * 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 org.apache.commons.lang3.ObjectUtils;
+
+import java.text.Format;
+import java.text.MessageFormat;
+import java.text.ParsePosition;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Extends <code>java.text.MessageFormat</code> to allow pluggable/additional formatting
+ * options for embedded format elements. Client code should specify a registry
+ * of <code>FormatFactory</code> instances associated with <code>String</code>
+ * format names. This registry will be consulted when the format elements are
+ * parsed from the message pattern. In this way custom patterns can be specified,
+ * and the formats supported by <code>java.text.MessageFormat</code> can be overridden
+ * at the format and/or format style level (see MessageFormat). A "format element"
+ * embedded in the message pattern is specified (<b>()?</b> signifies optionality):<br>
+ * <code>{</code><i>argument-number</i><b>(</b><code>,</code><i>format-name</i><b>
+ * (</b><code>,</code><i>format-style</i><b>)?)?</b><code>}</code>
+ *
+ * <p>
+ * <i>format-name</i> and <i>format-style</i> values are trimmed of surrounding whitespace
+ * in the manner of <code>java.text.MessageFormat</code>. If <i>format-name</i> denotes
+ * <code>FormatFactory formatFactoryInstance</code> in <code>registry</code>, a <code>Format</code>
+ * matching <i>format-name</i> and <i>format-style</i> is requested from
+ * <code>formatFactoryInstance</code>. If this is successful, the <code>Format</code>
+ * found is used for this format element.
+ * </p>
+ *
+ * <p><b>NOTICE:</b> The various subformat mutator methods are considered unnecessary; they exist on the parent
+ * class to allow the type of customization which it is the job of this class to provide in
+ * a configurable fashion. These methods have thus been disabled and will throw
+ * <code>UnsupportedOperationException</code> if called.
+ * </p>
+ *
+ * <p>Limitations inherited from <code>java.text.MessageFormat</code>:</p>
+ * <ul>
+ * <li>When using "choice" subformats, support for nested formatting instructions is limited
+ * to that provided by the base class.</li>
+ * <li>Thread-safety of <code>Format</code>s, including <code>MessageFormat</code> and thus
+ * <code>ExtendedMessageFormat</code>, is not guaranteed.</li>
+ * </ul>
+ */
+public class ExtendedMessageFormat extends MessageFormat {
+ private static final long serialVersionUID = -2362048321261811743L;
+ private static final int HASH_SEED = 31;
+
+ private static final String DUMMY_PATTERN = "";
+ private static final char START_FMT = ',';
+ private static final char END_FE = '}';
+ private static final char START_FE = '{';
+ private static final char QUOTE = '\'';
+
+ private String toPattern;
+ private final Map<String, ? extends FormatFactory> registry;
+
+ /**
+ * Create a new ExtendedMessageFormat for the default locale.
+ *
+ * @param pattern the pattern to use, not null
+ * @throws IllegalArgumentException in case of a bad pattern.
+ */
+ public ExtendedMessageFormat(final String pattern) {
+ this(pattern, Locale.getDefault());
+ }
+
+ /**
+ * Create a new ExtendedMessageFormat.
+ *
+ * @param pattern the pattern to use, not null
+ * @param locale the locale to use, not null
+ * @throws IllegalArgumentException in case of a bad pattern.
+ */
+ public ExtendedMessageFormat(final String pattern, final Locale locale) {
+ this(pattern, locale, null);
+ }
+
+ /**
+ * Create a new ExtendedMessageFormat for the default locale.
+ *
+ * @param pattern the pattern to use, not null
+ * @param registry the registry of format factories, may be null
+ * @throws IllegalArgumentException in case of a bad pattern.
+ */
+ public ExtendedMessageFormat(final String pattern, final Map<String, ? extends FormatFactory> registry) {
+ this(pattern, Locale.getDefault(), registry);
+ }
+
+ /**
+ * Create a new ExtendedMessageFormat.
+ *
+ * @param pattern the pattern to use, not null
+ * @param locale the locale to use, not null
+ * @param registry the registry of format factories, may be null
+ * @throws IllegalArgumentException in case of a bad pattern.
+ */
+ public ExtendedMessageFormat(final String pattern, final Locale locale, final Map<String, ? extends FormatFactory> registry) {
+ super(DUMMY_PATTERN);
+ setLocale(locale);
+ this.registry = registry;
+ applyPattern(pattern);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toPattern() {
+ return toPattern;
+ }
+
+ /**
+ * Apply the specified pattern.
+ *
+ * @param pattern String
+ */
+ @Override
+ public final void applyPattern(final String pattern) {
+ if (registry == null) {
+ super.applyPattern(pattern);
+ toPattern = super.toPattern();
+ return;
+ }
+ final ArrayList<Format> foundFormats = new ArrayList<>();
+ final ArrayList<String> foundDescriptions = new ArrayList<>();
+ final StringBuilder stripCustom = new StringBuilder(pattern.length());
+
+ final ParsePosition pos = new ParsePosition(0);
+ final char[] c = pattern.toCharArray();
+ int fmtCount = 0;
+ while (pos.getIndex() < pattern.length()) {
+ switch (c[pos.getIndex()]) {
+ case QUOTE:
+ appendQuotedString(pattern, pos, stripCustom);
+ break;
+ case START_FE:
+ fmtCount++;
+ seekNonWs(pattern, pos);
+ final int start = pos.getIndex();
+ final int index = readArgumentIndex(pattern, next(pos));
+ stripCustom.append(START_FE).append(index);
+ seekNonWs(pattern, pos);
+ Format format = null;
+ String formatDescription = null;
+ if (c[pos.getIndex()] == START_FMT) {
+ formatDescription = parseFormatDescription(pattern,
+ next(pos));
+ format = getFormat(formatDescription);
+ if (format == null) {
+ stripCustom.append(START_FMT).append(formatDescription);
+ }
+ }
+ foundFormats.add(format);
+ foundDescriptions.add(format == null ? null : formatDescription);
+ if(foundFormats.size() != fmtCount) {
+ throw new IllegalArgumentException("The validated expression is false");
+ }
+ if (foundDescriptions.size() != fmtCount) {
+ throw new IllegalArgumentException("The validated expression is false");
+ }
+ if (c[pos.getIndex()] != END_FE) {
+ throw new IllegalArgumentException(
+ "Unreadable format element at position " + start);
+ }
+ //$FALL-THROUGH$
+ default:
+ stripCustom.append(c[pos.getIndex()]);
+ next(pos);
+ }
+ }
+ super.applyPattern(stripCustom.toString());
+ toPattern = insertFormats(super.toPattern(), foundDescriptions);
+ if (containsElements(foundFormats)) {
+ final Format[] origFormats = getFormats();
+ // only loop over what we know we have, as MessageFormat on Java 1.3
+ // seems to provide an extra format element:
+ int i = 0;
+ for (final Iterator<Format> it = foundFormats.iterator(); it.hasNext(); i++) {
+ final Format f = it.next();
+ if (f != null) {
+ origFormats[i] = f;
+ }
+ }
+ super.setFormats(origFormats);
+ }
+ }
+
+ /**
+ * Throws UnsupportedOperationException - see class Javadoc for details.
+ *
+ * @param formatElementIndex format element index
+ * @param newFormat the new format
+ * @throws UnsupportedOperationException always thrown since this isn't supported by ExtendMessageFormat
+ */
+ @Override
+ public void setFormat(final int formatElementIndex, final Format newFormat) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Throws UnsupportedOperationException - see class Javadoc for details.
+ *
+ * @param argumentIndex argument index
+ * @param newFormat the new format
+ * @throws UnsupportedOperationException always thrown since this isn't supported by ExtendMessageFormat
+ */
+ @Override
+ public void setFormatByArgumentIndex(final int argumentIndex, final Format newFormat) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Throws UnsupportedOperationException - see class Javadoc for details.
+ *
+ * @param newFormats new formats
+ * @throws UnsupportedOperationException always thrown since this isn't supported by ExtendMessageFormat
+ */
+ @Override
+ public void setFormats(final Format[] newFormats) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Throws UnsupportedOperationException - see class Javadoc for details.
+ *
+ * @param newFormats new formats
+ * @throws UnsupportedOperationException always thrown since this isn't supported by ExtendMessageFormat
+ */
+ @Override
+ public void setFormatsByArgumentIndex(final Format[] newFormats) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Check if this extended message format is equal to another object.
+ *
+ * @param obj the object to compare to
+ * @return true if this object equals the other, otherwise false
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (ObjectUtils.notEqual(getClass(), obj.getClass())) {
+ return false;
+ }
+ final ExtendedMessageFormat rhs = (ExtendedMessageFormat)obj;
+ if (ObjectUtils.notEqual(toPattern, rhs.toPattern)) {
+ return false;
+ }
+ if (ObjectUtils.notEqual(registry, rhs.registry)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = HASH_SEED * result + Objects.hashCode(registry);
+ result = HASH_SEED * result + Objects.hashCode(toPattern);
+ return result;
+ }
+
+ /**
+ * Get a custom format from a format description.
+ *
+ * @param desc String
+ * @return Format
+ */
+ private Format getFormat(final String desc) {
+ if (registry != null) {
+ String name = desc;
+ String args = null;
+ final int i = desc.indexOf(START_FMT);
+ if (i > 0) {
+ name = desc.substring(0, i).trim();
+ args = desc.substring(i + 1).trim();
+ }
+ final FormatFactory factory = registry.get(name);
+ if (factory != null) {
+ return factory.getFormat(name, args, getLocale());
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Read the argument index from the current format element
+ *
+ * @param pattern pattern to parse
+ * @param pos current parse position
+ * @return argument index
+ */
+ private int readArgumentIndex(final String pattern, final ParsePosition pos) {
+ final int start = pos.getIndex();
+ seekNonWs(pattern, pos);
+ final StringBuilder result = new StringBuilder();
+ boolean error = false;
+ for (; !error && pos.getIndex() < pattern.length(); next(pos)) {
+ char c = pattern.charAt(pos.getIndex());
+ if (Character.isWhitespace(c)) {
+ seekNonWs(pattern, pos);
+ c = pattern.charAt(pos.getIndex());
+ if (c != START_FMT && c != END_FE) {
+ error = true;
+ continue;
+ }
+ }
+ if ((c == START_FMT || c == END_FE) && result.length() > 0) {
+ try {
+ return Integer.parseInt(result.toString());
+ } catch (final NumberFormatException e) { // NOPMD
+ // we've already ensured only digits, so unless something
+ // outlandishly large was specified we should be okay.
+ }
+ }
+ error = !Character.isDigit(c);
+ result.append(c);
+ }
+ if (error) {
+ throw new IllegalArgumentException(
+ "Invalid format argument index at position " + start + ": "
+ + pattern.substring(start, pos.getIndex()));
+ }
+ throw new IllegalArgumentException(
+ "Unterminated format element at position " + start);
+ }
+
+ /**
+ * Parse the format component of a format element.
+ *
+ * @param pattern string to parse
+ * @param pos current parse position
+ * @return Format description String
+ */
+ private String parseFormatDescription(final String pattern, final ParsePosition pos) {
+ final int start = pos.getIndex();
+ seekNonWs(pattern, pos);
+ final int text = pos.getIndex();
+ int depth = 1;
+ for (; pos.getIndex() < pattern.length(); next(pos)) {
+ switch (pattern.charAt(pos.getIndex())) {
+ case START_FE:
+ depth++;
+ break;
+ case END_FE:
+ depth--;
+ if (depth == 0) {
+ return pattern.substring(text, pos.getIndex());
+ }
+ break;
+ case QUOTE:
+ getQuotedString(pattern, pos);
+ break;
+ default:
+ break;
+ }
+ }
+ throw new IllegalArgumentException(
+ "Unterminated format element at position " + start);
+ }
+
+ /**
+ * Insert formats back into the pattern for toPattern() support.
+ *
+ * @param pattern source
+ * @param customPatterns The custom patterns to re-insert, if any
+ * @return full pattern
+ */
+ private String insertFormats(final String pattern, final ArrayList<String> customPatterns) {
+ if (!containsElements(customPatterns)) {
+ return pattern;
+ }
+ final StringBuilder sb = new StringBuilder(pattern.length() * 2);
+ final ParsePosition pos = new ParsePosition(0);
+ int fe = -1;
+ int depth = 0;
+ while (pos.getIndex() < pattern.length()) {
+ final char c = pattern.charAt(pos.getIndex());
+ switch (c) {
+ case QUOTE:
+ appendQuotedString(pattern, pos, sb);
+ break;
+ case START_FE:
+ depth++;
+ sb.append(START_FE).append(readArgumentIndex(pattern, next(pos)));
+ // do not look for custom patterns when they are embedded, e.g. in a choice
+ if (depth == 1) {
+ fe++;
+ final String customPattern = customPatterns.get(fe);
+ if (customPattern != null) {
+ sb.append(START_FMT).append(customPattern);
+ }
+ }
+ break;
+ case END_FE:
+ depth--;
+ //$FALL-THROUGH$
+ default:
+ sb.append(c);
+ next(pos);
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Consume whitespace from the current parse position.
+ *
+ * @param pattern String to read
+ * @param pos current position
+ */
+ private void seekNonWs(final String pattern, final ParsePosition pos) {
+ int len = 0;
+ final char[] buffer = pattern.toCharArray();
+ do {
+ len = StrMatcher.splitMatcher().isMatch(buffer, pos.getIndex());
+ pos.setIndex(pos.getIndex() + len);
+ } while (len > 0 && pos.getIndex() < pattern.length());
+ }
+
+ /**
+ * Convenience method to advance parse position by 1
+ *
+ * @param pos ParsePosition
+ * @return <code>pos</code>
+ */
+ private ParsePosition next(final ParsePosition pos) {
+ pos.setIndex(pos.getIndex() + 1);
+ return pos;
+ }
+
+ /**
+ * Consume a quoted string, adding it to <code>appendTo</code> if
+ * specified.
+ *
+ * @param pattern pattern to parse
+ * @param pos current parse position
+ * @param appendTo optional StringBuilder to append
+ * @return <code>appendTo</code>
+ */
+ private StringBuilder appendQuotedString(final String pattern, final ParsePosition pos,
+ final StringBuilder appendTo) {
+ assert pattern.toCharArray()[pos.getIndex()] == QUOTE :
+ "Quoted string must start with quote character";
+
+ // handle quote character at the beginning of the string
+ if(appendTo != null) {
+ appendTo.append(QUOTE);
+ }
+ next(pos);
+
+ final int start = pos.getIndex();
+ final char[] c = pattern.toCharArray();
+ final int lastHold = start;
+ for (int i = pos.getIndex(); i < pattern.length(); i++) {
+ switch (c[pos.getIndex()]) {
+ case QUOTE:
+ next(pos);
+ return appendTo == null ? null : appendTo.append(c, lastHold,
+ pos.getIndex() - lastHold);
+ default:
+ next(pos);
+ }
+ }
+ throw new IllegalArgumentException(
+ "Unterminated quoted string at position " + start);
+ }
+
+ /**
+ * Consume quoted string only
+ *
+ * @param pattern pattern to parse
+ * @param pos current parse position
+ */
+ private void getQuotedString(final String pattern, final ParsePosition pos) {
+ appendQuotedString(pattern, pos, null);
+ }
+
+ /**
+ * Learn whether the specified Collection contains non-null elements.
+ * @param coll to check
+ * @return <code>true</code> if some Object was found, <code>false</code> otherwise.
+ */
+ private boolean containsElements(final Collection<?> coll) {
+ if (coll == null || coll.isEmpty()) {
+ return false;
+ }
+ for (final Object name : coll) {
+ if (name != null) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
http://git-wip-us.apache.org/repos/asf/commons-text/blob/6f24aa45/src/main/java/org/apache/commons/text/FormatFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/FormatFactory.java b/src/main/java/org/apache/commons/text/FormatFactory.java
new file mode 100644
index 0000000..1f85b0f
--- /dev/null
+++ b/src/main/java/org/apache/commons/text/FormatFactory.java
@@ -0,0 +1,39 @@
+/*
+ * 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.text.Format;
+import java.util.Locale;
+
+/**
+ * Format factory.
+ */
+public interface FormatFactory {
+
+ /**
+ * Create or retrieve a format instance.
+ *
+ * @param name The format type name
+ * @param arguments Arguments used to create the format instance. This allows the
+ * <code>FormatFactory</code> to implement the "format style"
+ * concept from <code>java.text.MessageFormat</code>.
+ * @param locale The locale, may be null
+ * @return The format instance
+ */
+ Format getFormat(String name, String arguments, Locale locale);
+
+}
http://git-wip-us.apache.org/repos/asf/commons-text/blob/6f24aa45/src/main/java/org/apache/commons/text/FormattableUtils.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/FormattableUtils.java b/src/main/java/org/apache/commons/text/FormattableUtils.java
new file mode 100644
index 0000000..887f593
--- /dev/null
+++ b/src/main/java/org/apache/commons/text/FormattableUtils.java
@@ -0,0 +1,151 @@
+/*
+ * 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.util.Formattable;
+import java.util.Formatter;
+
+import static java.util.FormattableFlags.LEFT_JUSTIFY;
+
+/**
+ * <p>Provides utilities for working with the {@code Formattable} interface.</p>
+ *
+ * <p>The {@link Formattable} interface provides basic control over formatting
+ * when using a {@code Formatter}. It is primarily concerned with numeric precision
+ * and padding, and is not designed to allow generalised alternate formats.</p>
+ *
+ */
+public class FormattableUtils {
+
+ /**
+ * A format that simply outputs the value as a string.
+ */
+ private static final String SIMPLEST_FORMAT = "%s";
+
+ /**
+ * <p>{@code FormattableUtils} instances should NOT be constructed in
+ * standard programming. Instead, the methods of the class should be invoked
+ * statically.</p>
+ *
+ * <p>This constructor is public to permit tools that require a JavaBean
+ * instance to operate.</p>
+ */
+ public FormattableUtils() {
+ super();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Get the default formatted representation of the specified
+ * {@code Formattable}.
+ *
+ * @param formattable the instance to convert to a string, not null
+ * @return the resulting string, not null
+ */
+ public static String toString(final Formattable formattable) {
+ return String.format(SIMPLEST_FORMAT, formattable);
+ }
+
+ /**
+ * Handles the common {@code Formattable} operations of truncate-pad-append,
+ * with no ellipsis on precision overflow, and padding width underflow with
+ * spaces.
+ *
+ * @param seq the string to handle, not null
+ * @param formatter the destination formatter, not null
+ * @param flags the flags for formatting, see {@code Formattable}
+ * @param width the width of the output, see {@code Formattable}
+ * @param precision the precision of the output, see {@code Formattable}
+ * @return the {@code formatter} instance, not null
+ */
+ public static Formatter append(final CharSequence seq, final Formatter formatter, final int flags, final int width,
+ final int precision) {
+ return append(seq, formatter, flags, width, precision, ' ', null);
+ }
+
+ /**
+ * Handles the common {@link Formattable} operations of truncate-pad-append,
+ * with no ellipsis on precision overflow.
+ *
+ * @param seq the string to handle, not null
+ * @param formatter the destination formatter, not null
+ * @param flags the flags for formatting, see {@code Formattable}
+ * @param width the width of the output, see {@code Formattable}
+ * @param precision the precision of the output, see {@code Formattable}
+ * @param padChar the pad character to use
+ * @return the {@code formatter} instance, not null
+ */
+ public static Formatter append(final CharSequence seq, final Formatter formatter, final int flags, final int width,
+ final int precision, final char padChar) {
+ return append(seq, formatter, flags, width, precision, padChar, null);
+ }
+
+ /**
+ * Handles the common {@link Formattable} operations of truncate-pad-append,
+ * padding width underflow with spaces.
+ *
+ * @param seq the string to handle, not null
+ * @param formatter the destination formatter, not null
+ * @param flags the flags for formatting, see {@code Formattable}
+ * @param width the width of the output, see {@code Formattable}
+ * @param precision the precision of the output, see {@code Formattable}
+ * @param ellipsis the ellipsis to use when precision dictates truncation, null or
+ * empty causes a hard truncation
+ * @return the {@code formatter} instance, not null
+ */
+ public static Formatter append(final CharSequence seq, final Formatter formatter, final int flags, final int width,
+ final int precision, final CharSequence ellipsis) {
+ return append(seq, formatter, flags, width, precision, ' ', ellipsis);
+ }
+
+ /**
+ * Handles the common {@link Formattable} operations of truncate-pad-append.
+ *
+ * @param seq the string to handle, not null
+ * @param formatter the destination formatter, not null
+ * @param flags the flags for formatting, see {@code Formattable}
+ * @param width the width of the output, see {@code Formattable}
+ * @param precision the precision of the output, see {@code Formattable}
+ * @param padChar the pad character to use
+ * @param ellipsis the ellipsis to use when precision dictates truncation, null or
+ * empty causes a hard truncation
+ * @return the {@code formatter} instance, not null
+ */
+ public static Formatter append(final CharSequence seq, final Formatter formatter, final int flags, final int width,
+ final int precision, final char padChar, final CharSequence ellipsis) {
+ if ( ! (ellipsis == null || precision < 0 || ellipsis.length() <= precision) ) {
+ throw new IllegalArgumentException(String.format("Specified ellipsis '%1$s' exceeds precision of %2$s", ellipsis, Integer.valueOf(precision)));
+ }
+ final StringBuilder buf = new StringBuilder(seq);
+ if (precision >= 0 && precision < seq.length()) {
+ final CharSequence _ellipsis;
+ if (ellipsis == null) {
+ _ellipsis = "";
+ } else {
+ _ellipsis = ellipsis;
+ }
+ buf.replace(precision - _ellipsis.length(), seq.length(), _ellipsis.toString());
+ }
+ final boolean leftJustify = (flags & LEFT_JUSTIFY) == LEFT_JUSTIFY;
+ for (int i = buf.length(); i < width; i++) {
+ buf.insert(leftJustify ? i : 0, padChar);
+ }
+ formatter.format(buf.toString());
+ return formatter;
+ }
+
+}
[04/14] [text] TEXT-23: Adding remainder of the o.a.c.l.text package
Posted by ch...@apache.org.
http://git-wip-us.apache.org/repos/asf/commons-text/blob/6f24aa45/src/test/java/org/apache/commons/text/StrSubstitutorTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/text/StrSubstitutorTest.java b/src/test/java/org/apache/commons/text/StrSubstitutorTest.java
new file mode 100644
index 0000000..e382691
--- /dev/null
+++ b/src/test/java/org/apache/commons/text/StrSubstitutorTest.java
@@ -0,0 +1,740 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.commons.lang3.mutable.MutableObject;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test class for {@link StrSubstitutor}.
+ */
+public class StrSubstitutorTest {
+
+ private Map<String, String> values;
+
+ @Before
+ public void setUp() throws Exception {
+ values = new HashMap<>();
+ values.put("animal", "quick brown fox");
+ values.put("target", "lazy dog");
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ values = null;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Tests simple key replace.
+ */
+ @Test
+ public void testReplaceSimple() {
+ doTestReplace("The quick brown fox jumps over the lazy dog.", "The ${animal} jumps over the ${target}.", true);
+ }
+
+ /**
+ * Tests simple key replace.
+ */
+ @Test
+ public void testReplaceSolo() {
+ doTestReplace("quick brown fox", "${animal}", false);
+ }
+
+ /**
+ * Tests replace with no variables.
+ */
+ @Test
+ public void testReplaceNoVariables() {
+ doTestNoReplace("The balloon arrived.");
+ }
+
+ /**
+ * Tests replace with null.
+ */
+ @Test
+ public void testReplaceNull() {
+ doTestNoReplace(null);
+ }
+
+ /**
+ * Tests replace with null.
+ */
+ @Test
+ public void testReplaceEmpty() {
+ doTestNoReplace("");
+ }
+
+ /**
+ * Tests key replace changing map after initialization (not recommended).
+ */
+ @Test
+ public void testReplaceChangedMap() {
+ final StrSubstitutor sub = new StrSubstitutor(values);
+ values.put("target", "moon");
+ assertEquals("The quick brown fox jumps over the moon.", sub.replace("The ${animal} jumps over the ${target}."));
+ }
+
+ /**
+ * Tests unknown key replace.
+ */
+ @Test
+ public void testReplaceUnknownKey() {
+ doTestReplace("The ${person} jumps over the lazy dog.", "The ${person} jumps over the ${target}.", true);
+ doTestReplace("The ${person} jumps over the lazy dog. 1234567890.", "The ${person} jumps over the ${target}. ${undefined.number:-1234567890}.", true);
+ }
+
+ /**
+ * Tests adjacent keys.
+ */
+ @Test
+ public void testReplaceAdjacentAtStart() {
+ values.put("code", "GBP");
+ values.put("amount", "12.50");
+ final StrSubstitutor sub = new StrSubstitutor(values);
+ assertEquals("GBP12.50 charged", sub.replace("${code}${amount} charged"));
+ }
+
+ /**
+ * Tests adjacent keys.
+ */
+ @Test
+ public void testReplaceAdjacentAtEnd() {
+ values.put("code", "GBP");
+ values.put("amount", "12.50");
+ final StrSubstitutor sub = new StrSubstitutor(values);
+ assertEquals("Amount is GBP12.50", sub.replace("Amount is ${code}${amount}"));
+ }
+
+ /**
+ * Tests simple recursive replace.
+ */
+ @Test
+ public void testReplaceRecursive() {
+ values.put("animal", "${critter}");
+ values.put("target", "${pet}");
+ values.put("pet", "${petCharacteristic} dog");
+ values.put("petCharacteristic", "lazy");
+ values.put("critter", "${critterSpeed} ${critterColor} ${critterType}");
+ values.put("critterSpeed", "quick");
+ values.put("critterColor", "brown");
+ values.put("critterType", "fox");
+ doTestReplace("The quick brown fox jumps over the lazy dog.", "The ${animal} jumps over the ${target}.", true);
+
+ values.put("pet", "${petCharacteristicUnknown:-lazy} dog");
+ doTestReplace("The quick brown fox jumps over the lazy dog.", "The ${animal} jumps over the ${target}.", true);
+ }
+
+ /**
+ * Tests escaping.
+ */
+ @Test
+ public void testReplaceEscaping() {
+ doTestReplace("The ${animal} jumps over the lazy dog.", "The $${animal} jumps over the ${target}.", true);
+ }
+
+ /**
+ * Tests escaping.
+ */
+ @Test
+ public void testReplaceSoloEscaping() {
+ doTestReplace("${animal}", "$${animal}", false);
+ }
+
+ /**
+ * Tests complex escaping.
+ */
+ @Test
+ public void testReplaceComplexEscaping() {
+ doTestReplace("The ${quick brown fox} jumps over the lazy dog.", "The $${${animal}} jumps over the ${target}.", true);
+ doTestReplace("The ${quick brown fox} jumps over the lazy dog. ${1234567890}.", "The $${${animal}} jumps over the ${target}. $${${undefined.number:-1234567890}}.", true);
+ }
+
+ /**
+ * Tests when no prefix or suffix.
+ */
+ @Test
+ public void testReplaceNoPrefixNoSuffix() {
+ doTestReplace("The animal jumps over the lazy dog.", "The animal jumps over the ${target}.", true);
+ }
+
+ /**
+ * Tests when no incomplete prefix.
+ */
+ @Test
+ public void testReplaceIncompletePrefix() {
+ doTestReplace("The {animal} jumps over the lazy dog.", "The {animal} jumps over the ${target}.", true);
+ }
+
+ /**
+ * Tests when prefix but no suffix.
+ */
+ @Test
+ public void testReplacePrefixNoSuffix() {
+ doTestReplace("The ${animal jumps over the ${target} lazy dog.", "The ${animal jumps over the ${target} ${target}.", true);
+ }
+
+ /**
+ * Tests when suffix but no prefix.
+ */
+ @Test
+ public void testReplaceNoPrefixSuffix() {
+ doTestReplace("The animal} jumps over the lazy dog.", "The animal} jumps over the ${target}.", true);
+ }
+
+ /**
+ * Tests when no variable name.
+ */
+ @Test
+ public void testReplaceEmptyKeys() {
+ doTestReplace("The ${} jumps over the lazy dog.", "The ${} jumps over the ${target}.", true);
+ doTestReplace("The animal jumps over the lazy dog.", "The ${:-animal} jumps over the ${target}.", true);
+ }
+
+ /**
+ * Tests replace creates output same as input.
+ */
+ @Test
+ public void testReplaceToIdentical() {
+ values.put("animal", "$${${thing}}");
+ values.put("thing", "animal");
+ doTestReplace("The ${animal} jumps.", "The ${animal} jumps.", true);
+ }
+
+ /**
+ * Tests a cyclic replace operation.
+ * The cycle should be detected and cause an exception to be thrown.
+ */
+ @Test
+ public void testCyclicReplacement() {
+ final Map<String, String> map = new HashMap<>();
+ map.put("animal", "${critter}");
+ map.put("target", "${pet}");
+ map.put("pet", "${petCharacteristic} dog");
+ map.put("petCharacteristic", "lazy");
+ map.put("critter", "${critterSpeed} ${critterColor} ${critterType}");
+ map.put("critterSpeed", "quick");
+ map.put("critterColor", "brown");
+ map.put("critterType", "${animal}");
+ StrSubstitutor sub = new StrSubstitutor(map);
+ try {
+ sub.replace("The ${animal} jumps over the ${target}.");
+ fail("Cyclic replacement was not detected!");
+ } catch (final IllegalStateException ex) {
+ // expected
+ }
+
+ // also check even when default value is set.
+ map.put("critterType", "${animal:-fox}");
+ sub = new StrSubstitutor(map);
+ try {
+ sub.replace("The ${animal} jumps over the ${target}.");
+ fail("Cyclic replacement was not detected!");
+ } catch (final IllegalStateException ex) {
+ // expected
+ }
+ }
+
+ /**
+ * Tests interpolation with weird boundary patterns.
+ */
+ @Test
+ public void testReplaceWeirdPattens() {
+ doTestNoReplace("");
+ doTestNoReplace("${}");
+ doTestNoReplace("${ }");
+ doTestNoReplace("${\t}");
+ doTestNoReplace("${\n}");
+ doTestNoReplace("${\b}");
+ doTestNoReplace("${");
+ doTestNoReplace("$}");
+ doTestNoReplace("}");
+ doTestNoReplace("${}$");
+ doTestNoReplace("${${");
+ doTestNoReplace("${${}}");
+ doTestNoReplace("${$${}}");
+ doTestNoReplace("${$$${}}");
+ doTestNoReplace("${$$${$}}");
+ doTestNoReplace("${${}}");
+ doTestNoReplace("${${ }}");
+ }
+
+ /**
+ * Tests simple key replace.
+ */
+ @Test
+ public void testReplacePartialString_noReplace() {
+ final StrSubstitutor sub = new StrSubstitutor();
+ assertEquals("${animal} jumps", sub.replace("The ${animal} jumps over the ${target}.", 4, 15));
+ }
+
+ /**
+ * Tests whether a variable can be replaced in a variable name.
+ */
+ @Test
+ public void testReplaceInVariable() {
+ values.put("animal.1", "fox");
+ values.put("animal.2", "mouse");
+ values.put("species", "2");
+ final StrSubstitutor sub = new StrSubstitutor(values);
+ sub.setEnableSubstitutionInVariables(true);
+ assertEquals(
+ "Wrong result (1)",
+ "The mouse jumps over the lazy dog.",
+ sub.replace("The ${animal.${species}} jumps over the ${target}."));
+ values.put("species", "1");
+ assertEquals(
+ "Wrong result (2)",
+ "The fox jumps over the lazy dog.",
+ sub.replace("The ${animal.${species}} jumps over the ${target}."));
+ assertEquals(
+ "Wrong result (3)",
+ "The fox jumps over the lazy dog.",
+ sub.replace("The ${unknown.animal.${unknown.species:-1}:-fox} jumps over the ${unknow.target:-lazy dog}."));
+ }
+
+ /**
+ * Tests whether substitution in variable names is disabled per default.
+ */
+ @Test
+ public void testReplaceInVariableDisabled() {
+ values.put("animal.1", "fox");
+ values.put("animal.2", "mouse");
+ values.put("species", "2");
+ final StrSubstitutor sub = new StrSubstitutor(values);
+ assertEquals(
+ "Wrong result (1)",
+ "The ${animal.${species}} jumps over the lazy dog.",
+ sub.replace("The ${animal.${species}} jumps over the ${target}."));
+ assertEquals(
+ "Wrong result (2)",
+ "The ${animal.${species:-1}} jumps over the lazy dog.",
+ sub.replace("The ${animal.${species:-1}} jumps over the ${target}."));
+ }
+
+ /**
+ * Tests complex and recursive substitution in variable names.
+ */
+ @Test
+ public void testReplaceInVariableRecursive() {
+ values.put("animal.2", "brown fox");
+ values.put("animal.1", "white mouse");
+ values.put("color", "white");
+ values.put("species.white", "1");
+ values.put("species.brown", "2");
+ final StrSubstitutor sub = new StrSubstitutor(values);
+ sub.setEnableSubstitutionInVariables(true);
+ assertEquals(
+ "Wrong result (1)",
+ "The white mouse jumps over the lazy dog.",
+ sub.replace("The ${animal.${species.${color}}} jumps over the ${target}."));
+ assertEquals(
+ "Wrong result (2)",
+ "The brown fox jumps over the lazy dog.",
+ sub.replace("The ${animal.${species.${unknownColor:-brown}}} jumps over the ${target}."));
+ }
+
+ @Test
+ public void testDefaultValueDelimiters() {
+ final Map<String, String> map = new HashMap<>();
+ map.put("animal", "fox");
+ map.put("target", "dog");
+
+ StrSubstitutor sub = new StrSubstitutor(map, "${", "}", '$');
+ assertEquals("The fox jumps over the lazy dog. 1234567890.",
+ sub.replace("The ${animal} jumps over the lazy ${target}. ${undefined.number:-1234567890}."));
+
+ sub = new StrSubstitutor(map, "${", "}", '$', "?:");
+ assertEquals("The fox jumps over the lazy dog. 1234567890.",
+ sub.replace("The ${animal} jumps over the lazy ${target}. ${undefined.number?:1234567890}."));
+
+ sub = new StrSubstitutor(map, "${", "}", '$', "||");
+ assertEquals("The fox jumps over the lazy dog. 1234567890.",
+ sub.replace("The ${animal} jumps over the lazy ${target}. ${undefined.number||1234567890}."));
+
+ sub = new StrSubstitutor(map, "${", "}", '$', "!");
+ assertEquals("The fox jumps over the lazy dog. 1234567890.",
+ sub.replace("The ${animal} jumps over the lazy ${target}. ${undefined.number!1234567890}."));
+
+ sub = new StrSubstitutor(map, "${", "}", '$', "");
+ sub.setValueDelimiterMatcher(null);
+ assertEquals("The fox jumps over the lazy dog. ${undefined.number!1234567890}.",
+ sub.replace("The ${animal} jumps over the lazy ${target}. ${undefined.number!1234567890}."));
+
+ sub = new StrSubstitutor(map, "${", "}", '$');
+ sub.setValueDelimiterMatcher(null);
+ assertEquals("The fox jumps over the lazy dog. ${undefined.number!1234567890}.",
+ sub.replace("The ${animal} jumps over the lazy ${target}. ${undefined.number!1234567890}."));
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Tests protected.
+ */
+ @Test
+ public void testResolveVariable() {
+ final StrBuilder builder = new StrBuilder("Hi ${name}!");
+ final Map<String, String> map = new HashMap<>();
+ map.put("name", "commons");
+ final StrSubstitutor sub = new StrSubstitutor(map) {
+ @Override
+ protected String resolveVariable(final String variableName, final StrBuilder buf, final int startPos, final int endPos) {
+ assertEquals("name", variableName);
+ assertSame(builder, buf);
+ assertEquals(3, startPos);
+ assertEquals(10, endPos);
+ return "jakarta";
+ }
+ };
+ sub.replaceIn(builder);
+ assertEquals("Hi jakarta!", builder.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Tests constructor.
+ */
+ @Test
+ public void testConstructorNoArgs() {
+ final StrSubstitutor sub = new StrSubstitutor();
+ assertEquals("Hi ${name}", sub.replace("Hi ${name}"));
+ }
+
+ /**
+ * Tests constructor.
+ */
+ @Test
+ public void testConstructorMapPrefixSuffix() {
+ final Map<String, String> map = new HashMap<>();
+ map.put("name", "commons");
+ final StrSubstitutor sub = new StrSubstitutor(map, "<", ">");
+ assertEquals("Hi < commons", sub.replace("Hi $< <name>"));
+ }
+
+ /**
+ * Tests constructor.
+ */
+ @Test
+ public void testConstructorMapFull() {
+ final Map<String, String> map = new HashMap<>();
+ map.put("name", "commons");
+ StrSubstitutor sub = new StrSubstitutor(map, "<", ">", '!');
+ assertEquals("Hi < commons", sub.replace("Hi !< <name>"));
+ sub = new StrSubstitutor(map, "<", ">", '!', "||");
+ assertEquals("Hi < commons", sub.replace("Hi !< <name2||commons>"));
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Tests get set.
+ */
+ @Test
+ public void testGetSetEscape() {
+ final StrSubstitutor sub = new StrSubstitutor();
+ assertEquals('$', sub.getEscapeChar());
+ sub.setEscapeChar('<');
+ assertEquals('<', sub.getEscapeChar());
+ }
+
+ /**
+ * Tests get set.
+ */
+ @Test
+ public void testGetSetPrefix() {
+ final StrSubstitutor sub = new StrSubstitutor();
+ assertTrue(sub.getVariablePrefixMatcher() instanceof StrMatcher.StringMatcher);
+ sub.setVariablePrefix('<');
+ assertTrue(sub.getVariablePrefixMatcher() instanceof StrMatcher.CharMatcher);
+
+ sub.setVariablePrefix("<<");
+ assertTrue(sub.getVariablePrefixMatcher() instanceof StrMatcher.StringMatcher);
+ try {
+ sub.setVariablePrefix((String) null);
+ fail();
+ } catch (final IllegalArgumentException ex) {
+ // expected
+ }
+ assertTrue(sub.getVariablePrefixMatcher() instanceof StrMatcher.StringMatcher);
+
+ final StrMatcher matcher = StrMatcher.commaMatcher();
+ sub.setVariablePrefixMatcher(matcher);
+ assertSame(matcher, sub.getVariablePrefixMatcher());
+ try {
+ sub.setVariablePrefixMatcher((StrMatcher) null);
+ fail();
+ } catch (final IllegalArgumentException ex) {
+ // expected
+ }
+ assertSame(matcher, sub.getVariablePrefixMatcher());
+ }
+
+ /**
+ * Tests get set.
+ */
+ @Test
+ public void testGetSetSuffix() {
+ final StrSubstitutor sub = new StrSubstitutor();
+ assertTrue(sub.getVariableSuffixMatcher() instanceof StrMatcher.StringMatcher);
+ sub.setVariableSuffix('<');
+ assertTrue(sub.getVariableSuffixMatcher() instanceof StrMatcher.CharMatcher);
+
+ sub.setVariableSuffix("<<");
+ assertTrue(sub.getVariableSuffixMatcher() instanceof StrMatcher.StringMatcher);
+ try {
+ sub.setVariableSuffix((String) null);
+ fail();
+ } catch (final IllegalArgumentException ex) {
+ // expected
+ }
+ assertTrue(sub.getVariableSuffixMatcher() instanceof StrMatcher.StringMatcher);
+
+ final StrMatcher matcher = StrMatcher.commaMatcher();
+ sub.setVariableSuffixMatcher(matcher);
+ assertSame(matcher, sub.getVariableSuffixMatcher());
+ try {
+ sub.setVariableSuffixMatcher((StrMatcher) null);
+ fail();
+ } catch (final IllegalArgumentException ex) {
+ // expected
+ }
+ assertSame(matcher, sub.getVariableSuffixMatcher());
+ }
+
+ /**
+ * Tests get set.
+ */
+ @Test
+ public void testGetSetValueDelimiter() {
+ final StrSubstitutor sub = new StrSubstitutor();
+ assertTrue(sub.getValueDelimiterMatcher() instanceof StrMatcher.StringMatcher);
+ sub.setValueDelimiter(':');
+ assertTrue(sub.getValueDelimiterMatcher() instanceof StrMatcher.CharMatcher);
+
+ sub.setValueDelimiter("||");
+ assertTrue(sub.getValueDelimiterMatcher() instanceof StrMatcher.StringMatcher);
+ sub.setValueDelimiter((String) null);
+ assertNull(sub.getValueDelimiterMatcher());
+
+ final StrMatcher matcher = StrMatcher.commaMatcher();
+ sub.setValueDelimiterMatcher(matcher);
+ assertSame(matcher, sub.getValueDelimiterMatcher());
+ sub.setValueDelimiterMatcher((StrMatcher) null);
+ assertNull(sub.getValueDelimiterMatcher());
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Tests static.
+ */
+ @Test
+ public void testStaticReplace() {
+ final Map<String, String> map = new HashMap<>();
+ map.put("name", "commons");
+ assertEquals("Hi commons!", StrSubstitutor.replace("Hi ${name}!", map));
+ }
+
+ /**
+ * Tests static.
+ */
+ @Test
+ public void testStaticReplacePrefixSuffix() {
+ final Map<String, String> map = new HashMap<>();
+ map.put("name", "commons");
+ assertEquals("Hi commons!", StrSubstitutor.replace("Hi <name>!", map, "<", ">"));
+ }
+
+ /**
+ * Tests interpolation with system properties.
+ */
+ @Test
+ public void testStaticReplaceSystemProperties() {
+ final StrBuilder buf = new StrBuilder();
+ buf.append("Hi ").append(System.getProperty("user.name"));
+ buf.append(", you are working with ");
+ buf.append(System.getProperty("os.name"));
+ buf.append(", your home directory is ");
+ buf.append(System.getProperty("user.home")).append('.');
+ assertEquals(buf.toString(), StrSubstitutor.replaceSystemProperties("Hi ${user.name}, you are "
+ + "working with ${os.name}, your home "
+ + "directory is ${user.home}."));
+ }
+
+ /**
+ * Test for LANG-1055: StrSubstitutor.replaceSystemProperties does not work consistently
+ */
+ @Test
+ public void testLANG1055() {
+ System.setProperty("test_key", "test_value");
+
+ final String expected = StrSubstitutor.replace("test_key=${test_key}", System.getProperties());
+ final String actual = StrSubstitutor.replaceSystemProperties("test_key=${test_key}");
+ assertEquals(expected, actual);
+ }
+
+ /**
+ * Test the replace of a properties object
+ */
+ @Test
+ public void testSubstituteDefaultProperties(){
+ final String org = "${doesnotwork}";
+ System.setProperty("doesnotwork", "It works!");
+
+ // create a new Properties object with the System.getProperties as default
+ final Properties props = new Properties(System.getProperties());
+
+ assertEquals("It works!", StrSubstitutor.replace(org, props));
+ }
+
+ @Test
+ public void testSamePrefixAndSuffix() {
+ final Map<String, String> map = new HashMap<>();
+ map.put("greeting", "Hello");
+ map.put(" there ", "XXX");
+ map.put("name", "commons");
+ assertEquals("Hi commons!", StrSubstitutor.replace("Hi @name@!", map, "@", "@"));
+ assertEquals("Hello there commons!", StrSubstitutor.replace("@greeting@ there @name@!", map, "@", "@"));
+ }
+
+ @Test
+ public void testSubstitutePreserveEscape() {
+ final String org = "${not-escaped} $${escaped}";
+ final Map<String, String> map = new HashMap<>();
+ map.put("not-escaped", "value");
+
+ final StrSubstitutor sub = new StrSubstitutor(map, "${", "}", '$');
+ assertFalse(sub.isPreserveEscapes());
+ assertEquals("value ${escaped}", sub.replace(org));
+
+ sub.setPreserveEscapes(true);
+ assertTrue(sub.isPreserveEscapes());
+ assertEquals("value $${escaped}", sub.replace(org));
+ }
+
+ //-----------------------------------------------------------------------
+ private void doTestReplace(final String expectedResult, final String replaceTemplate, final boolean substring) {
+ final String expectedShortResult = expectedResult.substring(1, expectedResult.length() - 1);
+ final StrSubstitutor sub = new StrSubstitutor(values);
+
+ // replace using String
+ assertEquals(expectedResult, sub.replace(replaceTemplate));
+ if (substring) {
+ assertEquals(expectedShortResult, sub.replace(replaceTemplate, 1, replaceTemplate.length() - 2));
+ }
+
+ // replace using char[]
+ final char[] chars = replaceTemplate.toCharArray();
+ assertEquals(expectedResult, sub.replace(chars));
+ if (substring) {
+ assertEquals(expectedShortResult, sub.replace(chars, 1, chars.length - 2));
+ }
+
+ // replace using StringBuffer
+ StringBuffer buf = new StringBuffer(replaceTemplate);
+ assertEquals(expectedResult, sub.replace(buf));
+ if (substring) {
+ assertEquals(expectedShortResult, sub.replace(buf, 1, buf.length() - 2));
+ }
+
+ // replace using StringBuilder
+ StringBuilder builder = new StringBuilder(replaceTemplate);
+ assertEquals(expectedResult, sub.replace(builder));
+ if (substring) {
+ assertEquals(expectedShortResult, sub.replace(builder, 1, builder.length() - 2));
+ }
+
+ // replace using StrBuilder
+ StrBuilder bld = new StrBuilder(replaceTemplate);
+ assertEquals(expectedResult, sub.replace(bld));
+ if (substring) {
+ assertEquals(expectedShortResult, sub.replace(bld, 1, bld.length() - 2));
+ }
+
+ // replace using object
+ final MutableObject<String> obj = new MutableObject<>(replaceTemplate); // toString returns template
+ assertEquals(expectedResult, sub.replace(obj));
+
+ // replace in StringBuffer
+ buf = new StringBuffer(replaceTemplate);
+ assertTrue(sub.replaceIn(buf));
+ assertEquals(expectedResult, buf.toString());
+ if (substring) {
+ buf = new StringBuffer(replaceTemplate);
+ assertTrue(sub.replaceIn(buf, 1, buf.length() - 2));
+ assertEquals(expectedResult, buf.toString()); // expect full result as remainder is untouched
+ }
+
+ // replace in StringBuilder
+ builder = new StringBuilder(replaceTemplate);
+ assertTrue(sub.replaceIn(builder));
+ assertEquals(expectedResult, builder.toString());
+ if (substring) {
+ builder = new StringBuilder(replaceTemplate);
+ assertTrue(sub.replaceIn(builder, 1, builder.length() - 2));
+ assertEquals(expectedResult, builder.toString()); // expect full result as remainder is untouched
+ }
+
+ // replace in StrBuilder
+ bld = new StrBuilder(replaceTemplate);
+ assertTrue(sub.replaceIn(bld));
+ assertEquals(expectedResult, bld.toString());
+ if (substring) {
+ bld = new StrBuilder(replaceTemplate);
+ assertTrue(sub.replaceIn(bld, 1, bld.length() - 2));
+ assertEquals(expectedResult, bld.toString()); // expect full result as remainder is untouched
+ }
+ }
+
+ private void doTestNoReplace(final String replaceTemplate) {
+ final StrSubstitutor sub = new StrSubstitutor(values);
+
+ if (replaceTemplate == null) {
+ assertEquals(null, sub.replace((String) null));
+ assertEquals(null, sub.replace((String) null, 0, 100));
+ assertEquals(null, sub.replace((char[]) null));
+ assertEquals(null, sub.replace((char[]) null, 0, 100));
+ assertEquals(null, sub.replace((StringBuffer) null));
+ assertEquals(null, sub.replace((StringBuffer) null, 0, 100));
+ assertEquals(null, sub.replace((StrBuilder) null));
+ assertEquals(null, sub.replace((StrBuilder) null, 0, 100));
+ assertEquals(null, sub.replace((Object) null));
+ assertFalse(sub.replaceIn((StringBuffer) null));
+ assertFalse(sub.replaceIn((StringBuffer) null, 0, 100));
+ assertFalse(sub.replaceIn((StrBuilder) null));
+ assertFalse(sub.replaceIn((StrBuilder) null, 0, 100));
+ } else {
+ assertEquals(replaceTemplate, sub.replace(replaceTemplate));
+ final StrBuilder bld = new StrBuilder(replaceTemplate);
+ assertFalse(sub.replaceIn(bld));
+ assertEquals(replaceTemplate, bld.toString());
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/commons-text/blob/6f24aa45/src/test/java/org/apache/commons/text/StrTokenizerTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/text/StrTokenizerTest.java b/src/test/java/org/apache/commons/text/StrTokenizerTest.java
new file mode 100644
index 0000000..4b39f55
--- /dev/null
+++ b/src/test/java/org/apache/commons/text/StrTokenizerTest.java
@@ -0,0 +1,879 @@
+/*
+ * 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 org.junit.Test;
+
+import static org.junit.Assert.*;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import org.apache.commons.lang3.ArrayUtils;
+
+/**
+ * Unit test for {@link StrTokenizer}.
+ */
+public class StrTokenizerTest {
+
+ private static final String CSV_SIMPLE_FIXTURE = "A,b,c";
+
+ private static final String TSV_SIMPLE_FIXTURE = "A\tb\tc";
+
+ private void checkClone(final StrTokenizer tokenizer) {
+ assertFalse(StrTokenizer.getCSVInstance() == tokenizer);
+ assertFalse(StrTokenizer.getTSVInstance() == tokenizer);
+ }
+
+ // -----------------------------------------------------------------------
+ @Test
+ public void test1() {
+
+ final String input = "a;b;c;\"d;\"\"e\";f; ; ; ";
+ final StrTokenizer tok = new StrTokenizer(input);
+ tok.setDelimiterChar(';');
+ tok.setQuoteChar('"');
+ tok.setIgnoredMatcher(StrMatcher.trimMatcher());
+ tok.setIgnoreEmptyTokens(false);
+ final String tokens[] = tok.getTokenArray();
+
+ final String expected[] = new String[]{"a", "b", "c", "d;\"e", "f", "", "", "",};
+
+ assertEquals(ArrayUtils.toString(tokens), expected.length, tokens.length);
+ for (int i = 0; i < expected.length; i++) {
+ assertEquals("token[" + i + "] was '" + tokens[i] + "' but was expected to be '" + expected[i] + "'",
+ expected[i], tokens[i]);
+ }
+
+ }
+
+ @Test
+ public void test2() {
+
+ final String input = "a;b;c ;\"d;\"\"e\";f; ; ;";
+ final StrTokenizer tok = new StrTokenizer(input);
+ tok.setDelimiterChar(';');
+ tok.setQuoteChar('"');
+ tok.setIgnoredMatcher(StrMatcher.noneMatcher());
+ tok.setIgnoreEmptyTokens(false);
+ final String tokens[] = tok.getTokenArray();
+
+ final String expected[] = new String[]{"a", "b", "c ", "d;\"e", "f", " ", " ", "",};
+
+ assertEquals(ArrayUtils.toString(tokens), expected.length, tokens.length);
+ for (int i = 0; i < expected.length; i++) {
+ assertEquals("token[" + i + "] was '" + tokens[i] + "' but was expected to be '" + expected[i] + "'",
+ expected[i], tokens[i]);
+ }
+
+ }
+
+ @Test
+ public void test3() {
+
+ final String input = "a;b; c;\"d;\"\"e\";f; ; ;";
+ final StrTokenizer tok = new StrTokenizer(input);
+ tok.setDelimiterChar(';');
+ tok.setQuoteChar('"');
+ tok.setIgnoredMatcher(StrMatcher.noneMatcher());
+ tok.setIgnoreEmptyTokens(false);
+ final String tokens[] = tok.getTokenArray();
+
+ final String expected[] = new String[]{"a", "b", " c", "d;\"e", "f", " ", " ", "",};
+
+ assertEquals(ArrayUtils.toString(tokens), expected.length, tokens.length);
+ for (int i = 0; i < expected.length; i++) {
+ assertEquals("token[" + i + "] was '" + tokens[i] + "' but was expected to be '" + expected[i] + "'",
+ expected[i], tokens[i]);
+ }
+
+ }
+
+ @Test
+ public void test4() {
+
+ final String input = "a;b; c;\"d;\"\"e\";f; ; ;";
+ final StrTokenizer tok = new StrTokenizer(input);
+ tok.setDelimiterChar(';');
+ tok.setQuoteChar('"');
+ tok.setIgnoredMatcher(StrMatcher.trimMatcher());
+ tok.setIgnoreEmptyTokens(true);
+ final String tokens[] = tok.getTokenArray();
+
+ final String expected[] = new String[]{"a", "b", "c", "d;\"e", "f",};
+
+ assertEquals(ArrayUtils.toString(tokens), expected.length, tokens.length);
+ for (int i = 0; i < expected.length; i++) {
+ assertEquals("token[" + i + "] was '" + tokens[i] + "' but was expected to be '" + expected[i] + "'",
+ expected[i], tokens[i]);
+ }
+
+ }
+
+ @Test
+ public void test5() {
+
+ final String input = "a;b; c;\"d;\"\"e\";f; ; ;";
+ final StrTokenizer tok = new StrTokenizer(input);
+ tok.setDelimiterChar(';');
+ tok.setQuoteChar('"');
+ tok.setIgnoredMatcher(StrMatcher.trimMatcher());
+ tok.setIgnoreEmptyTokens(false);
+ tok.setEmptyTokenAsNull(true);
+ final String tokens[] = tok.getTokenArray();
+
+ final String expected[] = new String[]{"a", "b", "c", "d;\"e", "f", null, null, null,};
+
+ assertEquals(ArrayUtils.toString(tokens), expected.length, tokens.length);
+ for (int i = 0; i < expected.length; i++) {
+ assertEquals("token[" + i + "] was '" + tokens[i] + "' but was expected to be '" + expected[i] + "'",
+ expected[i], tokens[i]);
+ }
+
+ }
+
+ @Test
+ public void test6() {
+
+ final String input = "a;b; c;\"d;\"\"e\";f; ; ;";
+ final StrTokenizer tok = new StrTokenizer(input);
+ tok.setDelimiterChar(';');
+ tok.setQuoteChar('"');
+ tok.setIgnoredMatcher(StrMatcher.trimMatcher());
+ tok.setIgnoreEmptyTokens(false);
+ // tok.setTreatingEmptyAsNull(true);
+ final String tokens[] = tok.getTokenArray();
+
+ final String expected[] = new String[]{"a", "b", " c", "d;\"e", "f", null, null, null,};
+
+ int nextCount = 0;
+ while (tok.hasNext()) {
+ tok.next();
+ nextCount++;
+ }
+
+ int prevCount = 0;
+ while (tok.hasPrevious()) {
+ tok.previous();
+ prevCount++;
+ }
+
+ assertEquals(ArrayUtils.toString(tokens), expected.length, tokens.length);
+
+ assertTrue("could not cycle through entire token list" + " using the 'hasNext' and 'next' methods",
+ nextCount == expected.length);
+
+ assertTrue("could not cycle through entire token list" + " using the 'hasPrevious' and 'previous' methods",
+ prevCount == expected.length);
+
+ }
+
+ @Test
+ public void test7() {
+
+ final String input = "a b c \"d e\" f ";
+ final StrTokenizer tok = new StrTokenizer(input);
+ tok.setDelimiterMatcher(StrMatcher.spaceMatcher());
+ tok.setQuoteMatcher(StrMatcher.doubleQuoteMatcher());
+ tok.setIgnoredMatcher(StrMatcher.noneMatcher());
+ tok.setIgnoreEmptyTokens(false);
+ final String tokens[] = tok.getTokenArray();
+
+ final String expected[] = new String[]{"a", "", "", "b", "c", "d e", "f", "",};
+
+ assertEquals(ArrayUtils.toString(tokens), expected.length, tokens.length);
+ for (int i = 0; i < expected.length; i++) {
+ assertEquals("token[" + i + "] was '" + tokens[i] + "' but was expected to be '" + expected[i] + "'",
+ expected[i], tokens[i]);
+ }
+
+ }
+
+ @Test
+ public void test8() {
+
+ final String input = "a b c \"d e\" f ";
+ final StrTokenizer tok = new StrTokenizer(input);
+ tok.setDelimiterMatcher(StrMatcher.spaceMatcher());
+ tok.setQuoteMatcher(StrMatcher.doubleQuoteMatcher());
+ tok.setIgnoredMatcher(StrMatcher.noneMatcher());
+ tok.setIgnoreEmptyTokens(true);
+ final String tokens[] = tok.getTokenArray();
+
+ final String expected[] = new String[]{"a", "b", "c", "d e", "f",};
+
+ assertEquals(ArrayUtils.toString(tokens), expected.length, tokens.length);
+ for (int i = 0; i < expected.length; i++) {
+ assertEquals("token[" + i + "] was '" + tokens[i] + "' but was expected to be '" + expected[i] + "'",
+ expected[i], tokens[i]);
+ }
+
+ }
+
+ @Test
+ public void testBasic1() {
+ final String input = "a b c";
+ final StrTokenizer tok = new StrTokenizer(input);
+ assertEquals("a", tok.next());
+ assertEquals("b", tok.next());
+ assertEquals("c", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasic2() {
+ final String input = "a \nb\fc";
+ final StrTokenizer tok = new StrTokenizer(input);
+ assertEquals("a", tok.next());
+ assertEquals("b", tok.next());
+ assertEquals("c", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasic3() {
+ final String input = "a \nb\u0001\fc";
+ final StrTokenizer tok = new StrTokenizer(input);
+ assertEquals("a", tok.next());
+ assertEquals("b\u0001", tok.next());
+ assertEquals("c", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasic4() {
+ final String input = "a \"b\" c";
+ final StrTokenizer tok = new StrTokenizer(input);
+ assertEquals("a", tok.next());
+ assertEquals("\"b\"", tok.next());
+ assertEquals("c", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasic5() {
+ final String input = "a:b':c";
+ final StrTokenizer tok = new StrTokenizer(input, ':', '\'');
+ assertEquals("a", tok.next());
+ assertEquals("b'", tok.next());
+ assertEquals("c", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicDelim1() {
+ final String input = "a:b:c";
+ final StrTokenizer tok = new StrTokenizer(input, ':');
+ assertEquals("a", tok.next());
+ assertEquals("b", tok.next());
+ assertEquals("c", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicDelim2() {
+ final String input = "a:b:c";
+ final StrTokenizer tok = new StrTokenizer(input, ',');
+ assertEquals("a:b:c", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicEmpty1() {
+ final String input = "a b c";
+ final StrTokenizer tok = new StrTokenizer(input);
+ tok.setIgnoreEmptyTokens(false);
+ assertEquals("a", tok.next());
+ assertEquals("", tok.next());
+ assertEquals("b", tok.next());
+ assertEquals("c", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicEmpty2() {
+ final String input = "a b c";
+ final StrTokenizer tok = new StrTokenizer(input);
+ tok.setIgnoreEmptyTokens(false);
+ tok.setEmptyTokenAsNull(true);
+ assertEquals("a", tok.next());
+ assertEquals(null, tok.next());
+ assertEquals("b", tok.next());
+ assertEquals("c", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicQuoted1() {
+ final String input = "a 'b' c";
+ final StrTokenizer tok = new StrTokenizer(input, ' ', '\'');
+ assertEquals("a", tok.next());
+ assertEquals("b", tok.next());
+ assertEquals("c", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicQuoted2() {
+ final String input = "a:'b':";
+ final StrTokenizer tok = new StrTokenizer(input, ':', '\'');
+ tok.setIgnoreEmptyTokens(false);
+ tok.setEmptyTokenAsNull(true);
+ assertEquals("a", tok.next());
+ assertEquals("b", tok.next());
+ assertEquals(null, tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicQuoted3() {
+ final String input = "a:'b''c'";
+ final StrTokenizer tok = new StrTokenizer(input, ':', '\'');
+ tok.setIgnoreEmptyTokens(false);
+ tok.setEmptyTokenAsNull(true);
+ assertEquals("a", tok.next());
+ assertEquals("b'c", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicQuoted4() {
+ final String input = "a: 'b' 'c' :d";
+ final StrTokenizer tok = new StrTokenizer(input, ':', '\'');
+ tok.setTrimmerMatcher(StrMatcher.trimMatcher());
+ tok.setIgnoreEmptyTokens(false);
+ tok.setEmptyTokenAsNull(true);
+ assertEquals("a", tok.next());
+ assertEquals("b c", tok.next());
+ assertEquals("d", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicQuoted5() {
+ final String input = "a: 'b'x'c' :d";
+ final StrTokenizer tok = new StrTokenizer(input, ':', '\'');
+ tok.setTrimmerMatcher(StrMatcher.trimMatcher());
+ tok.setIgnoreEmptyTokens(false);
+ tok.setEmptyTokenAsNull(true);
+ assertEquals("a", tok.next());
+ assertEquals("bxc", tok.next());
+ assertEquals("d", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicQuoted6() {
+ final String input = "a:'b'\"c':d";
+ final StrTokenizer tok = new StrTokenizer(input, ':');
+ tok.setQuoteMatcher(StrMatcher.quoteMatcher());
+ assertEquals("a", tok.next());
+ assertEquals("b\"c:d", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicQuoted7() {
+ final String input = "a:\"There's a reason here\":b";
+ final StrTokenizer tok = new StrTokenizer(input, ':');
+ tok.setQuoteMatcher(StrMatcher.quoteMatcher());
+ assertEquals("a", tok.next());
+ assertEquals("There's a reason here", tok.next());
+ assertEquals("b", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicQuotedTrimmed1() {
+ final String input = "a: 'b' :";
+ final StrTokenizer tok = new StrTokenizer(input, ':', '\'');
+ tok.setTrimmerMatcher(StrMatcher.trimMatcher());
+ tok.setIgnoreEmptyTokens(false);
+ tok.setEmptyTokenAsNull(true);
+ assertEquals("a", tok.next());
+ assertEquals("b", tok.next());
+ assertEquals(null, tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicTrimmed1() {
+ final String input = "a: b : ";
+ final StrTokenizer tok = new StrTokenizer(input, ':');
+ tok.setTrimmerMatcher(StrMatcher.trimMatcher());
+ tok.setIgnoreEmptyTokens(false);
+ tok.setEmptyTokenAsNull(true);
+ assertEquals("a", tok.next());
+ assertEquals("b", tok.next());
+ assertEquals(null, tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicTrimmed2() {
+ final String input = "a: b :";
+ final StrTokenizer tok = new StrTokenizer(input, ':');
+ tok.setTrimmerMatcher(StrMatcher.stringMatcher(" "));
+ tok.setIgnoreEmptyTokens(false);
+ tok.setEmptyTokenAsNull(true);
+ assertEquals("a", tok.next());
+ assertEquals("b", tok.next());
+ assertEquals(null, tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicIgnoreTrimmed1() {
+ final String input = "a: bIGNOREc : ";
+ final StrTokenizer tok = new StrTokenizer(input, ':');
+ tok.setIgnoredMatcher(StrMatcher.stringMatcher("IGNORE"));
+ tok.setTrimmerMatcher(StrMatcher.trimMatcher());
+ tok.setIgnoreEmptyTokens(false);
+ tok.setEmptyTokenAsNull(true);
+ assertEquals("a", tok.next());
+ assertEquals("bc", tok.next());
+ assertEquals(null, tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicIgnoreTrimmed2() {
+ final String input = "IGNOREaIGNORE: IGNORE bIGNOREc IGNORE : IGNORE ";
+ final StrTokenizer tok = new StrTokenizer(input, ':');
+ tok.setIgnoredMatcher(StrMatcher.stringMatcher("IGNORE"));
+ tok.setTrimmerMatcher(StrMatcher.trimMatcher());
+ tok.setIgnoreEmptyTokens(false);
+ tok.setEmptyTokenAsNull(true);
+ assertEquals("a", tok.next());
+ assertEquals("bc", tok.next());
+ assertEquals(null, tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicIgnoreTrimmed3() {
+ final String input = "IGNOREaIGNORE: IGNORE bIGNOREc IGNORE : IGNORE ";
+ final StrTokenizer tok = new StrTokenizer(input, ':');
+ tok.setIgnoredMatcher(StrMatcher.stringMatcher("IGNORE"));
+ tok.setIgnoreEmptyTokens(false);
+ tok.setEmptyTokenAsNull(true);
+ assertEquals("a", tok.next());
+ assertEquals(" bc ", tok.next());
+ assertEquals(" ", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ @Test
+ public void testBasicIgnoreTrimmed4() {
+ final String input = "IGNOREaIGNORE: IGNORE 'bIGNOREc'IGNORE'd' IGNORE : IGNORE ";
+ final StrTokenizer tok = new StrTokenizer(input, ':', '\'');
+ tok.setIgnoredMatcher(StrMatcher.stringMatcher("IGNORE"));
+ tok.setTrimmerMatcher(StrMatcher.trimMatcher());
+ tok.setIgnoreEmptyTokens(false);
+ tok.setEmptyTokenAsNull(true);
+ assertEquals("a", tok.next());
+ assertEquals("bIGNOREcd", tok.next());
+ assertEquals(null, tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testListArray() {
+ final String input = "a b c";
+ final StrTokenizer tok = new StrTokenizer(input);
+ final String[] array = tok.getTokenArray();
+ final List<?> list = tok.getTokenList();
+
+ assertEquals(Arrays.asList(array), list);
+ assertEquals(3, list.size());
+ }
+
+ //-----------------------------------------------------------------------
+ private void testCSV(final String data) {
+ this.testXSVAbc(StrTokenizer.getCSVInstance(data));
+ this.testXSVAbc(StrTokenizer.getCSVInstance(data.toCharArray()));
+ }
+
+ @Test
+ public void testCSVEmpty() {
+ this.testEmpty(StrTokenizer.getCSVInstance());
+ this.testEmpty(StrTokenizer.getCSVInstance(""));
+ }
+
+ @Test
+ public void testCSVSimple() {
+ this.testCSV(CSV_SIMPLE_FIXTURE);
+ }
+
+ @Test
+ public void testCSVSimpleNeedsTrim() {
+ this.testCSV(" " + CSV_SIMPLE_FIXTURE);
+ this.testCSV(" \n\t " + CSV_SIMPLE_FIXTURE);
+ this.testCSV(" \n " + CSV_SIMPLE_FIXTURE + "\n\n\r");
+ }
+
+ void testEmpty(final StrTokenizer tokenizer) {
+ this.checkClone(tokenizer);
+ assertFalse(tokenizer.hasNext());
+ assertFalse(tokenizer.hasPrevious());
+ assertEquals(null, tokenizer.nextToken());
+ assertEquals(0, tokenizer.size());
+ try {
+ tokenizer.next();
+ fail();
+ } catch (final NoSuchElementException ex) {}
+ }
+
+ @Test
+ public void testGetContent() {
+ final String input = "a b c \"d e\" f ";
+ StrTokenizer tok = new StrTokenizer(input);
+ assertEquals(input, tok.getContent());
+
+ tok = new StrTokenizer(input.toCharArray());
+ assertEquals(input, tok.getContent());
+
+ tok = new StrTokenizer();
+ assertEquals(null, tok.getContent());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testChaining() {
+ final StrTokenizer tok = new StrTokenizer();
+ assertEquals(tok, tok.reset());
+ assertEquals(tok, tok.reset(""));
+ assertEquals(tok, tok.reset(new char[0]));
+ assertEquals(tok, tok.setDelimiterChar(' '));
+ assertEquals(tok, tok.setDelimiterString(" "));
+ assertEquals(tok, tok.setDelimiterMatcher(null));
+ assertEquals(tok, tok.setQuoteChar(' '));
+ assertEquals(tok, tok.setQuoteMatcher(null));
+ assertEquals(tok, tok.setIgnoredChar(' '));
+ assertEquals(tok, tok.setIgnoredMatcher(null));
+ assertEquals(tok, tok.setTrimmerMatcher(null));
+ assertEquals(tok, tok.setEmptyTokenAsNull(false));
+ assertEquals(tok, tok.setIgnoreEmptyTokens(false));
+ }
+
+ /**
+ * Tests that the {@link StrTokenizer#clone()} clone method catches {@link CloneNotSupportedException} and returns
+ * <code>null</code>.
+ */
+ @Test
+ public void testCloneNotSupportedException() {
+ final Object notCloned = new StrTokenizer() {
+ @Override
+ Object cloneReset() throws CloneNotSupportedException {
+ throw new CloneNotSupportedException("test");
+ }
+ }.clone();
+ assertNull(notCloned);
+ }
+
+ @Test
+ public void testCloneNull() {
+ final StrTokenizer tokenizer = new StrTokenizer((char[]) null);
+ // Start sanity check
+ assertEquals(null, tokenizer.nextToken());
+ tokenizer.reset();
+ assertEquals(null, tokenizer.nextToken());
+ // End sanity check
+ final StrTokenizer clonedTokenizer = (StrTokenizer) tokenizer.clone();
+ tokenizer.reset();
+ assertEquals(null, tokenizer.nextToken());
+ assertEquals(null, clonedTokenizer.nextToken());
+ }
+
+ @Test
+ public void testCloneReset() {
+ final char[] input = new char[]{'a'};
+ final StrTokenizer tokenizer = new StrTokenizer(input);
+ // Start sanity check
+ assertEquals("a", tokenizer.nextToken());
+ tokenizer.reset(input);
+ assertEquals("a", tokenizer.nextToken());
+ // End sanity check
+ final StrTokenizer clonedTokenizer = (StrTokenizer) tokenizer.clone();
+ input[0] = 'b';
+ tokenizer.reset(input);
+ assertEquals("b", tokenizer.nextToken());
+ assertEquals("a", clonedTokenizer.nextToken());
+ }
+
+ // -----------------------------------------------------------------------
+ @Test
+ public void testConstructor_String() {
+ StrTokenizer tok = new StrTokenizer("a b");
+ assertEquals("a", tok.next());
+ assertEquals("b", tok.next());
+ assertFalse(tok.hasNext());
+
+ tok = new StrTokenizer("");
+ assertFalse(tok.hasNext());
+
+ tok = new StrTokenizer((String) null);
+ assertFalse(tok.hasNext());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testConstructor_String_char() {
+ StrTokenizer tok = new StrTokenizer("a b", ' ');
+ assertEquals(1, tok.getDelimiterMatcher().isMatch(" ".toCharArray(), 0, 0, 1));
+ assertEquals("a", tok.next());
+ assertEquals("b", tok.next());
+ assertFalse(tok.hasNext());
+
+ tok = new StrTokenizer("", ' ');
+ assertFalse(tok.hasNext());
+
+ tok = new StrTokenizer((String) null, ' ');
+ assertFalse(tok.hasNext());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testConstructor_String_char_char() {
+ StrTokenizer tok = new StrTokenizer("a b", ' ', '"');
+ assertEquals(1, tok.getDelimiterMatcher().isMatch(" ".toCharArray(), 0, 0, 1));
+ assertEquals(1, tok.getQuoteMatcher().isMatch("\"".toCharArray(), 0, 0, 1));
+ assertEquals("a", tok.next());
+ assertEquals("b", tok.next());
+ assertFalse(tok.hasNext());
+
+ tok = new StrTokenizer("", ' ', '"');
+ assertFalse(tok.hasNext());
+
+ tok = new StrTokenizer((String) null, ' ', '"');
+ assertFalse(tok.hasNext());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testConstructor_charArray() {
+ StrTokenizer tok = new StrTokenizer("a b".toCharArray());
+ assertEquals("a", tok.next());
+ assertEquals("b", tok.next());
+ assertFalse(tok.hasNext());
+
+ tok = new StrTokenizer(new char[0]);
+ assertFalse(tok.hasNext());
+
+ tok = new StrTokenizer((char[]) null);
+ assertFalse(tok.hasNext());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testConstructor_charArray_char() {
+ StrTokenizer tok = new StrTokenizer("a b".toCharArray(), ' ');
+ assertEquals(1, tok.getDelimiterMatcher().isMatch(" ".toCharArray(), 0, 0, 1));
+ assertEquals("a", tok.next());
+ assertEquals("b", tok.next());
+ assertFalse(tok.hasNext());
+
+ tok = new StrTokenizer(new char[0], ' ');
+ assertFalse(tok.hasNext());
+
+ tok = new StrTokenizer((char[]) null, ' ');
+ assertFalse(tok.hasNext());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testConstructor_charArray_char_char() {
+ StrTokenizer tok = new StrTokenizer("a b".toCharArray(), ' ', '"');
+ assertEquals(1, tok.getDelimiterMatcher().isMatch(" ".toCharArray(), 0, 0, 1));
+ assertEquals(1, tok.getQuoteMatcher().isMatch("\"".toCharArray(), 0, 0, 1));
+ assertEquals("a", tok.next());
+ assertEquals("b", tok.next());
+ assertFalse(tok.hasNext());
+
+ tok = new StrTokenizer(new char[0], ' ', '"');
+ assertFalse(tok.hasNext());
+
+ tok = new StrTokenizer((char[]) null, ' ', '"');
+ assertFalse(tok.hasNext());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testReset() {
+ final StrTokenizer tok = new StrTokenizer("a b c");
+ assertEquals("a", tok.next());
+ assertEquals("b", tok.next());
+ assertEquals("c", tok.next());
+ assertFalse(tok.hasNext());
+
+ tok.reset();
+ assertEquals("a", tok.next());
+ assertEquals("b", tok.next());
+ assertEquals("c", tok.next());
+ assertFalse(tok.hasNext());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testReset_String() {
+ final StrTokenizer tok = new StrTokenizer("x x x");
+ tok.reset("d e");
+ assertEquals("d", tok.next());
+ assertEquals("e", tok.next());
+ assertFalse(tok.hasNext());
+
+ tok.reset((String) null);
+ assertFalse(tok.hasNext());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testReset_charArray() {
+ final StrTokenizer tok = new StrTokenizer("x x x");
+
+ final char[] array = new char[] {'a', 'b', 'c'};
+ tok.reset(array);
+ assertEquals("abc", tok.next());
+ assertFalse(tok.hasNext());
+
+ tok.reset((char[]) null);
+ assertFalse(tok.hasNext());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testTSV() {
+ this.testXSVAbc(StrTokenizer.getTSVInstance(TSV_SIMPLE_FIXTURE));
+ this.testXSVAbc(StrTokenizer.getTSVInstance(TSV_SIMPLE_FIXTURE.toCharArray()));
+ }
+
+ @Test
+ public void testTSVEmpty() {
+ this.testEmpty(StrTokenizer.getTSVInstance());
+ this.testEmpty(StrTokenizer.getTSVInstance(""));
+ }
+
+ void testXSVAbc(final StrTokenizer tokenizer) {
+ this.checkClone(tokenizer);
+ assertEquals(-1, tokenizer.previousIndex());
+ assertEquals(0, tokenizer.nextIndex());
+ assertEquals(null, tokenizer.previousToken());
+ assertEquals("A", tokenizer.nextToken());
+ assertEquals(1, tokenizer.nextIndex());
+ assertEquals("b", tokenizer.nextToken());
+ assertEquals(2, tokenizer.nextIndex());
+ assertEquals("c", tokenizer.nextToken());
+ assertEquals(3, tokenizer.nextIndex());
+ assertEquals(null, tokenizer.nextToken());
+ assertEquals(3, tokenizer.nextIndex());
+ assertEquals("c", tokenizer.previousToken());
+ assertEquals(2, tokenizer.nextIndex());
+ assertEquals("b", tokenizer.previousToken());
+ assertEquals(1, tokenizer.nextIndex());
+ assertEquals("A", tokenizer.previousToken());
+ assertEquals(0, tokenizer.nextIndex());
+ assertEquals(null, tokenizer.previousToken());
+ assertEquals(0, tokenizer.nextIndex());
+ assertEquals(-1, tokenizer.previousIndex());
+ assertEquals(3, tokenizer.size());
+ }
+
+ @Test
+ public void testIteration() {
+ final StrTokenizer tkn = new StrTokenizer("a b c");
+ assertFalse(tkn.hasPrevious());
+ try {
+ tkn.previous();
+ fail();
+ } catch (final NoSuchElementException ex) {}
+ assertTrue(tkn.hasNext());
+
+ assertEquals("a", tkn.next());
+ try {
+ tkn.remove();
+ fail();
+ } catch (final UnsupportedOperationException ex) {}
+ try {
+ tkn.set("x");
+ fail();
+ } catch (final UnsupportedOperationException ex) {}
+ try {
+ tkn.add("y");
+ fail();
+ } catch (final UnsupportedOperationException ex) {}
+ assertTrue(tkn.hasPrevious());
+ assertTrue(tkn.hasNext());
+
+ assertEquals("b", tkn.next());
+ assertTrue(tkn.hasPrevious());
+ assertTrue(tkn.hasNext());
+
+ assertEquals("c", tkn.next());
+ assertTrue(tkn.hasPrevious());
+ assertFalse(tkn.hasNext());
+
+ try {
+ tkn.next();
+ fail();
+ } catch (final NoSuchElementException ex) {}
+ assertTrue(tkn.hasPrevious());
+ assertFalse(tkn.hasNext());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testTokenizeSubclassInputChange() {
+ final StrTokenizer tkn = new StrTokenizer("a b c d e") {
+ @Override
+ protected List<String> tokenize(final char[] chars, final int offset, final int count) {
+ return super.tokenize("w x y z".toCharArray(), 2, 5);
+ }
+ };
+ assertEquals("x", tkn.next());
+ assertEquals("y", tkn.next());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testTokenizeSubclassOutputChange() {
+ final StrTokenizer tkn = new StrTokenizer("a b c") {
+ @Override
+ protected List<String> tokenize(final char[] chars, final int offset, final int count) {
+ final List<String> list = super.tokenize(chars, offset, count);
+ Collections.reverse(list);
+ return list;
+ }
+ };
+ assertEquals("c", tkn.next());
+ assertEquals("b", tkn.next());
+ assertEquals("a", tkn.next());
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testToString() {
+ final StrTokenizer tkn = new StrTokenizer("a b c d e");
+ assertEquals("StrTokenizer[not tokenized yet]", tkn.toString());
+ tkn.next();
+ assertEquals("StrTokenizer[a, b, c, d, e]", tkn.toString());
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/commons-text/blob/6f24aa45/src/test/java/org/apache/commons/text/WordUtilsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/text/WordUtilsTest.java b/src/test/java/org/apache/commons/text/WordUtilsTest.java
new file mode 100644
index 0000000..6b57072
--- /dev/null
+++ b/src/test/java/org/apache/commons/text/WordUtilsTest.java
@@ -0,0 +1,421 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+
+import org.apache.commons.lang3.SystemUtils;
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link WordUtils} class.
+ */
+public class WordUtilsTest {
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testConstructor() {
+ assertNotNull(new WordUtils());
+ final Constructor<?>[] cons = WordUtils.class.getDeclaredConstructors();
+ assertEquals(1, cons.length);
+ assertTrue(Modifier.isPublic(cons[0].getModifiers()));
+ assertTrue(Modifier.isPublic(WordUtils.class.getModifiers()));
+ assertFalse(Modifier.isFinal(WordUtils.class.getModifiers()));
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testWrap_StringInt() {
+ assertEquals(null, WordUtils.wrap(null, 20));
+ assertEquals(null, WordUtils.wrap(null, -1));
+
+ assertEquals("", WordUtils.wrap("", 20));
+ assertEquals("", WordUtils.wrap("", -1));
+
+ // normal
+ final String systemNewLine = SystemUtils.LINE_SEPARATOR;
+ String input = "Here is one line of text that is going to be wrapped after 20 columns.";
+ String expected = "Here is one line of" + systemNewLine + "text that is going"
+ + systemNewLine + "to be wrapped after" + systemNewLine + "20 columns.";
+ assertEquals(expected, WordUtils.wrap(input, 20));
+
+ // long word at end
+ input = "Click here to jump to the commons website - http://commons.apache.org";
+ expected = "Click here to jump" + systemNewLine + "to the commons" + systemNewLine
+ + "website -" + systemNewLine + "http://commons.apache.org";
+ assertEquals(expected, WordUtils.wrap(input, 20));
+
+ // long word in middle
+ input = "Click here, http://commons.apache.org, to jump to the commons website";
+ expected = "Click here," + systemNewLine + "http://commons.apache.org," + systemNewLine
+ + "to jump to the" + systemNewLine + "commons website";
+ assertEquals(expected, WordUtils.wrap(input, 20));
+
+ // leading spaces on a new line are stripped
+ // trailing spaces are not stripped
+ input = "word1 word2 word3";
+ expected = "word1 " + systemNewLine + "word2 " + systemNewLine + "word3";
+ assertEquals(expected, WordUtils.wrap(input, 7));
+ }
+
+ @Test
+ public void testWrap_StringIntStringBoolean() {
+ assertEquals(null, WordUtils.wrap(null, 20, "\n", false));
+ assertEquals(null, WordUtils.wrap(null, 20, "\n", true));
+ assertEquals(null, WordUtils.wrap(null, 20, null, true));
+ assertEquals(null, WordUtils.wrap(null, 20, null, false));
+ assertEquals(null, WordUtils.wrap(null, -1, null, true));
+ assertEquals(null, WordUtils.wrap(null, -1, null, false));
+
+ assertEquals("", WordUtils.wrap("", 20, "\n", false));
+ assertEquals("", WordUtils.wrap("", 20, "\n", true));
+ assertEquals("", WordUtils.wrap("", 20, null, false));
+ assertEquals("", WordUtils.wrap("", 20, null, true));
+ assertEquals("", WordUtils.wrap("", -1, null, false));
+ assertEquals("", WordUtils.wrap("", -1, null, true));
+
+ // normal
+ String input = "Here is one line of text that is going to be wrapped after 20 columns.";
+ String expected = "Here is one line of\ntext that is going\nto be wrapped after\n20 columns.";
+ assertEquals(expected, WordUtils.wrap(input, 20, "\n", false));
+ assertEquals(expected, WordUtils.wrap(input, 20, "\n", true));
+
+ // unusual newline char
+ input = "Here is one line of text that is going to be wrapped after 20 columns.";
+ expected = "Here is one line of<br />text that is going<br />to be wrapped after<br />20 columns.";
+ assertEquals(expected, WordUtils.wrap(input, 20, "<br />", false));
+ assertEquals(expected, WordUtils.wrap(input, 20, "<br />", true));
+
+ // short line length
+ input = "Here is one line";
+ expected = "Here\nis one\nline";
+ assertEquals(expected, WordUtils.wrap(input, 6, "\n", false));
+ expected = "Here\nis\none\nline";
+ assertEquals(expected, WordUtils.wrap(input, 2, "\n", false));
+ assertEquals(expected, WordUtils.wrap(input, -1, "\n", false));
+
+ // system newline char
+ final String systemNewLine = SystemUtils.LINE_SEPARATOR;
+ input = "Here is one line of text that is going to be wrapped after 20 columns.";
+ expected = "Here is one line of" + systemNewLine + "text that is going" + systemNewLine
+ + "to be wrapped after" + systemNewLine + "20 columns.";
+ assertEquals(expected, WordUtils.wrap(input, 20, null, false));
+ assertEquals(expected, WordUtils.wrap(input, 20, null, true));
+
+ // with extra spaces
+ input = " Here: is one line of text that is going to be wrapped after 20 columns.";
+ expected = "Here: is one line\nof text that is \ngoing to be \nwrapped after 20 \ncolumns.";
+ assertEquals(expected, WordUtils.wrap(input, 20, "\n", false));
+ assertEquals(expected, WordUtils.wrap(input, 20, "\n", true));
+
+ // with tab
+ input = "Here is\tone line of text that is going to be wrapped after 20 columns.";
+ expected = "Here is\tone line of\ntext that is going\nto be wrapped after\n20 columns.";
+ assertEquals(expected, WordUtils.wrap(input, 20, "\n", false));
+ assertEquals(expected, WordUtils.wrap(input, 20, "\n", true));
+
+ // with tab at wrapColumn
+ input = "Here is one line of\ttext that is going to be wrapped after 20 columns.";
+ expected = "Here is one line\nof\ttext that is\ngoing to be wrapped\nafter 20 columns.";
+ assertEquals(expected, WordUtils.wrap(input, 20, "\n", false));
+ assertEquals(expected, WordUtils.wrap(input, 20, "\n", true));
+
+ // difference because of long word
+ input = "Click here to jump to the commons website - http://commons.apache.org";
+ expected = "Click here to jump\nto the commons\nwebsite -\nhttp://commons.apache.org";
+ assertEquals(expected, WordUtils.wrap(input, 20, "\n", false));
+ expected = "Click here to jump\nto the commons\nwebsite -\nhttp://commons.apach\ne.org";
+ assertEquals(expected, WordUtils.wrap(input, 20, "\n", true));
+
+ // difference because of long word in middle
+ input = "Click here, http://commons.apache.org, to jump to the commons website";
+ expected = "Click here,\nhttp://commons.apache.org,\nto jump to the\ncommons website";
+ assertEquals(expected, WordUtils.wrap(input, 20, "\n", false));
+ expected = "Click here,\nhttp://commons.apach\ne.org, to jump to\nthe commons website";
+ assertEquals(expected, WordUtils.wrap(input, 20, "\n", true));
+ }
+
+ @Test
+ public void testWrap_StringIntStringBooleanString() {
+
+ //no changes test
+ String input = "flammable/inflammable";
+ String expected = "flammable/inflammable";
+ assertEquals(expected, WordUtils.wrap(input, 30, "\n", false, "/"));
+
+ // wrap on / and small width
+ expected = "flammable\ninflammable";
+ assertEquals(expected, WordUtils.wrap(input, 2, "\n", false, "/"));
+
+ // wrap long words on / 1
+ expected = "flammable\ninflammab\nle";
+ assertEquals(expected, WordUtils.wrap(input, 9, "\n", true, "/"));
+
+ // wrap long words on / 2
+ expected = "flammable\ninflammable";
+ assertEquals(expected, WordUtils.wrap(input, 15, "\n", true, "/"));
+
+ // wrap long words on / 3
+ input = "flammableinflammable";
+ expected = "flammableinflam\nmable";
+ assertEquals(expected, WordUtils.wrap(input, 15, "\n", true, "/"));
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testCapitalize_String() {
+ assertEquals(null, WordUtils.capitalize(null));
+ assertEquals("", WordUtils.capitalize(""));
+ assertEquals(" ", WordUtils.capitalize(" "));
+
+ assertEquals("I", WordUtils.capitalize("I") );
+ assertEquals("I", WordUtils.capitalize("i") );
+ assertEquals("I Am Here 123", WordUtils.capitalize("i am here 123") );
+ assertEquals("I Am Here 123", WordUtils.capitalize("I Am Here 123") );
+ assertEquals("I Am HERE 123", WordUtils.capitalize("i am HERE 123") );
+ assertEquals("I AM HERE 123", WordUtils.capitalize("I AM HERE 123") );
+ }
+
+ @Test
+ public void testCapitalizeWithDelimiters_String() {
+ assertEquals(null, WordUtils.capitalize(null, null));
+ assertEquals("", WordUtils.capitalize("", new char[0]));
+ assertEquals(" ", WordUtils.capitalize(" ", new char[0]));
+
+ char[] chars = new char[] { '-', '+', ' ', '@' };
+ assertEquals("I", WordUtils.capitalize("I", chars) );
+ assertEquals("I", WordUtils.capitalize("i", chars) );
+ assertEquals("I-Am Here+123", WordUtils.capitalize("i-am here+123", chars) );
+ assertEquals("I Am+Here-123", WordUtils.capitalize("I Am+Here-123", chars) );
+ assertEquals("I+Am-HERE 123", WordUtils.capitalize("i+am-HERE 123", chars) );
+ assertEquals("I-AM HERE+123", WordUtils.capitalize("I-AM HERE+123", chars) );
+ chars = new char[] {'.'};
+ assertEquals("I aM.Fine", WordUtils.capitalize("i aM.fine", chars) );
+ assertEquals("I Am.fine", WordUtils.capitalize("i am.fine", null) );
+ }
+
+ @Test
+ public void testCapitalizeFully_String() {
+ assertEquals(null, WordUtils.capitalizeFully(null));
+ assertEquals("", WordUtils.capitalizeFully(""));
+ assertEquals(" ", WordUtils.capitalizeFully(" "));
+
+ assertEquals("I", WordUtils.capitalizeFully("I") );
+ assertEquals("I", WordUtils.capitalizeFully("i") );
+ assertEquals("I Am Here 123", WordUtils.capitalizeFully("i am here 123") );
+ assertEquals("I Am Here 123", WordUtils.capitalizeFully("I Am Here 123") );
+ assertEquals("I Am Here 123", WordUtils.capitalizeFully("i am HERE 123") );
+ assertEquals("I Am Here 123", WordUtils.capitalizeFully("I AM HERE 123") );
+ }
+
+ @Test
+ public void testCapitalizeFullyWithDelimiters_String() {
+ assertEquals(null, WordUtils.capitalizeFully(null, null));
+ assertEquals("", WordUtils.capitalizeFully("", new char[0]));
+ assertEquals(" ", WordUtils.capitalizeFully(" ", new char[0]));
+
+ char[] chars = new char[] { '-', '+', ' ', '@' };
+ assertEquals("I", WordUtils.capitalizeFully("I", chars) );
+ assertEquals("I", WordUtils.capitalizeFully("i", chars) );
+ assertEquals("I-Am Here+123", WordUtils.capitalizeFully("i-am here+123", chars) );
+ assertEquals("I Am+Here-123", WordUtils.capitalizeFully("I Am+Here-123", chars) );
+ assertEquals("I+Am-Here 123", WordUtils.capitalizeFully("i+am-HERE 123", chars) );
+ assertEquals("I-Am Here+123", WordUtils.capitalizeFully("I-AM HERE+123", chars) );
+ chars = new char[] {'.'};
+ assertEquals("I am.Fine", WordUtils.capitalizeFully("i aM.fine", chars) );
+ assertEquals("I Am.fine", WordUtils.capitalizeFully("i am.fine", null) );
+ }
+
+ @Test
+ public void testContainsAllWords_StringString() {
+ assertFalse(WordUtils.containsAllWords(null, (String) null));
+ assertFalse(WordUtils.containsAllWords(null, ""));
+ assertFalse(WordUtils.containsAllWords(null, "ab"));
+
+ assertFalse(WordUtils.containsAllWords("", (String) null));
+ assertFalse(WordUtils.containsAllWords("", ""));
+ assertFalse(WordUtils.containsAllWords("", "ab"));
+
+ assertFalse(WordUtils.containsAllWords("foo", (String) null));
+ assertFalse(WordUtils.containsAllWords("bar", ""));
+ assertFalse(WordUtils.containsAllWords("zzabyycdxx", "by"));
+ assertTrue(WordUtils.containsAllWords("lorem ipsum dolor sit amet", "ipsum", "lorem", "dolor"));
+ assertFalse(WordUtils.containsAllWords("lorem ipsum dolor sit amet", "ipsum", null, "lorem", "dolor"));
+ assertFalse(WordUtils.containsAllWords("lorem ipsum null dolor sit amet", "ipsum", null, "lorem", "dolor"));
+ assertFalse(WordUtils.containsAllWords("ab", "b"));
+ assertFalse(WordUtils.containsAllWords("ab", "z"));
+ }
+
+ @Test
+ public void testUncapitalize_String() {
+ assertEquals(null, WordUtils.uncapitalize(null));
+ assertEquals("", WordUtils.uncapitalize(""));
+ assertEquals(" ", WordUtils.uncapitalize(" "));
+
+ assertEquals("i", WordUtils.uncapitalize("I") );
+ assertEquals("i", WordUtils.uncapitalize("i") );
+ assertEquals("i am here 123", WordUtils.uncapitalize("i am here 123") );
+ assertEquals("i am here 123", WordUtils.uncapitalize("I Am Here 123") );
+ assertEquals("i am hERE 123", WordUtils.uncapitalize("i am HERE 123") );
+ assertEquals("i aM hERE 123", WordUtils.uncapitalize("I AM HERE 123") );
+ }
+
+ @Test
+ public void testUncapitalizeWithDelimiters_String() {
+ assertEquals(null, WordUtils.uncapitalize(null, null));
+ assertEquals("", WordUtils.uncapitalize("", new char[0]));
+ assertEquals(" ", WordUtils.uncapitalize(" ", new char[0]));
+
+ char[] chars = new char[] { '-', '+', ' ', '@' };
+ assertEquals("i", WordUtils.uncapitalize("I", chars) );
+ assertEquals("i", WordUtils.uncapitalize("i", chars) );
+ assertEquals("i am-here+123", WordUtils.uncapitalize("i am-here+123", chars) );
+ assertEquals("i+am here-123", WordUtils.uncapitalize("I+Am Here-123", chars) );
+ assertEquals("i-am+hERE 123", WordUtils.uncapitalize("i-am+HERE 123", chars) );
+ assertEquals("i aM-hERE+123", WordUtils.uncapitalize("I AM-HERE+123", chars) );
+ chars = new char[] {'.'};
+ assertEquals("i AM.fINE", WordUtils.uncapitalize("I AM.FINE", chars) );
+ assertEquals("i aM.FINE", WordUtils.uncapitalize("I AM.FINE", null) );
+ }
+
+ //-----------------------------------------------------------------------
+ @Test
+ public void testInitials_String() {
+ assertEquals(null, WordUtils.initials(null));
+ assertEquals("", WordUtils.initials(""));
+ assertEquals("", WordUtils.initials(" "));
+
+ assertEquals("I", WordUtils.initials("I"));
+ assertEquals("i", WordUtils.initials("i"));
+ assertEquals("BJL", WordUtils.initials("Ben John Lee"));
+ assertEquals("BJL", WordUtils.initials(" Ben \n John\tLee\t"));
+ assertEquals("BJ", WordUtils.initials("Ben J.Lee"));
+ assertEquals("BJ.L", WordUtils.initials(" Ben John . Lee"));
+ assertEquals("iah1", WordUtils.initials("i am here 123"));
+ }
+
+ // -----------------------------------------------------------------------
+ @Test
+ public void testInitials_String_charArray() {
+ char[] array = null;
+ assertEquals(null, WordUtils.initials(null, array));
+ assertEquals("", WordUtils.initials("", array));
+ assertEquals("", WordUtils.initials(" ", array));
+ assertEquals("I", WordUtils.initials("I", array));
+ assertEquals("i", WordUtils.initials("i", array));
+ assertEquals("S", WordUtils.initials("SJC", array));
+ assertEquals("BJL", WordUtils.initials("Ben John Lee", array));
+ assertEquals("BJL", WordUtils.initials(" Ben \n John\tLee\t", array));
+ assertEquals("BJ", WordUtils.initials("Ben J.Lee", array));
+ assertEquals("BJ.L", WordUtils.initials(" Ben John . Lee", array));
+ assertEquals("KO", WordUtils.initials("Kay O'Murphy", array));
+ assertEquals("iah1", WordUtils.initials("i am here 123", array));
+
+ array = new char[0];
+ assertEquals(null, WordUtils.initials(null, array));
+ assertEquals("", WordUtils.initials("", array));
+ assertEquals("", WordUtils.initials(" ", array));
+ assertEquals("", WordUtils.initials("I", array));
+ assertEquals("", WordUtils.initials("i", array));
+ assertEquals("", WordUtils.initials("SJC", array));
+ assertEquals("", WordUtils.initials("Ben John Lee", array));
+ assertEquals("", WordUtils.initials(" Ben \n John\tLee\t", array));
+ assertEquals("", WordUtils.initials("Ben J.Lee", array));
+ assertEquals("", WordUtils.initials(" Ben John . Lee", array));
+ assertEquals("", WordUtils.initials("Kay O'Murphy", array));
+ assertEquals("", WordUtils.initials("i am here 123", array));
+
+ array = " ".toCharArray();
+ assertEquals(null, WordUtils.initials(null, array));
+ assertEquals("", WordUtils.initials("", array));
+ assertEquals("", WordUtils.initials(" ", array));
+ assertEquals("I", WordUtils.initials("I", array));
+ assertEquals("i", WordUtils.initials("i", array));
+ assertEquals("S", WordUtils.initials("SJC", array));
+ assertEquals("BJL", WordUtils.initials("Ben John Lee", array));
+ assertEquals("BJ", WordUtils.initials("Ben J.Lee", array));
+ assertEquals("B\nJ", WordUtils.initials(" Ben \n John\tLee\t", array));
+ assertEquals("BJ.L", WordUtils.initials(" Ben John . Lee", array));
+ assertEquals("KO", WordUtils.initials("Kay O'Murphy", array));
+ assertEquals("iah1", WordUtils.initials("i am here 123", array));
+
+ array = " .".toCharArray();
+ assertEquals(null, WordUtils.initials(null, array));
+ assertEquals("", WordUtils.initials("", array));
+ assertEquals("", WordUtils.initials(" ", array));
+ assertEquals("I", WordUtils.initials("I", array));
+ assertEquals("i", WordUtils.initials("i", array));
+ assertEquals("S", WordUtils.initials("SJC", array));
+ assertEquals("BJL", WordUtils.initials("Ben John Lee", array));
+ assertEquals("BJL", WordUtils.initials("Ben J.Lee", array));
+ assertEquals("BJL", WordUtils.initials(" Ben John . Lee", array));
+ assertEquals("KO", WordUtils.initials("Kay O'Murphy", array));
+ assertEquals("iah1", WordUtils.initials("i am here 123", array));
+
+ array = " .'".toCharArray();
+ assertEquals(null, WordUtils.initials(null, array));
+ assertEquals("", WordUtils.initials("", array));
+ assertEquals("", WordUtils.initials(" ", array));
+ assertEquals("I", WordUtils.initials("I", array));
+ assertEquals("i", WordUtils.initials("i", array));
+ assertEquals("S", WordUtils.initials("SJC", array));
+ assertEquals("BJL", WordUtils.initials("Ben John Lee", array));
+ assertEquals("BJL", WordUtils.initials("Ben J.Lee", array));
+ assertEquals("BJL", WordUtils.initials(" Ben John . Lee", array));
+ assertEquals("KOM", WordUtils.initials("Kay O'Murphy", array));
+ assertEquals("iah1", WordUtils.initials("i am here 123", array));
+
+ array = "SIJo1".toCharArray();
+ assertEquals(null, WordUtils.initials(null, array));
+ assertEquals("", WordUtils.initials("", array));
+ assertEquals(" ", WordUtils.initials(" ", array));
+ assertEquals("", WordUtils.initials("I", array));
+ assertEquals("i", WordUtils.initials("i", array));
+ assertEquals("C", WordUtils.initials("SJC", array));
+ assertEquals("Bh", WordUtils.initials("Ben John Lee", array));
+ assertEquals("B.", WordUtils.initials("Ben J.Lee", array));
+ assertEquals(" h", WordUtils.initials(" Ben John . Lee", array));
+ assertEquals("K", WordUtils.initials("Kay O'Murphy", array));
+ assertEquals("i2", WordUtils.initials("i am here 123", array));
+ }
+
+ // -----------------------------------------------------------------------
+ @Test
+ public void testSwapCase_String() {
+ assertEquals(null, WordUtils.swapCase(null));
+ assertEquals("", WordUtils.swapCase(""));
+ assertEquals(" ", WordUtils.swapCase(" "));
+
+ assertEquals("i", WordUtils.swapCase("I") );
+ assertEquals("I", WordUtils.swapCase("i") );
+ assertEquals("I AM HERE 123", WordUtils.swapCase("i am here 123") );
+ assertEquals("i aM hERE 123", WordUtils.swapCase("I Am Here 123") );
+ assertEquals("I AM here 123", WordUtils.swapCase("i am HERE 123") );
+ assertEquals("i am here 123", WordUtils.swapCase("I AM HERE 123") );
+
+ final String test = "This String contains a TitleCase character: \u01C8";
+ final String expect = "tHIS sTRING CONTAINS A tITLEcASE CHARACTER: \u01C9";
+ assertEquals(expect, WordUtils.swapCase(test));
+ }
+
+}