You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2018/02/12 18:13:51 UTC

[1/4] [text] [TEXT-115] Add a StrBuilder replacement based on the StringMatcher interface: TextStringBuilder.

Repository: commons-text
Updated Branches:
  refs/heads/master 0bf361aaa -> 978e2896d


http://git-wip-us.apache.org/repos/asf/commons-text/blob/978e2896/src/test/java/org/apache/commons/text/TextStringBuilderTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/text/TextStringBuilderTest.java b/src/test/java/org/apache/commons/text/TextStringBuilderTest.java
new file mode 100644
index 0000000..88d3a50
--- /dev/null
+++ b/src/test/java/org/apache/commons/text/TextStringBuilderTest.java
@@ -0,0 +1,2147 @@
+/*
+ * 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.assertNotSame;
+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.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+
+import org.apache.commons.text.matcher.StringMatcher;
+import org.apache.commons.text.matcher.StringMatcherFactory;
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link TextStringBuilder}.
+ */
+public class TextStringBuilderTest {
+
+    // -----------------------------------------------------------------------
+    @Test
+    public void testConstructors() {
+        final TextStringBuilder sb0 = new TextStringBuilder();
+        assertEquals(32, sb0.capacity());
+        assertEquals(0, sb0.length());
+        assertEquals(0, sb0.size());
+
+        final TextStringBuilder sb1 = new TextStringBuilder(32);
+        assertEquals(32, sb1.capacity());
+        assertEquals(0, sb1.length());
+        assertEquals(0, sb1.size());
+
+        final TextStringBuilder sb2 = new TextStringBuilder(0);
+        assertEquals(32, sb2.capacity());
+        assertEquals(0, sb2.length());
+        assertEquals(0, sb2.size());
+
+        final TextStringBuilder sb3 = new TextStringBuilder(-1);
+        assertEquals(32, sb3.capacity());
+        assertEquals(0, sb3.length());
+        assertEquals(0, sb3.size());
+
+        final TextStringBuilder sb4 = new TextStringBuilder(1);
+        assertEquals(1, sb4.capacity());
+        assertEquals(0, sb4.length());
+        assertEquals(0, sb4.size());
+
+        final TextStringBuilder sb5 = new TextStringBuilder((String) null);
+        assertEquals(32, sb5.capacity());
+        assertEquals(0, sb5.length());
+        assertEquals(0, sb5.size());
+
+        final TextStringBuilder sb6 = new TextStringBuilder("");
+        assertEquals(32, sb6.capacity());
+        assertEquals(0, sb6.length());
+        assertEquals(0, sb6.size());
+
+        final TextStringBuilder sb7 = new TextStringBuilder("foo");
+        assertEquals(35, sb7.capacity());
+        assertEquals(3, sb7.length());
+        assertEquals(3, sb7.size());
+    }
+
+    // -----------------------------------------------------------------------
+    @Test
+    public void testChaining() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        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 TextStringBuilder sb = new TextStringBuilder();
+            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 TextStringBuilder sb = new TextStringBuilder("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 TextStringBuilder sb = new TextStringBuilder();
+            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 TextStringBuilder sb = new TextStringBuilder("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 TextStringBuilder sb = new TextStringBuilder();
+            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 TextStringBuilder sb = new TextStringBuilder("Test");
+        sb.readFrom(new MockReadable(" 123"));
+        assertEquals("Test 123", sb.toString());
+    }
+
+    private static class MockReadable implements Readable {
+
+        private final CharBuffer src;
+
+        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 TextStringBuilder sb = new TextStringBuilder();
+        assertNull(sb.getNewLineText());
+
+        sb.setNewLineText("#");
+        assertEquals("#", sb.getNewLineText());
+
+        sb.setNewLineText("");
+        assertEquals("", sb.getNewLineText());
+
+        sb.setNewLineText((String) null);
+        assertNull(sb.getNewLineText());
+    }
+
+    // -----------------------------------------------------------------------
+    @Test
+    public void testGetSetNullText() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        assertNull(sb.getNullText());
+
+        sb.setNullText("null");
+        assertEquals("null", sb.getNullText());
+
+        sb.setNullText("");
+        assertNull(sb.getNullText());
+
+        sb.setNullText("NULL");
+        assertEquals("NULL", sb.getNullText());
+
+        sb.setNullText((String) null);
+        assertNull(sb.getNullText());
+    }
+
+    // -----------------------------------------------------------------------
+    @Test
+    public void testCapacityAndLength() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        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());
+        assertFalse(sb.isEmpty());
+
+        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());
+        assertFalse(sb.isEmpty());
+
+        sb.ensureCapacity(16);
+        assertTrue(sb.capacity() > 16);
+        assertEquals(33, sb.length());
+        assertEquals(33, sb.size());
+        assertFalse(sb.isEmpty());
+
+        sb.minimizeCapacity();
+        assertEquals(33, sb.capacity());
+        assertEquals(33, sb.length());
+        assertEquals(33, sb.size());
+        assertFalse(sb.isEmpty());
+
+        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());
+        assertFalse(sb.isEmpty());
+
+        sb.setLength(16);
+        assertTrue(sb.capacity() >= 16);
+        assertEquals(16, sb.length());
+        assertEquals(16, sb.size());
+        assertEquals("1234567890123456", sb.toString());
+        assertFalse(sb.isEmpty());
+
+        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());
+        assertFalse(sb.isEmpty());
+
+        sb.setLength(0);
+        assertTrue(sb.capacity() >= 32);
+        assertEquals(0, sb.length());
+        assertEquals(0, sb.size());
+        assertTrue(sb.isEmpty());
+    }
+
+    // -----------------------------------------------------------------------
+    @Test
+    public void testLength() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        assertEquals(0, sb.length());
+
+        sb.append("Hello");
+        assertEquals(5, sb.length());
+    }
+
+    @Test
+    public void testSetLength() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        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 TextStringBuilder sb = new TextStringBuilder();
+        assertEquals(sb.buffer.length, sb.capacity());
+
+        sb.append("HelloWorldHelloWorldHelloWorldHelloWorld");
+        assertEquals(sb.buffer.length, sb.capacity());
+    }
+
+    @Test
+    public void testEnsureCapacity() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        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 TextStringBuilder sb = new TextStringBuilder();
+        sb.minimizeCapacity();
+        assertEquals(0, sb.capacity());
+
+        sb.append("HelloWorld");
+        sb.minimizeCapacity();
+        assertEquals(10, sb.capacity());
+    }
+
+    // -----------------------------------------------------------------------
+    @Test
+    public void testSize() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        assertEquals(0, sb.size());
+
+        sb.append("Hello");
+        assertEquals(5, sb.size());
+    }
+
+    @Test
+    public void testIsEmpty() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        assertTrue(sb.isEmpty());
+
+        sb.append("Hello");
+        assertFalse(sb.isEmpty());
+
+        sb.clear();
+        assertTrue(sb.isEmpty());
+    }
+
+    @Test
+    public void testClear() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        sb.append("Hello");
+        sb.clear();
+        assertEquals(0, sb.length());
+        assertTrue(sb.buffer.length >= 5);
+    }
+
+    // -----------------------------------------------------------------------
+    @Test
+    public void testCharAt() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        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 TextStringBuilder sb = new TextStringBuilder();
+        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 TextStringBuilder sb = new TextStringBuilder("abc");
+        sb.deleteCharAt(0);
+        assertEquals("bc", sb.toString());
+
+        try {
+            sb.deleteCharAt(1000);
+            fail("Expected IndexOutOfBoundsException");
+        } catch (final IndexOutOfBoundsException e) {
+        }
+    }
+
+    // -----------------------------------------------------------------------
+    @Test
+    public void testToCharArray() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        assertEquals(0, sb.toCharArray().length);
+
+        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 TextStringBuilder sb = new TextStringBuilder();
+        assertEquals(0, sb.toCharArray(0, 0).length);
+
+        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 TextStringBuilder sb = new TextStringBuilder();
+
+        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 TextStringBuilder sb = new TextStringBuilder();
+
+        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() {
+        TextStringBuilder sb = new TextStringBuilder("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 TextStringBuilder("anything");
+        try {
+            sb.delete(2, 1);
+            fail("Expected IndexOutOfBoundsException");
+        } catch (final IndexOutOfBoundsException e) {
+        }
+    }
+
+    // -----------------------------------------------------------------------
+    @Test
+    public void testDeleteAll_char() {
+        TextStringBuilder sb = new TextStringBuilder("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 TextStringBuilder("");
+        sb.deleteAll('b');
+        assertEquals("", sb.toString());
+    }
+
+    @Test
+    public void testDeleteFirst_char() {
+        TextStringBuilder sb = new TextStringBuilder("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 TextStringBuilder("");
+        sb.deleteFirst('b');
+        assertEquals("", sb.toString());
+    }
+
+    // -----------------------------------------------------------------------
+    @Test
+    public void testDeleteAll_String() {
+        TextStringBuilder sb = new TextStringBuilder("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 TextStringBuilder("abcbccba");
+        sb.deleteAll("bc");
+        assertEquals("acba", sb.toString());
+
+        sb = new TextStringBuilder("");
+        sb.deleteAll("bc");
+        assertEquals("", sb.toString());
+    }
+
+    @Test
+    public void testDeleteFirst_String() {
+        TextStringBuilder sb = new TextStringBuilder("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 TextStringBuilder("abcbccba");
+        sb.deleteFirst("bc");
+        assertEquals("abccba", sb.toString());
+
+        sb = new TextStringBuilder("");
+        sb.deleteFirst("bc");
+        assertEquals("", sb.toString());
+    }
+
+    // -----------------------------------------------------------------------
+    @Test
+    public void testDeleteAll_StringMatcher() {
+        TextStringBuilder sb = new TextStringBuilder("A0xA1A2yA3");
+        sb.deleteAll((StringMatcher) null);
+        assertEquals("A0xA1A2yA3", sb.toString());
+        sb.deleteAll(A_NUMBER_MATCHER);
+        assertEquals("xy", sb.toString());
+
+        sb = new TextStringBuilder("Ax1");
+        sb.deleteAll(A_NUMBER_MATCHER);
+        assertEquals("Ax1", sb.toString());
+
+        sb = new TextStringBuilder("");
+        sb.deleteAll(A_NUMBER_MATCHER);
+        assertEquals("", sb.toString());
+    }
+
+    @Test
+    public void testDeleteFirst_StringMatcher() {
+        TextStringBuilder sb = new TextStringBuilder("A0xA1A2yA3");
+        sb.deleteFirst((StringMatcher) null);
+        assertEquals("A0xA1A2yA3", sb.toString());
+        sb.deleteFirst(A_NUMBER_MATCHER);
+        assertEquals("xA1A2yA3", sb.toString());
+
+        sb = new TextStringBuilder("Ax1");
+        sb.deleteFirst(A_NUMBER_MATCHER);
+        assertEquals("Ax1", sb.toString());
+
+        sb = new TextStringBuilder("");
+        sb.deleteFirst(A_NUMBER_MATCHER);
+        assertEquals("", sb.toString());
+    }
+
+    // -----------------------------------------------------------------------
+    @Test
+    public void testReplace_int_int_String() {
+        TextStringBuilder sb = new TextStringBuilder("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 TextStringBuilder("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 TextStringBuilder();
+        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 TextStringBuilder sb = new TextStringBuilder("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 TextStringBuilder sb = new TextStringBuilder("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() {
+        TextStringBuilder sb = new TextStringBuilder("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 TextStringBuilder("abcba");
+        sb.replaceAll("b", "xbx");
+        assertEquals("axbxcxbxa", sb.toString());
+
+        sb = new TextStringBuilder("bb");
+        sb.replaceAll("b", "xbx");
+        assertEquals("xbxxbx", sb.toString());
+    }
+
+    @Test
+    public void testReplaceFirst_String_String() {
+        TextStringBuilder sb = new TextStringBuilder("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 TextStringBuilder("abcba");
+        sb.replaceFirst("b", "xbx");
+        assertEquals("axbxcba", sb.toString());
+
+        sb = new TextStringBuilder("bb");
+        sb.replaceFirst("b", "xbx");
+        assertEquals("xbxb", sb.toString());
+    }
+
+    // -----------------------------------------------------------------------
+    @Test
+    public void testReplaceAll_StringMatcher_String() {
+        TextStringBuilder sb = new TextStringBuilder("abcbccba");
+        sb.replaceAll((StringMatcher) null, null);
+        assertEquals("abcbccba", sb.toString());
+        sb.replaceAll((StringMatcher) null, "anything");
+        assertEquals("abcbccba", sb.toString());
+        sb.replaceAll(StringMatcherFactory.INSTANCE.noneMatcher(), null);
+        assertEquals("abcbccba", sb.toString());
+        sb.replaceAll(StringMatcherFactory.INSTANCE.noneMatcher(), "anything");
+        assertEquals("abcbccba", sb.toString());
+
+        sb.replaceAll(StringMatcherFactory.INSTANCE.charMatcher('x'), "y");
+        assertEquals("abcbccba", sb.toString());
+        sb.replaceAll(StringMatcherFactory.INSTANCE.charMatcher('a'), "d");
+        assertEquals("dbcbccbd", sb.toString());
+        sb.replaceAll(StringMatcherFactory.INSTANCE.charMatcher('d'), null);
+        assertEquals("bcbccb", sb.toString());
+        sb.replaceAll(StringMatcherFactory.INSTANCE.stringMatcher("cb"), "-");
+        assertEquals("b-c-", sb.toString());
+
+        sb = new TextStringBuilder("abcba");
+        sb.replaceAll(StringMatcherFactory.INSTANCE.charMatcher('b'), "xbx");
+        assertEquals("axbxcxbxa", sb.toString());
+
+        sb = new TextStringBuilder("bb");
+        sb.replaceAll(StringMatcherFactory.INSTANCE.charMatcher('b'), "xbx");
+        assertEquals("xbxxbx", sb.toString());
+
+        sb = new TextStringBuilder("A1-A2A3-A4");
+        sb.replaceAll(A_NUMBER_MATCHER, "***");
+        assertEquals("***-******-***", sb.toString());
+
+        sb = new TextStringBuilder("Dear X, hello X.");
+        sb.replaceAll(StringMatcherFactory.INSTANCE.stringMatcher("X"), "012345678901234567");
+        assertEquals("Dear 012345678901234567, hello 012345678901234567.", sb.toString());
+    }
+
+    @Test
+    public void testReplaceFirst_StringMatcher_String() {
+        TextStringBuilder sb = new TextStringBuilder("abcbccba");
+        sb.replaceFirst((StringMatcher) null, null);
+        assertEquals("abcbccba", sb.toString());
+        sb.replaceFirst((StringMatcher) null, "anything");
+        assertEquals("abcbccba", sb.toString());
+        sb.replaceFirst(StringMatcherFactory.INSTANCE.noneMatcher(), null);
+        assertEquals("abcbccba", sb.toString());
+        sb.replaceFirst(StringMatcherFactory.INSTANCE.noneMatcher(), "anything");
+        assertEquals("abcbccba", sb.toString());
+
+        sb.replaceFirst(StringMatcherFactory.INSTANCE.charMatcher('x'), "y");
+        assertEquals("abcbccba", sb.toString());
+        sb.replaceFirst(StringMatcherFactory.INSTANCE.charMatcher('a'), "d");
+        assertEquals("dbcbccba", sb.toString());
+        sb.replaceFirst(StringMatcherFactory.INSTANCE.charMatcher('d'), null);
+        assertEquals("bcbccba", sb.toString());
+        sb.replaceFirst(StringMatcherFactory.INSTANCE.stringMatcher("cb"), "-");
+        assertEquals("b-ccba", sb.toString());
+
+        sb = new TextStringBuilder("abcba");
+        sb.replaceFirst(StringMatcherFactory.INSTANCE.charMatcher('b'), "xbx");
+        assertEquals("axbxcba", sb.toString());
+
+        sb = new TextStringBuilder("bb");
+        sb.replaceFirst(StringMatcherFactory.INSTANCE.charMatcher('b'), "xbx");
+        assertEquals("xbxb", sb.toString());
+
+        sb = new TextStringBuilder("A1-A2A3-A4");
+        sb.replaceFirst(A_NUMBER_MATCHER, "***");
+        assertEquals("***-A2A3-A4", sb.toString());
+    }
+
+    // -----------------------------------------------------------------------
+    @Test
+    public void testReplace_StringMatcher_String_int_int_int_VaryMatcher() {
+        TextStringBuilder sb = new TextStringBuilder("abcbccba");
+        sb.replace((StringMatcher) null, "x", 0, sb.length(), -1);
+        assertEquals("abcbccba", sb.toString());
+
+        sb.replace(StringMatcherFactory.INSTANCE.charMatcher('a'), "x", 0, sb.length(), -1);
+        assertEquals("xbcbccbx", sb.toString());
+
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("cb"), "x", 0, sb.length(), -1);
+        assertEquals("xbxcxx", sb.toString());
+
+        sb = new TextStringBuilder("A1-A2A3-A4");
+        sb.replace(A_NUMBER_MATCHER, "***", 0, sb.length(), -1);
+        assertEquals("***-******-***", sb.toString());
+
+        sb = new TextStringBuilder();
+        sb.replace(A_NUMBER_MATCHER, "***", 0, sb.length(), -1);
+        assertEquals("", sb.toString());
+    }
+
+    @Test
+    public void testReplace_StringMatcher_String_int_int_int_VaryReplace() {
+        TextStringBuilder sb = new TextStringBuilder("abcbccba");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("cb"), "cb", 0, sb.length(), -1);
+        assertEquals("abcbccba", sb.toString());
+
+        sb = new TextStringBuilder("abcbccba");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("cb"), "-", 0, sb.length(), -1);
+        assertEquals("ab-c-a", sb.toString());
+
+        sb = new TextStringBuilder("abcbccba");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("cb"), "+++", 0, sb.length(), -1);
+        assertEquals("ab+++c+++a", sb.toString());
+
+        sb = new TextStringBuilder("abcbccba");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("cb"), "", 0, sb.length(), -1);
+        assertEquals("abca", sb.toString());
+
+        sb = new TextStringBuilder("abcbccba");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("cb"), null, 0, sb.length(), -1);
+        assertEquals("abca", sb.toString());
+    }
+
+    @Test
+    public void testReplace_StringMatcher_String_int_int_int_VaryStartIndex() {
+        TextStringBuilder sb = new TextStringBuilder("aaxaaaayaa");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("aa"), "-", 0, sb.length(), -1);
+        assertEquals("-x--y-", sb.toString());
+
+        sb = new TextStringBuilder("aaxaaaayaa");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("aa"), "-", 1, sb.length(), -1);
+        assertEquals("aax--y-", sb.toString());
+
+        sb = new TextStringBuilder("aaxaaaayaa");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("aa"), "-", 2, sb.length(), -1);
+        assertEquals("aax--y-", sb.toString());
+
+        sb = new TextStringBuilder("aaxaaaayaa");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("aa"), "-", 3, sb.length(), -1);
+        assertEquals("aax--y-", sb.toString());
+
+        sb = new TextStringBuilder("aaxaaaayaa");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("aa"), "-", 4, sb.length(), -1);
+        assertEquals("aaxa-ay-", sb.toString());
+
+        sb = new TextStringBuilder("aaxaaaayaa");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("aa"), "-", 5, sb.length(), -1);
+        assertEquals("aaxaa-y-", sb.toString());
+
+        sb = new TextStringBuilder("aaxaaaayaa");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("aa"), "-", 6, sb.length(), -1);
+        assertEquals("aaxaaaay-", sb.toString());
+
+        sb = new TextStringBuilder("aaxaaaayaa");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("aa"), "-", 7, sb.length(), -1);
+        assertEquals("aaxaaaay-", sb.toString());
+
+        sb = new TextStringBuilder("aaxaaaayaa");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("aa"), "-", 8, sb.length(), -1);
+        assertEquals("aaxaaaay-", sb.toString());
+
+        sb = new TextStringBuilder("aaxaaaayaa");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("aa"), "-", 9, sb.length(), -1);
+        assertEquals("aaxaaaayaa", sb.toString());
+
+        sb = new TextStringBuilder("aaxaaaayaa");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("aa"), "-", 10, sb.length(), -1);
+        assertEquals("aaxaaaayaa", sb.toString());
+
+        sb = new TextStringBuilder("aaxaaaayaa");
+        try {
+            sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("aa"), "-", 11, sb.length(), -1);
+            fail();
+        } catch (final IndexOutOfBoundsException ex) {
+        }
+        assertEquals("aaxaaaayaa", sb.toString());
+
+        sb = new TextStringBuilder("aaxaaaayaa");
+        try {
+            sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("aa"), "-", -1, sb.length(), -1);
+            fail();
+        } catch (final IndexOutOfBoundsException ex) {
+        }
+        assertEquals("aaxaaaayaa", sb.toString());
+    }
+
+    @Test
+    public void testReplace_StringMatcher_String_int_int_int_VaryEndIndex() {
+        TextStringBuilder sb = new TextStringBuilder("aaxaaaayaa");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("aa"), "-", 0, 0, -1);
+        assertEquals("aaxaaaayaa", sb.toString());
+
+        sb = new TextStringBuilder("aaxaaaayaa");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("aa"), "-", 0, 2, -1);
+        assertEquals("-xaaaayaa", sb.toString());
+
+        sb = new TextStringBuilder("aaxaaaayaa");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("aa"), "-", 0, 3, -1);
+        assertEquals("-xaaaayaa", sb.toString());
+
+        sb = new TextStringBuilder("aaxaaaayaa");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("aa"), "-", 0, 4, -1);
+        assertEquals("-xaaaayaa", sb.toString());
+
+        sb = new TextStringBuilder("aaxaaaayaa");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("aa"), "-", 0, 5, -1);
+        assertEquals("-x-aayaa", sb.toString());
+
+        sb = new TextStringBuilder("aaxaaaayaa");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("aa"), "-", 0, 6, -1);
+        assertEquals("-x-aayaa", sb.toString());
+
+        sb = new TextStringBuilder("aaxaaaayaa");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("aa"), "-", 0, 7, -1);
+        assertEquals("-x--yaa", sb.toString());
+
+        sb = new TextStringBuilder("aaxaaaayaa");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("aa"), "-", 0, 8, -1);
+        assertEquals("-x--yaa", sb.toString());
+
+        sb = new TextStringBuilder("aaxaaaayaa");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("aa"), "-", 0, 9, -1);
+        assertEquals("-x--yaa", sb.toString());
+
+        sb = new TextStringBuilder("aaxaaaayaa");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("aa"), "-", 0, 10, -1);
+        assertEquals("-x--y-", sb.toString());
+
+        sb = new TextStringBuilder("aaxaaaayaa");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("aa"), "-", 0, 1000, -1);
+        assertEquals("-x--y-", sb.toString());
+
+        sb = new TextStringBuilder("aaxaaaayaa");
+        try {
+            sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("aa"), "-", 2, 1, -1);
+            fail();
+        } catch (final IndexOutOfBoundsException ex) {
+        }
+        assertEquals("aaxaaaayaa", sb.toString());
+    }
+
+    @Test
+    public void testReplace_StringMatcher_String_int_int_int_VaryCount() {
+        TextStringBuilder sb = new TextStringBuilder("aaxaaaayaa");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("aa"), "-", 0, 10, -1);
+        assertEquals("-x--y-", sb.toString());
+
+        sb = new TextStringBuilder("aaxaaaayaa");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("aa"), "-", 0, 10, 0);
+        assertEquals("aaxaaaayaa", sb.toString());
+
+        sb = new TextStringBuilder("aaxaaaayaa");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("aa"), "-", 0, 10, 1);
+        assertEquals("-xaaaayaa", sb.toString());
+
+        sb = new TextStringBuilder("aaxaaaayaa");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("aa"), "-", 0, 10, 2);
+        assertEquals("-x-aayaa", sb.toString());
+
+        sb = new TextStringBuilder("aaxaaaayaa");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("aa"), "-", 0, 10, 3);
+        assertEquals("-x--yaa", sb.toString());
+
+        sb = new TextStringBuilder("aaxaaaayaa");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("aa"), "-", 0, 10, 4);
+        assertEquals("-x--y-", sb.toString());
+
+        sb = new TextStringBuilder("aaxaaaayaa");
+        sb.replace(StringMatcherFactory.INSTANCE.stringMatcher("aa"), "-", 0, 10, 5);
+        assertEquals("-x--y-", sb.toString());
+    }
+
+    // -----------------------------------------------------------------------
+    @Test
+    public void testReverse() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        assertEquals("", sb.reverse().toString());
+
+        sb.clear().append(true);
+        assertEquals("eurt", sb.reverse().toString());
+        assertEquals("true", sb.reverse().toString());
+    }
+
+    // -----------------------------------------------------------------------
+    @Test
+    public void testTrim() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        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 TextStringBuilder sb = new TextStringBuilder();
+        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 TextStringBuilder sb = new TextStringBuilder();
+        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 TextStringBuilder sb = new TextStringBuilder("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 TextStringBuilder sb = new TextStringBuilder("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 TextStringBuilder sb = new TextStringBuilder("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 TextStringBuilder sb = new TextStringBuilder("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 TextStringBuilder sb = new TextStringBuilder("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 TextStringBuilder sb = new TextStringBuilder("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 TextStringBuilder sb = new TextStringBuilder("abcdefghijklmnopqrstuvwxyz");
+        assertTrue(sb.contains('a'));
+        assertTrue(sb.contains('o'));
+        assertTrue(sb.contains('z'));
+        assertFalse(sb.contains('1'));
+    }
+
+    @Test
+    public void testContains_String() {
+        final TextStringBuilder sb = new TextStringBuilder("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_StringMatcher() {
+        TextStringBuilder sb = new TextStringBuilder("abcdefghijklmnopqrstuvwxyz");
+        assertTrue(sb.contains(StringMatcherFactory.INSTANCE.charMatcher('a')));
+        assertTrue(sb.contains(StringMatcherFactory.INSTANCE.stringMatcher("pq")));
+        assertTrue(sb.contains(StringMatcherFactory.INSTANCE.charMatcher('z')));
+        assertFalse(sb.contains(StringMatcherFactory.INSTANCE.stringMatcher("zy")));
+        assertFalse(sb.contains((StringMatcher) null));
+
+        sb = new TextStringBuilder();
+        assertFalse(sb.contains(A_NUMBER_MATCHER));
+        sb.append("B A1 C");
+        assertTrue(sb.contains(A_NUMBER_MATCHER));
+    }
+
+    // -----------------------------------------------------------------------
+    @Test
+    public void testIndexOf_char() {
+        final TextStringBuilder sb = new TextStringBuilder("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() {
+        TextStringBuilder sb = new TextStringBuilder("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 TextStringBuilder("xyzabc");
+        assertEquals(2, sb.indexOf('z', 0));
+        assertEquals(-1, sb.indexOf('z', 3));
+    }
+
+    @Test
+    public void testLastIndexOf_char() {
+        final TextStringBuilder sb = new TextStringBuilder("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() {
+        TextStringBuilder sb = new TextStringBuilder("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 TextStringBuilder("xyzabc");
+        assertEquals(2, sb.lastIndexOf('z', sb.length()));
+        assertEquals(-1, sb.lastIndexOf('z', 1));
+    }
+
+    // -----------------------------------------------------------------------
+    @Test
+    public void testIndexOf_String() {
+        final TextStringBuilder sb = new TextStringBuilder("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() {
+        TextStringBuilder sb = new TextStringBuilder("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 TextStringBuilder("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 TextStringBuilder sb = new TextStringBuilder("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() {
+        TextStringBuilder sb = new TextStringBuilder("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 TextStringBuilder("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_StringMatcher() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        assertEquals(-1, sb.indexOf((StringMatcher) null));
+        assertEquals(-1, sb.indexOf(StringMatcherFactory.INSTANCE.charMatcher('a')));
+
+        sb.append("ab bd");
+        assertEquals(0, sb.indexOf(StringMatcherFactory.INSTANCE.charMatcher('a')));
+        assertEquals(1, sb.indexOf(StringMatcherFactory.INSTANCE.charMatcher('b')));
+        assertEquals(2, sb.indexOf(StringMatcherFactory.INSTANCE.spaceMatcher()));
+        assertEquals(4, sb.indexOf(StringMatcherFactory.INSTANCE.charMatcher('d')));
+        assertEquals(-1, sb.indexOf(StringMatcherFactory.INSTANCE.noneMatcher()));
+        assertEquals(-1, sb.indexOf((StringMatcher) null));
+
+        sb.append(" A1 junction");
+        assertEquals(6, sb.indexOf(A_NUMBER_MATCHER));
+    }
+
+    @Test
+    public void testIndexOf_StringMatcher_int() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        assertEquals(-1, sb.indexOf((StringMatcher) null, 2));
+        assertEquals(-1, sb.indexOf(StringMatcherFactory.INSTANCE.charMatcher('a'), 2));
+        assertEquals(-1, sb.indexOf(StringMatcherFactory.INSTANCE.charMatcher('a'), 0));
+
+        sb.append("ab bd");
+        assertEquals(0, sb.indexOf(StringMatcherFactory.INSTANCE.charMatcher('a'), -2));
+        assertEquals(0, sb.indexOf(StringMatcherFactory.INSTANCE.charMatcher('a'), 0));
+        assertEquals(-1, sb.indexOf(StringMatcherFactory.INSTANCE.charMatcher('a'), 2));
+        assertEquals(-1, sb.indexOf(StringMatcherFactory.INSTANCE.charMatcher('a'), 20));
+
+        assertEquals(1, sb.indexOf(StringMatcherFactory.INSTANCE.charMatcher('b'), -1));
+        assertEquals(1, sb.indexOf(StringMatcherFactory.INSTANCE.charMatcher('b'), 0));
+        assertEquals(1, sb.indexOf(StringMatcherFactory.INSTANCE.charMatcher('b'), 1));
+        assertEquals(3, sb.indexOf(StringMatcherFactory.INSTANCE.charMatcher('b'), 2));
+        assertEquals(3, sb.indexOf(StringMatcherFactory.INSTANCE.charMatcher('b'), 3));
+        assertEquals(-1, sb.indexOf(StringMatcherFactory.INSTANCE.charMatcher('b'), 4));
+        assertEquals(-1, sb.indexOf(StringMatcherFactory.INSTANCE.charMatcher('b'), 5));
+        assertEquals(-1, sb.indexOf(StringMatcherFactory.INSTANCE.charMatcher('b'), 6));
+
+        assertEquals(2, sb.indexOf(StringMatcherFactory.INSTANCE.spaceMatcher(), -2));
+        assertEquals(2, sb.indexOf(StringMatcherFactory.INSTANCE.spaceMatcher(), 0));
+        assertEquals(2, sb.indexOf(StringMatcherFactory.INSTANCE.spaceMatcher(), 2));
+        assertEquals(-1, sb.indexOf(StringMatcherFactory.INSTANCE.spaceMatcher(), 4));
+        assertEquals(-1, sb.indexOf(StringMatcherFactory.INSTANCE.spaceMatcher(), 20));
+
+        assertEquals(-1, sb.indexOf(StringMatcherFactory.INSTANCE.noneMatcher(), 0));
+        assertEquals(-1, sb.indexOf((StringMatcher) 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_StringMatcher() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        assertEquals(-1, sb.lastIndexOf((StringMatcher) null));
+        assertEquals(-1, sb.lastIndexOf(StringMatcherFactory.INSTANCE.charMatcher('a')));
+
+        sb.append("ab bd");
+        assertEquals(0, sb.lastIndexOf(StringMatcherFactory.INSTANCE.charMatcher('a')));
+        assertEquals(3, sb.lastIndexOf(StringMatcherFactory.INSTANCE.charMatcher('b')));
+        assertEquals(2, sb.lastIndexOf(StringMatcherFactory.INSTANCE.spaceMatcher()));
+        assertEquals(4, sb.lastIndexOf(StringMatcherFactory.INSTANCE.charMatcher('d')));
+        assertEquals(-1, sb.lastIndexOf(StringMatcherFactory.INSTANCE.noneMatcher()));
+        assertEquals(-1, sb.lastIndexOf((StringMatcher) null));
+
+        sb.append(" A1 junction");
+        assertEquals(6, sb.lastIndexOf(A_NUMBER_MATCHER));
+    }
+
+    @Test
+    public void testLastIndexOf_StringMatcher_int() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        assertEquals(-1, sb.lastIndexOf((StringMatcher) null, 2));
+        assertEquals(-1, sb.lastIndexOf(StringMatcherFactory.INSTANCE.charMatcher('a'), 2));
+        assertEquals(-1, sb.lastIndexOf(StringMatcherFactory.INSTANCE.charMatcher('a'), 0));
+        assertEquals(-1, sb.lastIndexOf(StringMatcherFactory.INSTANCE.charMatcher('a'), -1));
+
+        sb.append("ab bd");
+        assertEquals(-1, sb.lastIndexOf(StringMatcherFactory.INSTANCE.charMatcher('a'), -2));
+        assertEquals(0, sb.lastIndexOf(StringMatcherFactory.INSTANCE.charMatcher('a'), 0));
+        assertEquals(0, sb.lastIndexOf(StringMatcherFactory.INSTANCE.charMatcher('a'), 2));
+        assertEquals(0, sb.lastIndexOf(StringMatcherFactory.INSTANCE.charMatcher('a'), 20));
+
+        assertEquals(-1, sb.lastIndexOf(StringMatcherFactory.INSTANCE.charMatcher('b'), -1));
+        assertEquals(-1, sb.lastIndexOf(StringMatcherFactory.INSTANCE.charMatcher('b'), 0));
+        assertEquals(1, sb.lastIndexOf(StringMatcherFactory.INSTANCE.charMatcher('b'), 1));
+        assertEquals(1, sb.lastIndexOf(StringMatcherFactory.INSTANCE.charMatcher('b'), 2));
+        assertEquals(3, sb.lastIndexOf(StringMatcherFactory.INSTANCE.charMatcher('b'), 3));
+        assertEquals(3, sb.lastIndexOf(StringMatcherFactory.INSTANCE.charMatcher('b'), 4));
+        assertEquals(3, sb.lastIndexOf(StringMatcherFactory.INSTANCE.charMatcher('b'), 5));
+        assertEquals(3, sb.lastIndexOf(StringMatcherFactory.INSTANCE.charMatcher('b'), 6));
+
+        assertEquals(-1, sb.lastIndexOf(StringMatcherFactory.INSTANCE.spaceMatcher(), -2));
+        assertEquals(-1, sb.lastIndexOf(StringMatcherFactory.INSTANCE.spaceMatcher(), 0));
+        assertEquals(2, sb.lastIndexOf(StringMatcherFactory.INSTANCE.spaceMatcher(), 2));
+        assertEquals(2, sb.lastIndexOf(StringMatcherFactory.INSTANCE.spaceMatcher(), 4));
+        assertEquals(2, sb.lastIndexOf(StringMatcherFactory.INSTANCE.spaceMatcher(), 20));
+
+        assertEquals(-1, sb.lastIndexOf(StringMatcherFactory.INSTANCE.noneMatcher(), 0));
+        assertEquals(-1, sb.lastIndexOf((StringMatcher) 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 StringMatcher A_NUMBER_MATCHER = new StringMatcher() {
+
+        @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 TextStringBuilder b = new TextStringBuilder();
+        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 TextStringBuilder sb = new TextStringBuilder("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 TextStringBuilder sb = new TextStringBuilder("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 TextStringBuilder sb1 = new TextStringBuilder();
+        final TextStringBuilder sb2 = new TextStringBuilder();
+        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 TextStringBuilder sb1 = new TextStringBuilder();
+        final TextStringBuilder sb2 = new TextStringBuilder();
+        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_EqualsWithNullTextStringBuilder() throws Exception {
+        final TextStringBuilder sb = new TextStringBuilder();
+        final TextStringBuilder other = null;
+        assertFalse(sb.equals(other));
+    }
+
+    // -----------------------------------------------------------------------
+    @Test
+    public void testHashCode() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        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 TextStringBuilder sb = new TextStringBuilder("abc");
+        assertEquals("abc", sb.toString());
+    }
+
+    // -----------------------------------------------------------------------
+    @Test
+    public void testToStringBuffer() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        assertEquals(new StringBuffer().toString(), sb.toStringBuffer().toString());
+
+        sb.append("junit");
+        assertEquals(new StringBuffer("junit").toString(), sb.toStringBuffer().toString());
+    }
+
+    // -----------------------------------------------------------------------
+    @Test
+    public void testToStringBuilder() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        assertEquals(new StringBuilder().toString(), sb.toStringBuilder().toString());
+
+        sb.append("junit");
+        assertEquals(new StringBuilder("junit").toString(), sb.toStringBuilder().toString());
+    }
+
+    // -----------------------------------------------------------------------
+    @Test
+    public void testLang294() {
+        final TextStringBuilder sb = new TextStringBuilder("\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 TextStringBuilder sb = new TextStringBuilder("onetwothree");
+        sb.deleteFirst("three");
+        assertEquals(-1, sb.indexOf("three"));
+    }
+
+    // -----------------------------------------------------------------------
+    @Test
+    public void testLang295() {
+        final TextStringBuilder sb = new TextStringBuilder("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 TextStringBuilder sb = new TextStringBuilder();
+        sb.appendFixedWidthPadRight(null, 10, '*');
+        assertEquals("Failed to invoke appendFixedWidthPadRight correctly", "**********", sb.toString());
+    }
+
+    @Test
+    public void testLang412Left() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        sb.appendFixedWidthPadLeft(null, 10, '*');
+        assertEquals("Failed to invoke appendFixedWidthPadLeft correctly", "**********", sb.toString());
+    }
+
+    @Test
+    public void testAsBuilder() {
+        final TextStringBuilder sb = new TextStringBuilder().appendAll("Lorem", " ", "ipsum", " ", "dolor");
+        assertEquals(sb.toString(), sb.build());
+    }
+
+    // -----------------------------------------------------------------------
+    @Test
+    public void testAppendCharBuffer() {
+        final TextStringBuilder sb1 = new TextStringBuilder();
+        final CharBuffer buf = CharBuffer.allocate(10);
+        buf.append("0123456789");
+        buf.flip();
+        sb1.append(buf);
+        assertEquals("0123456789", sb1.toString());
+
+        final TextStringBuilder sb2 = new TextStringBuilder();
+        sb2.append(buf, 1, 8);
+        assertEquals("12345678", sb2.toString());
+    }
+
+    // -----------------------------------------------------------------------
+    @Test
+    public void testAppendToWriter() throws Exception {
+        final TextStringBuilder sb = new TextStringBuilder("1234567890");
+        final StringWriter writer = new StringWriter();
+        writer.append("Test ");
+
+        sb.appendTo(writer);
+
+        assertEquals("Test 1234567890", writer.toString());
+    }
+
+    @Test
+    public void testAppendToStringBuilder() throws Exception {
+        final TextStringBuilder sb = new TextStringBuilder("1234567890");
+        final StringBuilder builder = new StringBuilder("Test ");
+
+        sb.appendTo(builder);
+
+        assertEquals("Test 1234567890", builder.toString());
+    }
+
+    @Test
+    public void testAppendToStringBuffer() throws Exception {
+        final TextStringBuilder sb = new TextStringBuilder("1234567890");
+        final StringBuffer buffer = new StringBuffer("Test ");
+
+        sb.appendTo(buffer);
+
+        assertEquals("Test 1234567890", buffer.toString());
+    }
+
+    @Test
+    public void testAppendToCharBuffer() throws Exception {
+        final TextStringBuilder sb = new TextStringBuilder("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());
+    }
+
+    @Test
+    public void testAppendCharBufferNull() throws Exception {
+        final TextStringBuilder sb = new TextStringBuilder("1234567890");
+        final CharBuffer buffer = null;
+        sb.append(buffer);
+        assertEquals("1234567890", sb.toString());
+
+        final TextStringBuilder sb1 = new TextStringBuilder("1234567890");
+        final CharBuffer buffer1 = null;
+        sb.append(buffer1, 0, 0);
+        assertEquals("1234567890", sb1.toString());
+    }
+
+    @Test
+    public void testAppendCharBufferException() throws Exception {
+        final TextStringBuilder sb = new TextStringBuilder("1234567890");
+        final String text = "Test";
+        final CharBuffer buffer = CharBuffer.allocate(sb.size() + text.length());
+        buffer.put(text);
+        buffer.flip();
+        try {
+            sb.append(buffer, -1, 12);
+        } catch (final StringIndexOutOfBoundsException e) {
+            assertEquals("startIndex must be valid", e.getMessage());
+        }
+
+        try {
+            sb.append(buffer, 0, -1);
+        } catch (final StringIndexOutOfBoundsException e) {
+            assertEquals("length must be valid", e.getMessage());
+        }
+
+        sb.append(buffer);
+        assertEquals("1234567890Test", sb.toString());
+    }
+
+    @Test
+    public void testAppendCharSequence() {
+        final CharSequence obj0 = null;
+        final CharSequence obj1 = new TextStringBuilder("test1");
+        final CharSequence obj2 = new StringBuilder("test2");
+        final CharSequence obj3 = new StringBuffer("test3");
+        final CharBuffer obj4 = CharBuffer.wrap("test4".toCharArray());
+
+        final TextStringBuilder sb0 = new TextStringBuilder();
+        assertEquals("", sb0.append(obj0).toString());
+
+        final TextStringBuilder sb1 = new TextStringBuilder();
+        assertEquals("test1", sb1.append(obj1).toString());
+
+        final TextStringBuilder sb2 = new TextStringBuilder();
+        assertEquals("test2", sb2.append(obj2).toString());
+
+        final TextStringBuilder sb3 = new TextStringBuilder();
+        assertEquals("test3", sb3.append(obj3).toString());
+
+        final TextStringBuilder sb4 = new TextStringBuilder();
+        assertEquals("test4", sb4.append(obj4).toString());
+
+        final TextStringBuilder sb5 = new TextStringBuilder();
+        assertEquals("", sb5.append(obj0, 0, 0).toString());
+    }
+
+    @Test
+    public void testAppendStringBuilderNull() {
+        final TextStringBuilder sb1 = new TextStringBuilder();
+        final StringBuilder b = null;
+        assertEquals("", sb1.append(b).toString());
+
+        final TextStringBuilder sb2 = new TextStringBuilder();
+        assertEquals("", sb2.append(b, 0, 0).toString());
+    }
+
+    @Test
+    public void testAppendln() {
+        final TextStringBuilder sb1 = new TextStringBuilder();
+        final char ch = 'c';
+        assertEquals("c" + System.lineSeparator(), sb1.appendln(ch).toString());
+    }
+
+    @Test(expected = StringIndexOutOfBoundsException.class)
+    public void testAppendTakingTwoIntsWithZeroThrowsStringIndexOutOfBoundsException() {
+        final Charset charset = Charset.defaultCharset();
+        final ByteBuffer byteBuffer = charset.encode("end < start");
+        final CharBuffer charBuffer = charset.decode(byteBuffer);
+
+        new TextStringBuilder(630).append(charBuffer, 0, 630);
+    }
+
+    @Test(expected = StringIndexOutOfBoundsException.class)
+    public void testAppendTakingTwoIntsWithIndexOutOfBoundsThrowsStringIndexOutOfBoundsExceptionTwo() {
+        final Charset charset = Charset.defaultCharset();
+        final ByteBuffer byteBuffer = charset.encode("asdf");
+        final CharBuffer charBuffer = charset.decode(byteBuffer);
+
+        new TextStringBuilder().append(charBuffer, 933, 654);
+    }
+
+    @Test(expected = StringIndexOutOfBoundsException.class)
+    public void testDeleteCharAtWithNegative() {
+        new TextStringBuilder().deleteCharAt((-1258));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/commons-text/blob/978e2896/src/test/java/org/apache/commons/text/similarity/LevenshteinDetailedDistanceTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/text/similarity/LevenshteinDetailedDistanceTest.java b/src/test/java/org/apache/commons/text/similarity/LevenshteinDetailedDistanceTest.java
index a1a927c..1d1c2ca 100644
--- a/src/test/java/org/apache/commons/text/similarity/LevenshteinDetailedDistanceTest.java
+++ b/src/test/java/org/apache/commons/text/similarity/LevenshteinDetailedDistanceTest.java
@@ -18,7 +18,7 @@ package org.apache.commons.text.similarity;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
-import org.apache.commons.text.StrBuilder;
+import org.apache.commons.text.TextStringBuilder;
 import org.junit.Test;
 
 public class LevenshteinDetailedDistanceTest {
@@ -439,7 +439,7 @@ public class LevenshteinDetailedDistanceTest {
     @Test(expected = IllegalArgumentException.class)
     public void testApplyThrowsIllegalArgumentExceptionAndCreatesLevenshteinDetailedDistanceTakingInteger() {
         final LevenshteinDetailedDistance levenshteinDetailedDistance = new LevenshteinDetailedDistance(0);
-        final CharSequence charSequence = new StrBuilder();
+        final CharSequence charSequence = new TextStringBuilder();
 
         levenshteinDetailedDistance.apply(charSequence, null);
     }


[2/4] [text] [TEXT-115] Add a StrBuilder replacement based on the StringMatcher interface: TextStringBuilder.

Posted by gg...@apache.org.
http://git-wip-us.apache.org/repos/asf/commons-text/blob/978e2896/src/test/java/org/apache/commons/text/StringSubstitutorTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/text/StringSubstitutorTest.java b/src/test/java/org/apache/commons/text/StringSubstitutorTest.java
index 6f62fe8..e389342 100644
--- a/src/test/java/org/apache/commons/text/StringSubstitutorTest.java
+++ b/src/test/java/org/apache/commons/text/StringSubstitutorTest.java
@@ -22,6 +22,7 @@ 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 java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
@@ -52,16 +53,16 @@ public class StringSubstitutorTest {
             assertNull(sub.replace((char[]) null, 0, 100));
             assertNull(sub.replace((StringBuffer) null));
             assertNull(sub.replace((StringBuffer) null, 0, 100));
-            assertNull(sub.replace((StrBuilder) null));
-            assertNull(sub.replace((StrBuilder) null, 0, 100));
+            assertNull(sub.replace((TextStringBuilder) null));
+            assertNull(sub.replace((TextStringBuilder) null, 0, 100));
             assertNull(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));
+            assertFalse(sub.replaceIn((TextStringBuilder) null));
+            assertFalse(sub.replaceIn((TextStringBuilder) null, 0, 100));
         } else {
             assertEquals(replaceTemplate, sub.replace(replaceTemplate));
-            final StrBuilder bld = new StrBuilder(replaceTemplate);
+            final TextStringBuilder bld = new TextStringBuilder(replaceTemplate);
             assertFalse(sub.replaceIn(bld));
             assertEquals(replaceTemplate, bld.toString());
         }
@@ -72,7 +73,7 @@ public class StringSubstitutorTest {
         doTestReplace(sub, expectedResult, replaceTemplate, substring);
     }
 
-    //-----------------------------------------------------------------------
+    // -----------------------------------------------------------------------
     private void doTestReplace(final StringSubstitutor sub, final String expectedResult, final String replaceTemplate,
             final boolean substring) {
         final String expectedShortResult = expectedResult.substring(1, expectedResult.length() - 1);
@@ -104,15 +105,15 @@ public class StringSubstitutorTest {
             assertEquals(expectedShortResult, sub.replace(builder, 1, builder.length() - 2));
         }
 
-        // replace using StrBuilder
-        StrBuilder bld = new StrBuilder(replaceTemplate);
+        // replace using TextStringBuilder
+        TextStringBuilder bld = new TextStringBuilder(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
+        final MutableObject<String> obj = new MutableObject<>(replaceTemplate); // toString returns template
         assertEquals(expectedResult, sub.replace(obj));
 
         // replace in StringBuffer
@@ -122,7 +123,7 @@ public class StringSubstitutorTest {
         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
+            assertEquals(expectedResult, buf.toString()); // expect full result as remainder is untouched
         }
 
         // replace in StringBuilder
@@ -132,17 +133,17 @@ public class StringSubstitutorTest {
         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
+            assertEquals(expectedResult, builder.toString()); // expect full result as remainder is untouched
         }
 
-        // replace in StrBuilder
-        bld = new StrBuilder(replaceTemplate);
+        // replace in TextStringBuilder
+        bld = new TextStringBuilder(replaceTemplate);
         assertTrue(sub.replaceIn(bld));
         assertEquals(expectedResult, bld.toString());
         if (substring) {
-            bld = new StrBuilder(replaceTemplate);
+            bld = new TextStringBuilder(replaceTemplate);
             assertTrue(sub.replaceIn(bld, 1, bld.length() - 2));
-            assertEquals(expectedResult, bld.toString());  // expect full result as remainder is untouched
+            assertEquals(expectedResult, bld.toString()); // expect full result as remainder is untouched
         }
     }
 
@@ -158,7 +159,7 @@ public class StringSubstitutorTest {
         values = null;
     }
 
-    //-----------------------------------------------------------------------
+    // -----------------------------------------------------------------------
     /**
      * Tests get set.
      */
@@ -220,8 +221,8 @@ public class StringSubstitutorTest {
      */
     @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.", "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);
     }
@@ -261,8 +262,8 @@ public class StringSubstitutorTest {
 
     @Test
     public void testReplaceInTakingStringBufferWithNonNull() {
-        final StringSubstitutor strSubstitutor =
-                new StringSubstitutor(new HashMap<String, String>(), "WV@i#y?N*[", "WV@i#y?N*[", '*');
+        final StringSubstitutor strSubstitutor = new StringSubstitutor(new HashMap<String, String>(), "WV@i#y?N*[",
+                "WV@i#y?N*[", '*');
 
         assertFalse(strSubstitutor.isPreserveEscapes());
         assertFalse(strSubstitutor.replaceIn(new StringBuffer("WV@i#y?N*[")));
@@ -292,8 +293,8 @@ public class StringSubstitutorTest {
         final Map<String, Object> hashMap = new HashMap<>();
         final StringLookup mapStringLookup = StringLookupFactory.INSTANCE.mapStringLookup(hashMap);
         final StringMatcher strMatcher = StringMatcherFactory.INSTANCE.tabMatcher();
-        final StringSubstitutor strSubstitutor =
-                new StringSubstitutor(mapStringLookup, strMatcher, strMatcher, 'b', strMatcher);
+        final StringSubstitutor strSubstitutor = new StringSubstitutor(mapStringLookup, strMatcher, strMatcher, 'b',
+                strMatcher);
 
         assertFalse(strSubstitutor.replaceIn((StringBuilder) null, 1315, (-1369)));
         assertEquals('b', strSubstitutor.getEscapeChar());
@@ -310,20 +311,13 @@ public class StringSubstitutorTest {
         values.put("species", "2");
         final StringSubstitutor sub = new StringSubstitutor(values);
         sub.setEnableSubstitutionInVariables(true);
-        assertEquals(
-                "Wrong result (1)",
-                "The mouse jumps over the lazy dog.",
+        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.",
+        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}."));
+        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}."));
     }
 
     /**
@@ -335,13 +329,9 @@ public class StringSubstitutorTest {
         values.put("animal.2", "mouse");
         values.put("species", "2");
         final StringSubstitutor sub = new StringSubstitutor(values);
-        assertEquals(
-                "Wrong result (1)",
-                "The ${animal.${species}} jumps over the lazy dog.",
+        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.",
+        assertEquals("Wrong result (2)", "The ${animal.${species:-1}} jumps over the lazy dog.",
                 sub.replace("The ${animal.${species:-1}} jumps over the ${target}."));
     }
 
@@ -357,13 +347,9 @@ public class StringSubstitutorTest {
         values.put("species.brown", "2");
         final StringSubstitutor sub = new StringSubstitutor(values);
         sub.setEnableSubstitutionInVariables(true);
-        assertEquals(
-                "Wrong result (1)",
-                "The white mouse jumps over the lazy dog.",
+        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.",
+        assertEquals("Wrong result (2)", "The brown fox jumps over the lazy dog.",
                 sub.replace("The ${animal.${species.${unknownColor:-brown}}} jumps over the ${target}."));
     }
 
@@ -436,7 +422,7 @@ public class StringSubstitutorTest {
         doTestReplace("The quick brown fox jumps over the lazy dog.", "The ${animal} jumps over the ${target}.", true);
     }
 
-    //-----------------------------------------------------------------------
+    // -----------------------------------------------------------------------
     /**
      * Tests simple key replace.
      */
@@ -519,18 +505,18 @@ public class StringSubstitutorTest {
         doTestNoReplace("${${ }}");
     }
 
-    //-----------------------------------------------------------------------
+    // -----------------------------------------------------------------------
     /**
      * Tests protected.
      */
     @Test
     public void testResolveVariable() {
-        final StrBuilder builder = new StrBuilder("Hi ${name}!");
+        final TextStringBuilder builder = new TextStringBuilder("Hi ${name}!");
         final Map<String, String> map = new HashMap<>();
         map.put("name", "commons");
         final StringSubstitutor sub = new StringSubstitutor(map) {
             @Override
-            protected String resolveVariable(final String variableName, final StrBuilder buf, final int startPos,
+            protected String resolveVariable(final String variableName, final TextStringBuilder buf, final int startPos,
                     final int endPos) {
                 assertEquals("name", variableName);
                 assertSame(builder, buf);
@@ -553,7 +539,7 @@ public class StringSubstitutorTest {
         assertEquals("Hello there commons!", StringSubstitutor.replace("@greeting@ there @name@!", map, "@", "@"));
     }
 
-    //-----------------------------------------------------------------------
+    // -----------------------------------------------------------------------
     /**
      * Tests static.
      */
@@ -579,15 +565,14 @@ public class StringSubstitutorTest {
      */
     @Test
     public void testStaticReplaceSystemProperties() {
-        final StrBuilder buf = new StrBuilder();
+        final TextStringBuilder buf = new TextStringBuilder();
         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(), StringSubstitutor.replaceSystemProperties("Hi ${user.name}, you are "
-            + "working with ${os.name}, your home "
-            + "directory is ${user.home}."));
+        assertEquals(buf.toString(), StringSubstitutor.replaceSystemProperties(
+                "Hi ${user.name}, you are " + "working with ${os.name}, your home " + "directory is ${user.home}."));
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/commons-text/blob/978e2896/src/test/java/org/apache/commons/text/TextStringBuilderAppendInsertTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/text/TextStringBuilderAppendInsertTest.java b/src/test/java/org/apache/commons/text/TextStringBuilderAppendInsertTest.java
new file mode 100644
index 0000000..9a944c7
--- /dev/null
+++ b/src/test/java/org/apache/commons/text/TextStringBuilderAppendInsertTest.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 static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
+
+import java.text.DecimalFormatSymbols;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link TextStringBuilder}.
+ */
+public class TextStringBuilderAppendInsertTest {
+
+    /** The system line separator. */
+    private static final String SEP = System.lineSeparator();
+
+    /** 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() {
+        TextStringBuilder sb = new TextStringBuilder("---");
+        sb.appendNewLine().append("+++");
+        assertThat(sb.toString()).isEqualTo("---" + SEP + "+++");
+
+        sb = new TextStringBuilder("---");
+        sb.setNewLineText("#").appendNewLine().setNewLineText(null).appendNewLine();
+        assertThat(sb.toString()).isEqualTo("---#" + SEP);
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppendWithNullText() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        sb.setNullText("NULL");
+        assertThat(sb.toString()).isEqualTo("");
+
+        sb.appendNull();
+        assertThat(sb.toString()).isEqualTo("NULL");
+
+        sb.append((Object) null);
+        assertThat(sb.toString()).isEqualTo("NULLNULL");
+
+        sb.append(FOO);
+        assertThat(sb.toString()).isEqualTo("NULLNULLfoo");
+
+        sb.append((String) null);
+        assertThat(sb.toString()).isEqualTo("NULLNULLfooNULL");
+
+        sb.append("");
+        assertThat(sb.toString()).isEqualTo("NULLNULLfooNULL");
+
+        sb.append("bar");
+        assertThat(sb.toString()).isEqualTo("NULLNULLfooNULLbar");
+
+        sb.append((StringBuffer) null);
+        assertThat(sb.toString()).isEqualTo("NULLNULLfooNULLbarNULL");
+
+        sb.append(new StringBuffer("baz"));
+        assertThat(sb.toString()).isEqualTo("NULLNULLfooNULLbarNULLbaz");
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppend_Object() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        sb.appendNull();
+        assertThat(sb.toString()).isEqualTo("");
+
+        sb.append((Object) null);
+        assertThat(sb.toString()).isEqualTo("");
+
+        sb.append(FOO);
+        assertThat(sb.toString()).isEqualTo("foo");
+
+        sb.append((StringBuffer) null);
+        assertThat(sb.toString()).isEqualTo("foo");
+
+        sb.append(new StringBuffer("baz"));
+        assertThat(sb.toString()).isEqualTo("foobaz");
+
+        sb.append(new TextStringBuilder("yes"));
+        assertThat(sb.toString()).isEqualTo("foobazyes");
+
+        sb.append((CharSequence) "Seq");
+        assertThat(sb.toString()).isEqualTo("foobazyesSeq");
+
+        sb.append(new StringBuilder("bld")); // Check it supports StringBuilder
+        assertThat(sb.toString()).isEqualTo("foobazyesSeqbld");
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppend_StringBuilder() {
+        TextStringBuilder sb = new TextStringBuilder();
+        sb.setNullText("NULL").append((String) null);
+        assertThat(sb.toString()).isEqualTo("NULL");
+
+        sb = new TextStringBuilder();
+        sb.append(new StringBuilder("foo"));
+        assertThat(sb.toString()).isEqualTo("foo");
+
+        sb.append(new StringBuilder(""));
+        assertThat(sb.toString()).isEqualTo("foo");
+
+        sb.append(new StringBuilder("bar"));
+        assertThat(sb.toString()).isEqualTo("foobar");
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppend_String() {
+        TextStringBuilder sb = new TextStringBuilder();
+        sb.setNullText("NULL").append((String) null);
+        assertThat(sb.toString()).isEqualTo("NULL");
+
+        sb = new TextStringBuilder();
+        sb.append("foo");
+        assertThat(sb.toString()).isEqualTo("foo");
+
+        sb.append("");
+        assertThat(sb.toString()).isEqualTo("foo");
+
+        sb.append("bar");
+        assertThat(sb.toString()).isEqualTo("foobar");
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppend_String_int_int() {
+        TextStringBuilder sb = new TextStringBuilder();
+        sb.setNullText("NULL").append((String) null, 0, 1);
+        assertThat(sb.toString()).isEqualTo("NULL");
+
+        sb = new TextStringBuilder();
+        sb.append("foo", 0, 3);
+        assertThat(sb.toString()).isEqualTo("foo");
+
+        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);
+        assertThat(sb.toString()).isEqualTo("foo");
+
+        sb.append("abcbardef", 3, 3);
+        assertThat(sb.toString()).isEqualTo("foobar");
+
+        sb.append((CharSequence) "abcbardef", 4, 3);
+        assertThat(sb.toString()).isEqualTo("foobarard");
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppend_StringBuilder_int_int() {
+        TextStringBuilder sb = new TextStringBuilder();
+        sb.setNullText("NULL").append((String) null, 0, 1);
+        assertThat(sb.toString()).isEqualTo("NULL");
+
+        sb = new TextStringBuilder();
+        sb.append(new StringBuilder("foo"), 0, 3);
+        assertThat(sb.toString()).isEqualTo("foo");
+
+        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);
+        assertThat(sb.toString()).isEqualTo("foo");
+
+        sb.append(new StringBuilder("abcbardef"), 3, 3);
+        assertThat(sb.toString()).isEqualTo("foobar");
+
+        sb.append(new StringBuilder("abcbardef"), 4, 3);
+        assertThat(sb.toString()).isEqualTo("foobarard");
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppend_StringBuffer() {
+        TextStringBuilder sb = new TextStringBuilder();
+        sb.setNullText("NULL").append((StringBuffer) null);
+        assertThat(sb.toString()).isEqualTo("NULL");
+
+        sb = new TextStringBuilder();
+        sb.append(new StringBuffer("foo"));
+        assertThat(sb.toString()).isEqualTo("foo");
+
+        sb.append(new StringBuffer(""));
+        assertThat(sb.toString()).isEqualTo("foo");
+
+        sb.append(new StringBuffer("bar"));
+        assertThat(sb.toString()).isEqualTo("foobar");
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppend_StringBuffer_int_int() {
+        TextStringBuilder sb = new TextStringBuilder();
+        sb.setNullText("NULL").append((StringBuffer) null, 0, 1);
+        assertThat(sb.toString()).isEqualTo("NULL");
+
+        sb = new TextStringBuilder();
+        sb.append(new StringBuffer("foo"), 0, 3);
+        assertThat(sb.toString()).isEqualTo("foo");
+
+        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);
+        assertThat(sb.toString()).isEqualTo("foo");
+
+        sb.append(new StringBuffer("abcbardef"), 3, 3);
+        assertThat(sb.toString()).isEqualTo("foobar");
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppend_TextStringBuilder() {
+        TextStringBuilder sb = new TextStringBuilder();
+        sb.setNullText("NULL").append((TextStringBuilder) null);
+        assertThat(sb.toString()).isEqualTo("NULL");
+
+        sb = new TextStringBuilder();
+        sb.append(new TextStringBuilder("foo"));
+        assertThat(sb.toString()).isEqualTo("foo");
+
+        sb.append(new TextStringBuilder(""));
+        assertThat(sb.toString()).isEqualTo("foo");
+
+        sb.append(new TextStringBuilder("bar"));
+        assertThat(sb.toString()).isEqualTo("foobar");
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppend_TextStringBuilder_int_int() {
+        TextStringBuilder sb = new TextStringBuilder();
+        sb.setNullText("NULL").append((TextStringBuilder) null, 0, 1);
+        assertThat(sb.toString()).isEqualTo("NULL");
+
+        sb = new TextStringBuilder();
+        sb.append(new TextStringBuilder("foo"), 0, 3);
+        assertThat(sb.toString()).isEqualTo("foo");
+
+        try {
+            sb.append(new TextStringBuilder("bar"), -1, 1);
+            fail("append(char[], -1,) expected IndexOutOfBoundsException");
+        } catch (final IndexOutOfBoundsException e) {
+            // expected
+        }
+
+        try {
+            sb.append(new TextStringBuilder("bar"), 3, 1);
+            fail("append(char[], 3,) expected IndexOutOfBoundsException");
+        } catch (final IndexOutOfBoundsException e) {
+            // expected
+        }
+
+        try {
+            sb.append(new TextStringBuilder("bar"), 1, -1);
+            fail("append(char[],, -1) expected IndexOutOfBoundsException");
+        } catch (final IndexOutOfBoundsException e) {
+            // expected
+        }
+
+        try {
+            sb.append(new TextStringBuilder("bar"), 1, 3);
+            fail("append(char[], 1, 3) expected IndexOutOfBoundsException");
+        } catch (final IndexOutOfBoundsException e) {
+            // expected
+        }
+
+        try {
+            sb.append(new TextStringBuilder("bar"), -1, 3);
+            fail("append(char[], -1, 3) expected IndexOutOfBoundsException");
+        } catch (final IndexOutOfBoundsException e) {
+            // expected
+        }
+
+        try {
+            sb.append(new TextStringBuilder("bar"), 4, 0);
+            fail("append(char[], 4, 0) expected IndexOutOfBoundsException");
+        } catch (final IndexOutOfBoundsException e) {
+            // expected
+        }
+
+        sb.append(new TextStringBuilder("bar"), 3, 0);
+        assertThat(sb.toString()).isEqualTo("foo");
+
+        sb.append(new TextStringBuilder("abcbardef"), 3, 3);
+        assertThat(sb.toString()).isEqualTo("foobar");
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppend_CharArray() {
+        TextStringBuilder sb = new TextStringBuilder();
+        sb.setNullText("NULL").append((char[]) null);
+        assertThat(sb.toString()).isEqualTo("NULL");
+
+        sb = new TextStringBuilder();
+        sb.append(new char[0]);
+        assertThat(sb.toString()).isEqualTo("");
+
+        sb.append(new char[]{'f', 'o', 'o'});
+        assertThat(sb.toString()).isEqualTo("foo");
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppend_CharArray_int_int() {
+        TextStringBuilder sb = new TextStringBuilder();
+        sb.setNullText("NULL").append((char[]) null, 0, 1);
+        assertThat(sb.toString()).isEqualTo("NULL");
+
+        sb = new TextStringBuilder();
+        sb.append(new char[]{'f', 'o', 'o'}, 0, 3);
+        assertThat(sb.toString()).isEqualTo("foo");
+
+        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);
+        assertThat(sb.toString()).isEqualTo("foo");
+
+        sb.append(new char[]{'a', 'b', 'c', 'b', 'a', 'r', 'd', 'e', 'f'}, 3, 3);
+        assertThat(sb.toString()).isEqualTo("foobar");
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppend_Boolean() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        sb.append(true);
+        assertThat(sb.toString()).isEqualTo("true");
+
+        sb.append(false);
+        assertThat(sb.toString()).isEqualTo("truefalse");
+
+        sb.append('!');
+        assertThat(sb.toString()).isEqualTo("truefalse!");
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppend_PrimitiveNumber() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        sb.append(0);
+        assertThat(sb.toString()).isEqualTo("0");
+
+        sb.append(1L);
+        assertThat(sb.toString()).isEqualTo("01");
+
+        sb.append(2.3f);
+        assertThat(sb.toString()).isEqualTo("012.3");
+
+        sb.append(4.5d);
+        assertThat(sb.toString()).isEqualTo("012.34.5");
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppendln_FormattedString() {
+        final int[] count = new int[2];
+        final TextStringBuilder sb = new TextStringBuilder() {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public TextStringBuilder append(final String str) {
+                count[0]++;
+                return super.append(str);
+            }
+            @Override
+            public TextStringBuilder appendNewLine() {
+                count[1]++;
+                return super.appendNewLine();
+            }
+        };
+        sb.appendln("Hello %s", "Alice");
+        assertThat(sb.toString()).isEqualTo("Hello Alice" + SEP);
+        assertThat(count[0]);  // appendNewLine() calls append(String).isEqualTo(2)
+        assertThat(count[1]).isEqualTo(1);
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppendln_Object() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        sb.appendln((Object) null);
+        assertThat(sb.toString()).isEqualTo("" + SEP);
+
+        sb.appendln(FOO);
+        assertThat(sb.toString()).isEqualTo(SEP + "foo" + SEP);
+
+        sb.appendln(Integer.valueOf(6));
+        assertThat(sb.toString()).isEqualTo(SEP + "foo" + SEP + "6" + SEP);
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppendln_String() {
+        final int[] count = new int[2];
+        final TextStringBuilder sb = new TextStringBuilder() {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public TextStringBuilder append(final String str) {
+                count[0]++;
+                return super.append(str);
+            }
+            @Override
+            public TextStringBuilder appendNewLine() {
+                count[1]++;
+                return super.appendNewLine();
+            }
+        };
+        sb.appendln("foo");
+        assertThat(sb.toString()).isEqualTo("foo" + SEP);
+        assertThat(count[0]);  // appendNewLine() calls append(String).isEqualTo(2)
+        assertThat(count[1]).isEqualTo(1);
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppendln_String_int_int() {
+        final int[] count = new int[2];
+        final TextStringBuilder sb = new TextStringBuilder() {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public TextStringBuilder append(final String str, final int startIndex, final int length) {
+                count[0]++;
+                return super.append(str, startIndex, length);
+            }
+            @Override
+            public TextStringBuilder appendNewLine() {
+                count[1]++;
+                return super.appendNewLine();
+            }
+        };
+        sb.appendln("foo", 0, 3);
+        assertThat(sb.toString()).isEqualTo("foo" + SEP);
+        assertThat(count[0]).isEqualTo(1);
+        assertThat(count[1]).isEqualTo(1);
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppendln_StringBuffer() {
+        final int[] count = new int[2];
+        final TextStringBuilder sb = new TextStringBuilder() {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public TextStringBuilder append(final StringBuffer str) {
+                count[0]++;
+                return super.append(str);
+            }
+            @Override
+            public TextStringBuilder appendNewLine() {
+                count[1]++;
+                return super.appendNewLine();
+            }
+        };
+        sb.appendln(new StringBuffer("foo"));
+        assertThat(sb.toString()).isEqualTo("foo" + SEP);
+        assertThat(count[0]).isEqualTo(1);
+        assertThat(count[1]).isEqualTo(1);
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppendln_StringBuilder() {
+        final int[] count = new int[2];
+        final TextStringBuilder sb = new TextStringBuilder() {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public TextStringBuilder append(final StringBuilder str) {
+                count[0]++;
+                return super.append(str);
+            }
+            @Override
+            public TextStringBuilder appendNewLine() {
+                count[1]++;
+                return super.appendNewLine();
+            }
+        };
+        sb.appendln(new StringBuilder("foo"));
+        assertThat(sb.toString()).isEqualTo("foo" + SEP);
+        assertThat(count[0]).isEqualTo(1);
+        assertThat(count[1]).isEqualTo(1);
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppendln_StringBuffer_int_int() {
+        final int[] count = new int[2];
+        final TextStringBuilder sb = new TextStringBuilder() {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public TextStringBuilder append(final StringBuffer str, final int startIndex, final int length) {
+                count[0]++;
+                return super.append(str, startIndex, length);
+            }
+            @Override
+            public TextStringBuilder appendNewLine() {
+                count[1]++;
+                return super.appendNewLine();
+            }
+        };
+        sb.appendln(new StringBuffer("foo"), 0, 3);
+        assertThat(sb.toString()).isEqualTo("foo" + SEP);
+        assertThat(count[0]).isEqualTo(1);
+        assertThat(count[1]).isEqualTo(1);
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppendln_StringBuilder_int_int() {
+        final int[] count = new int[2];
+        final TextStringBuilder sb = new TextStringBuilder() {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public TextStringBuilder append(final StringBuilder str, final int startIndex, final int length) {
+                count[0]++;
+                return super.append(str, startIndex, length);
+            }
+            @Override
+            public TextStringBuilder appendNewLine() {
+                count[1]++;
+                return super.appendNewLine();
+            }
+        };
+        sb.appendln(new StringBuilder("foo"), 0, 3);
+        assertThat(sb.toString()).isEqualTo("foo" + SEP);
+        assertThat(count[0]).isEqualTo(1);
+        assertThat(count[1]).isEqualTo(1);
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppendln_TextStringBuilder() {
+        final int[] count = new int[2];
+        final TextStringBuilder sb = new TextStringBuilder() {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public TextStringBuilder append(final TextStringBuilder str) {
+                count[0]++;
+                return super.append(str);
+            }
+            @Override
+            public TextStringBuilder appendNewLine() {
+                count[1]++;
+                return super.appendNewLine();
+            }
+        };
+        sb.appendln(new TextStringBuilder("foo"));
+        assertThat(sb.toString()).isEqualTo("foo" + SEP);
+        assertThat(count[0]).isEqualTo(1);
+        assertThat(count[1]).isEqualTo(1);
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppendln_TextStringBuilder_int_int() {
+        final int[] count = new int[2];
+        final TextStringBuilder sb = new TextStringBuilder() {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public TextStringBuilder append(final TextStringBuilder str, final int startIndex, final int length) {
+                count[0]++;
+                return super.append(str, startIndex, length);
+            }
+            @Override
+            public TextStringBuilder appendNewLine() {
+                count[1]++;
+                return super.appendNewLine();
+            }
+        };
+        sb.appendln(new TextStringBuilder("foo"), 0, 3);
+        assertThat(sb.toString()).isEqualTo("foo" + SEP);
+        assertThat(count[0]).isEqualTo(1);
+        assertThat(count[1]).isEqualTo(1);
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppendln_CharArray() {
+        final int[] count = new int[2];
+        final TextStringBuilder sb = new TextStringBuilder() {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public TextStringBuilder append(final char[] str) {
+                count[0]++;
+                return super.append(str);
+            }
+            @Override
+            public TextStringBuilder appendNewLine() {
+                count[1]++;
+                return super.appendNewLine();
+            }
+        };
+        sb.appendln("foo".toCharArray());
+        assertThat(sb.toString()).isEqualTo("foo" + SEP);
+        assertThat(count[0]).isEqualTo(1);
+        assertThat(count[1]).isEqualTo(1);
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppendln_CharArray_int_int() {
+        final int[] count = new int[2];
+        final TextStringBuilder sb = new TextStringBuilder() {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public TextStringBuilder append(final char[] str, final int startIndex, final int length) {
+                count[0]++;
+                return super.append(str, startIndex, length);
+            }
+            @Override
+            public TextStringBuilder appendNewLine() {
+                count[1]++;
+                return super.appendNewLine();
+            }
+        };
+        sb.appendln("foo".toCharArray(), 0, 3);
+        assertThat(sb.toString()).isEqualTo("foo" + SEP);
+        assertThat(count[0]).isEqualTo(1);
+        assertThat(count[1]).isEqualTo(1);
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppendln_Boolean() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        sb.appendln(true);
+        assertThat(sb.toString()).isEqualTo("true" + SEP);
+
+        sb.clear();
+        sb.appendln(false);
+        assertThat(sb.toString()).isEqualTo("false" + SEP);
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppendln_PrimitiveNumber() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        sb.appendln(0);
+        assertThat(sb.toString()).isEqualTo("0" + SEP);
+
+        sb.clear();
+        sb.appendln(1L);
+        assertThat(sb.toString()).isEqualTo("1" + SEP);
+
+        sb.clear();
+        sb.appendln(2.3f);
+        assertThat(sb.toString()).isEqualTo("2.3" + SEP);
+
+        sb.clear();
+        sb.appendln(4.5d);
+        assertThat(sb.toString()).isEqualTo("4.5" + SEP);
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppendPadding() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        sb.append("foo");
+        assertThat(sb.toString()).isEqualTo("foo");
+
+        sb.appendPadding(-1, '-');
+        assertThat(sb.toString()).isEqualTo("foo");
+
+        sb.appendPadding(0, '-');
+        assertThat(sb.toString()).isEqualTo("foo");
+
+        sb.appendPadding(1, '-');
+        assertThat(sb.toString()).isEqualTo("foo-");
+
+        sb.appendPadding(16, '-');
+        assertThat(sb.length()).isEqualTo(20);
+        //            12345678901234567890
+        assertThat(sb.toString()).isEqualTo("foo-----------------");
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppendFixedWidthPadLeft() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        sb.appendFixedWidthPadLeft("foo", -1, '-');
+        assertThat(sb.toString()).isEqualTo("");
+
+        sb.clear();
+        sb.appendFixedWidthPadLeft("foo", 0, '-');
+        assertThat(sb.toString()).isEqualTo("");
+
+        sb.clear();
+        sb.appendFixedWidthPadLeft("foo", 1, '-');
+        assertThat(sb.toString()).isEqualTo("o");
+
+        sb.clear();
+        sb.appendFixedWidthPadLeft("foo", 2, '-');
+        assertThat(sb.toString()).isEqualTo("oo");
+
+        sb.clear();
+        sb.appendFixedWidthPadLeft("foo", 3, '-');
+        assertThat(sb.toString()).isEqualTo("foo");
+
+        sb.clear();
+        sb.appendFixedWidthPadLeft("foo", 4, '-');
+        assertThat(sb.toString()).isEqualTo("-foo");
+
+        sb.clear();
+        sb.appendFixedWidthPadLeft("foo", 10, '-');
+        assertThat(sb.length()).isEqualTo(10);
+        //            1234567890
+        assertThat(sb.toString()).isEqualTo("-------foo");
+
+        sb.clear();
+        sb.setNullText("null");
+        sb.appendFixedWidthPadLeft(null, 5, '-');
+        assertThat(sb.toString()).isEqualTo("-null");
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppendFixedWidthPadLeft_int() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        sb.appendFixedWidthPadLeft(123, -1, '-');
+        assertThat(sb.toString()).isEqualTo("");
+
+        sb.clear();
+        sb.appendFixedWidthPadLeft(123, 0, '-');
+        assertThat(sb.toString()).isEqualTo("");
+
+        sb.clear();
+        sb.appendFixedWidthPadLeft(123, 1, '-');
+        assertThat(sb.toString()).isEqualTo("3");
+
+        sb.clear();
+        sb.appendFixedWidthPadLeft(123, 2, '-');
+        assertThat(sb.toString()).isEqualTo("23");
+
+        sb.clear();
+        sb.appendFixedWidthPadLeft(123, 3, '-');
+        assertThat(sb.toString()).isEqualTo("123");
+
+        sb.clear();
+        sb.appendFixedWidthPadLeft(123, 4, '-');
+        assertThat(sb.toString()).isEqualTo("-123");
+
+        sb.clear();
+        sb.appendFixedWidthPadLeft(123, 10, '-');
+        assertThat(sb.length()).isEqualTo(10);
+        //            1234567890
+        assertThat(sb.toString()).isEqualTo("-------123");
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppendFixedWidthPadRight() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        sb.appendFixedWidthPadRight("foo", -1, '-');
+        assertThat(sb.toString()).isEqualTo("");
+
+        sb.clear();
+        sb.appendFixedWidthPadRight("foo", 0, '-');
+        assertThat(sb.toString()).isEqualTo("");
+
+        sb.clear();
+        sb.appendFixedWidthPadRight("foo", 1, '-');
+        assertThat(sb.toString()).isEqualTo("f");
+
+        sb.clear();
+        sb.appendFixedWidthPadRight("foo", 2, '-');
+        assertThat(sb.toString()).isEqualTo("fo");
+
+        sb.clear();
+        sb.appendFixedWidthPadRight("foo", 3, '-');
+        assertThat(sb.toString()).isEqualTo("foo");
+
+        sb.clear();
+        sb.appendFixedWidthPadRight("foo", 4, '-');
+        assertThat(sb.toString()).isEqualTo("foo-");
+
+        sb.clear();
+        sb.appendFixedWidthPadRight("foo", 10, '-');
+        assertThat(sb.length()).isEqualTo(10);
+        //            1234567890
+        assertThat(sb.toString()).isEqualTo("foo-------");
+
+        sb.clear();
+        sb.setNullText("null");
+        sb.appendFixedWidthPadRight(null, 5, '-');
+        assertThat(sb.toString()).isEqualTo("null-");
+    }
+
+    // See: http://issues.apache.org/jira/browse/LANG-299
+    @Test
+    public void testLang299() {
+        final TextStringBuilder sb = new TextStringBuilder(1);
+        sb.appendFixedWidthPadRight("foo", 1, '-');
+        assertThat(sb.toString()).isEqualTo("f");
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppendFixedWidthPadRight_int() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        sb.appendFixedWidthPadRight(123, -1, '-');
+        assertThat(sb.toString()).isEqualTo("");
+
+        sb.clear();
+        sb.appendFixedWidthPadRight(123, 0, '-');
+        assertThat(sb.toString()).isEqualTo("");
+
+        sb.clear();
+        sb.appendFixedWidthPadRight(123, 1, '-');
+        assertThat(sb.toString()).isEqualTo("1");
+
+        sb.clear();
+        sb.appendFixedWidthPadRight(123, 2, '-');
+        assertThat(sb.toString()).isEqualTo("12");
+
+        sb.clear();
+        sb.appendFixedWidthPadRight(123, 3, '-');
+        assertThat(sb.toString()).isEqualTo("123");
+
+        sb.clear();
+        sb.appendFixedWidthPadRight(123, 4, '-');
+        assertThat(sb.toString()).isEqualTo("123-");
+
+        sb.clear();
+        sb.appendFixedWidthPadRight(123, 10, '-');
+        assertThat(sb.length()).isEqualTo(10);
+        //            1234567890
+        assertThat(sb.toString()).isEqualTo("123-------");
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppend_FormattedString() {
+        TextStringBuilder sb;
+
+        sb = new TextStringBuilder();
+        sb.append("Hi", (Object[]) null);
+        assertThat(sb.toString()).isEqualTo("Hi");
+
+        sb = new TextStringBuilder();
+        sb.append("Hi", "Alice");
+        assertThat(sb.toString()).isEqualTo("Hi");
+
+        sb = new TextStringBuilder();
+        sb.append("Hi %s", "Alice");
+        assertThat(sb.toString()).isEqualTo("Hi Alice");
+
+        sb = new TextStringBuilder();
+        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";
+        assertThat(sb.toString()).isEqualTo(expected);
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppendAll_Array() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        sb.appendAll((Object[]) null);
+        assertThat(sb.toString()).isEqualTo("");
+
+        sb.clear();
+        sb.appendAll(new Object[0]);
+        assertThat(sb.toString()).isEqualTo("");
+
+        sb.clear();
+        sb.appendAll(new Object[]{"foo", "bar", "baz"});
+        assertThat(sb.toString()).isEqualTo("foobarbaz");
+
+        sb.clear();
+        sb.appendAll("foo", "bar", "baz");
+        assertThat(sb.toString()).isEqualTo("foobarbaz");
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppendAll_Collection() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        sb.appendAll((Collection<?>) null);
+        assertThat(sb.toString()).isEqualTo("");
+
+        sb.clear();
+        sb.appendAll(Collections.EMPTY_LIST);
+        assertThat(sb.toString()).isEqualTo("");
+
+        sb.clear();
+        sb.appendAll(Arrays.asList(new Object[]{"foo", "bar", "baz"}));
+        assertThat(sb.toString()).isEqualTo("foobarbaz");
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppendAll_Iterator() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        sb.appendAll((Iterator<?>) null);
+        assertThat(sb.toString()).isEqualTo("");
+
+        sb.clear();
+        sb.appendAll(Collections.EMPTY_LIST.iterator());
+        assertThat(sb.toString()).isEqualTo("");
+
+        sb.clear();
+        sb.appendAll(Arrays.asList(new Object[]{"foo", "bar", "baz"}).iterator());
+        assertThat(sb.toString()).isEqualTo("foobarbaz");
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppendWithSeparators_Array() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        sb.appendWithSeparators((Object[]) null, ",");
+        assertThat(sb.toString()).isEqualTo("");
+
+        sb.clear();
+        sb.appendWithSeparators(new Object[0], ",");
+        assertThat(sb.toString()).isEqualTo("");
+
+        sb.clear();
+        sb.appendWithSeparators(new Object[]{"foo", "bar", "baz"}, ",");
+        assertThat(sb.toString()).isEqualTo("foo,bar,baz");
+
+        sb.clear();
+        sb.appendWithSeparators(new Object[]{"foo", "bar", "baz"}, null);
+        assertThat(sb.toString()).isEqualTo("foobarbaz");
+
+        sb.clear();
+        sb.appendWithSeparators(new Object[]{"foo", null, "baz"}, ",");
+        assertThat(sb.toString()).isEqualTo("foo,,baz");
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppendWithSeparators_Collection() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        sb.appendWithSeparators((Collection<?>) null, ",");
+        assertThat(sb.toString()).isEqualTo("");
+
+        sb.clear();
+        sb.appendWithSeparators(Collections.EMPTY_LIST, ",");
+        assertThat(sb.toString()).isEqualTo("");
+
+        sb.clear();
+        sb.appendWithSeparators(Arrays.asList(new Object[]{"foo", "bar", "baz"}), ",");
+        assertThat(sb.toString()).isEqualTo("foo,bar,baz");
+
+        sb.clear();
+        sb.appendWithSeparators(Arrays.asList(new Object[]{"foo", "bar", "baz"}), null);
+        assertThat(sb.toString()).isEqualTo("foobarbaz");
+
+        sb.clear();
+        sb.appendWithSeparators(Arrays.asList(new Object[]{"foo", null, "baz"}), ",");
+        assertThat(sb.toString()).isEqualTo("foo,,baz");
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppendWithSeparators_Iterator() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        sb.appendWithSeparators((Iterator<?>) null, ",");
+        assertThat(sb.toString()).isEqualTo("");
+
+        sb.clear();
+        sb.appendWithSeparators(Collections.EMPTY_LIST.iterator(), ",");
+        assertThat(sb.toString()).isEqualTo("");
+
+        sb.clear();
+        sb.appendWithSeparators(Arrays.asList(new Object[]{"foo", "bar", "baz"}).iterator(), ",");
+        assertThat(sb.toString()).isEqualTo("foo,bar,baz");
+
+        sb.clear();
+        sb.appendWithSeparators(Arrays.asList(new Object[]{"foo", "bar", "baz"}).iterator(), null);
+        assertThat(sb.toString()).isEqualTo("foobarbaz");
+
+        sb.clear();
+        sb.appendWithSeparators(Arrays.asList(new Object[]{"foo", null, "baz"}).iterator(), ",");
+        assertThat(sb.toString()).isEqualTo("foo,,baz");
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppendWithSeparatorsWithNullText() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        sb.setNullText("null");
+        sb.appendWithSeparators(new Object[]{"foo", null, "baz"}, ",");
+        assertThat(sb.toString()).isEqualTo("foo,null,baz");
+
+        sb.clear();
+        sb.appendWithSeparators(Arrays.asList(new Object[]{"foo", null, "baz"}), ",");
+        assertThat(sb.toString()).isEqualTo("foo,null,baz");
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppendSeparator_String() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        sb.appendSeparator(",");  // no effect
+        assertThat(sb.toString()).isEqualTo("");
+        sb.append("foo");
+        assertThat(sb.toString()).isEqualTo("foo");
+        sb.appendSeparator(",");
+        assertThat(sb.toString()).isEqualTo("foo,");
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppendSeparator_String_String() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        final String startSeparator = "order by ";
+        final String standardSeparator = ",";
+        final String foo = "foo";
+        sb.appendSeparator(null, null);
+        assertThat(sb.toString()).isEqualTo("");
+        sb.appendSeparator(standardSeparator, null);
+        assertThat(sb.toString()).isEqualTo("");
+        sb.appendSeparator(standardSeparator, startSeparator);
+        assertThat(sb.toString()).isEqualTo(startSeparator);
+        sb.appendSeparator(null, null);
+        assertThat(sb.toString()).isEqualTo(startSeparator);
+        sb.appendSeparator(null, startSeparator);
+        assertThat(sb.toString()).isEqualTo(startSeparator);
+        sb.append(foo);
+        assertThat(sb.toString()).isEqualTo(startSeparator + foo);
+        sb.appendSeparator(standardSeparator, startSeparator);
+        assertThat(sb.toString()).isEqualTo(startSeparator + foo + standardSeparator);
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppendSeparator_char() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        sb.appendSeparator(',');  // no effect
+        assertThat(sb.toString()).isEqualTo("");
+        sb.append("foo");
+        assertThat(sb.toString()).isEqualTo("foo");
+        sb.appendSeparator(',');
+        assertThat(sb.toString()).isEqualTo("foo,");
+    }
+    @Test
+    public void testAppendSeparator_char_char() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        final char startSeparator = ':';
+        final char standardSeparator = ',';
+        final String foo = "foo";
+        sb.appendSeparator(standardSeparator, startSeparator);  // no effect
+        assertThat(sb.toString()).isEqualTo(String.valueOf(startSeparator));
+        sb.append(foo);
+        assertThat(sb.toString()).isEqualTo(String.valueOf(startSeparator) + foo);
+        sb.appendSeparator(standardSeparator, startSeparator);
+        assertThat(sb.toString()).isEqualTo(String.valueOf(startSeparator) + foo + standardSeparator);
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppendSeparator_String_int() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        sb.appendSeparator(",", 0);  // no effect
+        assertThat(sb.toString()).isEqualTo("");
+        sb.append("foo");
+        assertThat(sb.toString()).isEqualTo("foo");
+        sb.appendSeparator(",", 1);
+        assertThat(sb.toString()).isEqualTo("foo,");
+
+        sb.appendSeparator(",", -1);  // no effect
+        assertThat(sb.toString()).isEqualTo("foo,");
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testAppendSeparator_char_int() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        sb.appendSeparator(',', 0);  // no effect
+        assertThat(sb.toString()).isEqualTo("");
+        sb.append("foo");
+        assertThat(sb.toString()).isEqualTo("foo");
+        sb.appendSeparator(',', 1);
+        assertThat(sb.toString()).isEqualTo("foo,");
+
+        sb.appendSeparator(',', -1);  // no effect
+        assertThat(sb.toString()).isEqualTo("foo,");
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testInsert() {
+
+        final TextStringBuilder sb = new TextStringBuilder();
+        sb.append("barbaz");
+        assertThat(sb.toString()).isEqualTo("barbaz");
+
+        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);
+        assertThat(sb.toString()).isEqualTo("barbaz");
+
+        sb.insert(0, FOO);
+        assertThat(sb.toString()).isEqualTo("foobarbaz");
+
+        sb.clear();
+        sb.append("barbaz");
+        assertThat(sb.toString()).isEqualTo("barbaz");
+
+        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);
+        assertThat(sb.toString()).isEqualTo("barbaz");
+
+        sb.insert(0, "foo");
+        assertThat(sb.toString()).isEqualTo("foobarbaz");
+
+        sb.clear();
+        sb.append("barbaz");
+        assertThat(sb.toString()).isEqualTo("barbaz");
+
+        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);
+        assertThat(sb.toString()).isEqualTo("barbaz");
+
+        sb.insert(0, new char[0]);
+        assertThat(sb.toString()).isEqualTo("barbaz");
+
+        sb.insert(0, new char[]{'f', 'o', 'o'});
+        assertThat(sb.toString()).isEqualTo("foobarbaz");
+
+        sb.clear();
+        sb.append("barbaz");
+        assertThat(sb.toString()).isEqualTo("barbaz");
+
+        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);
+        assertThat(sb.toString()).isEqualTo("barbaz");
+
+        sb.insert(0, new char[0], 0, 0);
+        assertThat(sb.toString()).isEqualTo("barbaz");
+
+        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);
+        assertThat(sb.toString()).isEqualTo("barbaz");
+
+        sb.insert(0, new char[]{'a', 'b', 'c', 'f', 'o', 'o', 'd', 'e', 'f'}, 3, 3);
+        assertThat(sb.toString()).isEqualTo("foobarbaz");
+
+        sb.clear();
+        sb.append("barbaz");
+        assertThat(sb.toString()).isEqualTo("barbaz");
+
+        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);
+        assertThat(sb.toString()).isEqualTo("truebarbaz");
+
+        sb.insert(0, false);
+        assertThat(sb.toString()).isEqualTo("falsetruebarbaz");
+
+        sb.clear();
+        sb.append("barbaz");
+        assertThat(sb.toString()).isEqualTo("barbaz");
+
+        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, '!');
+        assertThat(sb.toString()).isEqualTo("!barbaz");
+
+        sb.clear();
+        sb.append("barbaz");
+        assertThat(sb.toString()).isEqualTo("barbaz");
+
+        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');
+        assertThat(sb.toString()).isEqualTo("0barbaz");
+
+        sb.clear();
+        sb.append("barbaz");
+        assertThat(sb.toString()).isEqualTo("barbaz");
+
+        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);
+        assertThat(sb.toString()).isEqualTo("1barbaz");
+
+        sb.clear();
+        sb.append("barbaz");
+        assertThat(sb.toString()).isEqualTo("barbaz");
+
+        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);
+        assertThat(sb.toString()).isEqualTo("2.3barbaz");
+
+        sb.clear();
+        sb.append("barbaz");
+        assertThat(sb.toString()).isEqualTo("barbaz");
+
+        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);
+        assertThat(sb.toString()).isEqualTo("4.5barbaz");
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void testInsertWithNullText() {
+        final TextStringBuilder sb = new TextStringBuilder();
+        sb.setNullText("null");
+        sb.append("barbaz");
+        assertThat(sb.toString()).isEqualTo("barbaz");
+
+        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);
+        assertThat(sb.toString()).isEqualTo("nullbarbaz");
+
+        sb.insert(0, FOO);
+        assertThat(sb.toString()).isEqualTo("foonullbarbaz");
+
+        sb.clear();
+        sb.append("barbaz");
+        assertThat(sb.toString()).isEqualTo("barbaz");
+
+        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);
+        assertThat(sb.toString()).isEqualTo("nullbarbaz");
+
+        sb.insert(0, "foo");
+        assertThat(sb.toString()).isEqualTo("foonullbarbaz");
+
+        sb.insert(0, (char[]) null);
+        assertThat(sb.toString()).isEqualTo("nullfoonullbarbaz");
+
+        sb.insert(0, (char[]) null, 0, 0);
+        assertThat(sb.toString()).isEqualTo("nullnullfoonullbarbaz");
+    }
+}


[3/4] [text] [TEXT-115] Add a StrBuilder replacement based on the StringMatcher interface: TextStringBuilder.

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

<TRUNCATED>

[4/4] [text] [TEXT-115] Add a StrBuilder replacement based on the StringMatcher interface: TextStringBuilder.

Posted by gg...@apache.org.
[TEXT-115] Add a StrBuilder replacement based on the StringMatcher
interface: TextStringBuilder.

Project: http://git-wip-us.apache.org/repos/asf/commons-text/repo
Commit: http://git-wip-us.apache.org/repos/asf/commons-text/commit/978e2896
Tree: http://git-wip-us.apache.org/repos/asf/commons-text/tree/978e2896
Diff: http://git-wip-us.apache.org/repos/asf/commons-text/diff/978e2896

Branch: refs/heads/master
Commit: 978e2896dbaeeb24eaed566972a09b0653b39f2b
Parents: 0bf361a
Author: Gary Gregory <ga...@gmail.com>
Authored: Mon Feb 12 11:13:48 2018 -0700
Committer: Gary Gregory <ga...@gmail.com>
Committed: Mon Feb 12 11:13:48 2018 -0700

----------------------------------------------------------------------
 checkstyle-suppressions.xml                     |    6 +-
 src/changes/changes.xml                         |    3 +-
 .../org/apache/commons/text/StrBuilder.java     |    2 +
 .../apache/commons/text/StringSubstitutor.java  |   43 +-
 .../apache/commons/text/TextStringBuilder.java  | 3216 ++++++++++++++++++
 .../commons/text/StringSubstitutorTest.java     |   93 +-
 .../text/TextStringBuilderAppendInsertTest.java | 1607 +++++++++
 .../commons/text/TextStringBuilderTest.java     | 2147 ++++++++++++
 .../LevenshteinDetailedDistanceTest.java        |    4 +-
 9 files changed, 7042 insertions(+), 79 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/commons-text/blob/978e2896/checkstyle-suppressions.xml
----------------------------------------------------------------------
diff --git a/checkstyle-suppressions.xml b/checkstyle-suppressions.xml
index be52c13..d93989a 100644
--- a/checkstyle-suppressions.xml
+++ b/checkstyle-suppressions.xml
@@ -30,8 +30,8 @@
   <suppress checks="MagicNumber" files="JaccardDistance.java" lines="0-99999" />
   <suppress checks="MagicNumber" files="JaccardSimilarity.java" lines="0-99999" />
   <suppress checks="MagicNumber" files="JaroWinklerDistance.java" lines="0-99999" />
-  <suppress checks="FileLength" files="StrBuilder.java" lines="0-99999" />
   <suppress checks="MagicNumber" files="StrBuilder.java" lines="0-99999" />
+  <suppress checks="MagicNumber" files="TextStringBuilder.java" lines="0-99999" />
   <suppress checks="MagicNumber" files="StringEscapeUtils.java" lines="0-99999" />
   <suppress checks="MagicNumber" files="StrMatcher.java" lines="0-99999" />
   <suppress checks="MagicNumber" files="NumericEntityEscaper.java" lines="0-99999" />
@@ -44,7 +44,11 @@
   <suppress checks="MethodName" files=".*[/\\]test[/\\].*" />
   <suppress checks="Javadoc" files=".*[/\\]test[/\\].*" />
   <suppress checks="FileLength" files="StrBuilderTest.java" />
+  <suppress checks="FileLength" files="StrBuilder.java" lines="0-99999" />
+  <suppress checks="FileLength" files="TextStringBuilderTest.java" />
+  <suppress checks="FileLength" files="TextStringBuilder.java" lines="0-99999" />
   <suppress checks="MethodLength" files="LevenshteinDetailedDistanceTest.java" />
   <suppress checks="MethodLength" files="StrBuilderAppendInsertTest.java" />
+  <suppress checks="MethodLength" files="TextStringBuilderAppendInsertTest.java" />
   <suppress checks="TodoComment" files="StringEscapeUtilsTest.java" />
 </suppressions>

http://git-wip-us.apache.org/repos/asf/commons-text/blob/978e2896/src/changes/changes.xml
----------------------------------------------------------------------
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 778d77a..59a17cd 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -49,7 +49,8 @@ The <action> type attribute can be add,update,fix,remove.
     <action issue="TEXT-110" type="add" dev="pschumacher">Add Automatic-Module-Name MANIFEST entry for Java 9 compatibility</action>
     <action issue="TEXT-70" type="fix" dev="pschumacher">Build failure with java 9-ea+159</action>
     <action issue="TEXT-113" type="add" dev="ggregory">Add an interpolator string lookup</action>
-    <action issue="TEXT-114" type="add" dev="ggregory">Add a replacement for StrSubstitutor based on interfaces: StringSubstitutor</action>
+    <action issue="TEXT-114" type="add" dev="ggregory">Add a StrSubstitutor replacement based on interfaces: StringSubstitutor</action>
+    <action issue="TEXT-115" type="add" dev="ggregory">Add a StrBuilder replacement based on the StringMatcher interface: TextStringBuilder</action>
   </release>
 
   <release version="1.2" date="2017-12-12" description="Release 1.2">

http://git-wip-us.apache.org/repos/asf/commons-text/blob/978e2896/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
index 79d57c8..8fde14e 100644
--- a/src/main/java/org/apache/commons/text/StrBuilder.java
+++ b/src/main/java/org/apache/commons/text/StrBuilder.java
@@ -65,7 +65,9 @@ import java.util.Objects;
  * </p>
  *
  * @since 1.0
+ * @deprecated Use {@link TextStringBuilder}. This class will be removed in 2.0.
  */
+@Deprecated
 public class StrBuilder implements CharSequence, Appendable, Serializable, Builder<String> {
 
     /**

http://git-wip-us.apache.org/repos/asf/commons-text/blob/978e2896/src/main/java/org/apache/commons/text/StringSubstitutor.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/StringSubstitutor.java b/src/main/java/org/apache/commons/text/StringSubstitutor.java
index 583cd37..30180f0 100644
--- a/src/main/java/org/apache/commons/text/StringSubstitutor.java
+++ b/src/main/java/org/apache/commons/text/StringSubstitutor.java
@@ -466,7 +466,7 @@ public class StringSubstitutor {
         if (!priorVariables.contains(varName)) {
             return;
         }
-        final StrBuilder buf = new StrBuilder(256);
+        final TextStringBuilder buf = new TextStringBuilder(256);
         buf.append("Infinite loop in property interpolation of ");
         buf.append(priorVariables.remove(0));
         buf.append(": ");
@@ -599,7 +599,7 @@ public class StringSubstitutor {
         if (source == null) {
             return null;
         }
-        final StrBuilder buf = new StrBuilder(source.length).append(source);
+        final TextStringBuilder buf = new TextStringBuilder(source.length).append(source);
         substitute(buf, 0, source.length);
         return buf.toString();
     }
@@ -623,7 +623,7 @@ public class StringSubstitutor {
         if (source == null) {
             return null;
         }
-        final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
+        final TextStringBuilder buf = new TextStringBuilder(length).append(source, offset, length);
         substitute(buf, 0, length);
         return buf.toString();
     }
@@ -662,7 +662,7 @@ public class StringSubstitutor {
         if (source == null) {
             return null;
         }
-        final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
+        final TextStringBuilder buf = new TextStringBuilder(length).append(source, offset, length);
         substitute(buf, 0, length);
         return buf.toString();
     }
@@ -680,7 +680,7 @@ public class StringSubstitutor {
         if (source == null) {
             return null;
         }
-        final StrBuilder buf = new StrBuilder().append(source);
+        final TextStringBuilder buf = new TextStringBuilder().append(source);
         substitute(buf, 0, buf.length());
         return buf.toString();
     }
@@ -694,11 +694,11 @@ public class StringSubstitutor {
      *            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) {
+    public String replace(final TextStringBuilder source) {
         if (source == null) {
             return null;
         }
-        final StrBuilder buf = new StrBuilder(source.length()).append(source);
+        final TextStringBuilder buf = new TextStringBuilder(source.length()).append(source);
         substitute(buf, 0, buf.length());
         return buf.toString();
     }
@@ -718,11 +718,11 @@ public class StringSubstitutor {
      *            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) {
+    public String replace(final TextStringBuilder source, final int offset, final int length) {
         if (source == null) {
             return null;
         }
-        final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
+        final TextStringBuilder buf = new TextStringBuilder(length).append(source, offset, length);
         substitute(buf, 0, length);
         return buf.toString();
     }
@@ -740,7 +740,7 @@ public class StringSubstitutor {
         if (source == null) {
             return null;
         }
-        final StrBuilder buf = new StrBuilder(source);
+        final TextStringBuilder buf = new TextStringBuilder(source);
         if (!substitute(buf, 0, source.length())) {
             return source;
         }
@@ -766,7 +766,7 @@ public class StringSubstitutor {
         if (source == null) {
             return null;
         }
-        final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
+        final TextStringBuilder buf = new TextStringBuilder(length).append(source, offset, length);
         if (!substitute(buf, 0, length)) {
             return source.substring(offset, offset + length);
         }
@@ -786,7 +786,7 @@ public class StringSubstitutor {
         if (source == null) {
             return null;
         }
-        final StrBuilder buf = new StrBuilder(source.length()).append(source);
+        final TextStringBuilder buf = new TextStringBuilder(source.length()).append(source);
         substitute(buf, 0, buf.length());
         return buf.toString();
     }
@@ -810,7 +810,7 @@ public class StringSubstitutor {
         if (source == null) {
             return null;
         }
-        final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
+        final TextStringBuilder buf = new TextStringBuilder(length).append(source, offset, length);
         substitute(buf, 0, length);
         return buf.toString();
     }
@@ -824,7 +824,7 @@ public class StringSubstitutor {
      *            the builder to replace in, updated, null returns zero
      * @return true if altered
      */
-    public boolean replaceIn(final StrBuilder source) {
+    public boolean replaceIn(final TextStringBuilder source) {
         if (source == null) {
             return false;
         }
@@ -846,7 +846,7 @@ public class StringSubstitutor {
      *            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) {
+    public boolean replaceIn(final TextStringBuilder source, final int offset, final int length) {
         if (source == null) {
             return false;
         }
@@ -888,7 +888,7 @@ public class StringSubstitutor {
         if (source == null) {
             return false;
         }
-        final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
+        final TextStringBuilder buf = new TextStringBuilder(length).append(source, offset, length);
         if (!substitute(buf, 0, length)) {
             return false;
         }
@@ -931,7 +931,7 @@ public class StringSubstitutor {
         if (source == null) {
             return false;
         }
-        final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
+        final TextStringBuilder buf = new TextStringBuilder(length).append(source, offset, length);
         if (!substitute(buf, 0, length)) {
             return false;
         }
@@ -959,7 +959,7 @@ public class StringSubstitutor {
      *            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,
+    protected String resolveVariable(final String variableName, final TextStringBuilder buf, final int startPos,
             final int endPos) {
         final StringLookup resolver = getStringLookup();
         if (resolver == null) {
@@ -1204,7 +1204,7 @@ public class StringSubstitutor {
      *            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) {
+    protected boolean substitute(final TextStringBuilder buf, final int offset, final int length) {
         return substitute(buf, offset, length, null) > 0;
     }
 
@@ -1223,7 +1223,8 @@ public class StringSubstitutor {
      * @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) {
+    private int substitute(final TextStringBuilder buf, final int offset, final int length,
+            List<String> priorVariables) {
         final StringMatcher pfxMatcher = getVariablePrefixMatcher();
         final StringMatcher suffMatcher = getVariableSuffixMatcher();
         final char escape = getEscapeChar();
@@ -1278,7 +1279,7 @@ public class StringSubstitutor {
                                 String varNameExpr = new String(chars, startPos + startMatchLen,
                                         pos - startPos - startMatchLen);
                                 if (substitutionInVariablesEnabled) {
-                                    final StrBuilder bufName = new StrBuilder(varNameExpr);
+                                    final TextStringBuilder bufName = new TextStringBuilder(varNameExpr);
                                     substitute(bufName, 0, bufName.length());
                                     varNameExpr = bufName.toString();
                                 }