diff --git a/core/src/main/java/pl/tkowalcz/tjahzi/http/TextBuilders.java b/core/src/main/java/pl/tkowalcz/tjahzi/http/TextBuilders.java index 759f708..2b7b0f5 100644 --- a/core/src/main/java/pl/tkowalcz/tjahzi/http/TextBuilders.java +++ b/core/src/main/java/pl/tkowalcz/tjahzi/http/TextBuilders.java @@ -1,7 +1,7 @@ package pl.tkowalcz.tjahzi.http; import io.netty.util.concurrent.FastThreadLocal; -import pl.tkowalcz.tjahzi.javolution.text.TextBuilder; +import pl.tkowalcz.tjahzi.utils.TextBuilder; public class TextBuilders { diff --git a/core/src/main/java/pl/tkowalcz/tjahzi/javolution/text/TextBuilder.java b/core/src/main/java/pl/tkowalcz/tjahzi/javolution/text/TextBuilder.java deleted file mode 100644 index 045ff9a..0000000 --- a/core/src/main/java/pl/tkowalcz/tjahzi/javolution/text/TextBuilder.java +++ /dev/null @@ -1,637 +0,0 @@ -/* - * Javolution - Java(TM) Solution for Real-Time and Embedded Systems - * Copyright (C) 2012 - Javolution (http://javolution.org/) - * All rights reserved. - * - * Permission to use, copy, modify, and distribute this software is - * freely granted, provided that this notice is preserved. - */ -package pl.tkowalcz.tjahzi.javolution.text; - -import java.io.Serializable; - -/** - *
An {@link Appendable} text whose capacity expands - * gently without incurring expensive resize/copy operations ever.
- * - * This class is not intended for large documents manipulations.
- *
- * @author Jean-Marie Dautelle
- * @version 5.3, January 20, 2008
- */
-public class TextBuilder implements Appendable, CharSequence, Serializable {
-
- private static final long serialVersionUID = 0x600L; // Version.
- // We do a full resize (and copy) only when the capacity is less than C1.
- // For large collections, multi-dimensional arrays are employed.
- private static final int B0 = 5; // Initial capacity in bits.
- private static final int C0 = 1 << B0; // Initial capacity (32)
- private static final int B1 = 10; // Low array maximum capacity in bits.
- private static final int C1 = 1 << B1; // Low array maximum capacity (1024).
- private static final int M1 = C1 - 1; // Mask.
- // Resizes up to 1024 maximum (32, 64, 128, 256, 512, 1024).
- private char[] _low = new char[C0];
- // For larger capacity use multi-dimensional array.
- private char[][] _high = new char[1][];
-
- /**
- * Holds the current length.
- */
- private int _length;
-
- /**
- * Holds current capacity.
- */
- private int _capacity = C0;
-
- /**
- * Creates a text builder of small initial capacity.
- */
- public TextBuilder() {
- _high[0] = _low;
- }
-
- /**
- * Creates a text builder holding the specified String
- * (convenience method).
- *
- * @param str the initial string content of this text builder.
- */
- public TextBuilder(String str) {
- this();
- append(str);
- }
-
- /**
- * Creates a text builder of specified initial capacity.
- * Unless the text length exceeds the specified capacity, operations
- * on this text builder will not allocate memory.
- *
- * @param capacity the initial capacity.
- */
- public TextBuilder(int capacity) {
- this();
- while (capacity > _capacity) {
- increaseCapacity();
- }
- }
-
- /**
- * Returns the length (character count) of this text builder.
- *
- * @return the number of characters (16-bits Unicode).
- */
- public final int length() {
- return _length;
- }
-
- /**
- * Returns the character at the specified index.
- *
- * @param index the index of the character.
- * @return the character at the specified index.
- * @throws IndexOutOfBoundsException if
- * (index < 0) || (index >= this.length()).
- */
- public final char charAt(int index) {
- if (index >= _length)
- throw new IndexOutOfBoundsException();
- return index < C1 ? _low[index] : _high[index >> B1][index & M1];
- }
-
- /**
- * Sets the character at the specified position.
- *
- * @param index the index of the character to modify.
- * @param c the new character.
- * @throws IndexOutOfBoundsException if (index < 0) ||
- * (index >= this.length())
- */
- public final void setCharAt(int index, char c) {
- if ((index < 0) || (index >= _length))
- throw new IndexOutOfBoundsException();
- _high[index >> B1][index & M1] = c;
- }
-
- /**
- * Convenience method equivalent to {@link #setLength(int, char)
- * setLength(newLength, '\u0000')}.
- *
- * @param newLength the new length of this builder.
- * @throws IndexOutOfBoundsException if (newLength < 0)
- */
- public final void setLength(int newLength) {
- setLength(newLength, '\u0000');
- }
-
- /**
- * Sets the length of this character builder.
- * If the length is greater than the current length; the
- * specified character is inserted.
- *
- * @param newLength the new length of this builder.
- * @param fillChar the character to be appended if required.
- * @throws IndexOutOfBoundsException if (newLength < 0)
- */
- public final void setLength(int newLength, char fillChar) {
- if (newLength < 0)
- throw new IndexOutOfBoundsException();
- if (newLength <= _length)
- _length = newLength;
- else
- for (int i = _length; i++ < newLength;) {
- append(fillChar);
- }
- }
-
- /**
- * Returns a {@link java.lang.CharSequence} corresponding
- * to the character sequence between the specified indexes.
- *
- * @param start the index of the first character inclusive.
- * @param end the index of the last character exclusive.
- * @return a character sequence.
- * @throws IndexOutOfBoundsException if (start < 0) || (end < 0) ||
- * (start > end) || (end > this.length())
- */
- public final java.lang.CharSequence subSequence(int start, int end) {
- if ((start < 0) || (end < 0) || (start > end) || (end > _length))
- throw new IndexOutOfBoundsException();
- return new String(_low, start, end - start);
- }
-
- /**
- * Appends the specified character.
- *
- * @param c the character to append.
- * @return this
- */
- public final TextBuilder append(char c) {
- if (_length >= _capacity)
- increaseCapacity();
- _high[_length >> B1][_length & M1] = c;
- _length++;
- return this;
- }
-
- /**
- * Appends the specified character sequence. If the specified character
- * sequence is null this method is equivalent to
- * append("null").
- *
- * @param csq the character sequence to append or null.
- * @return this
- */
- public final TextBuilder append(CharSequence csq) {
- return (csq == null) ? append("null") : append(csq, 0, csq.length());
- }
-
- /**
- * Appends a subsequence of the specified character sequence.
- * If the specified character sequence is null this method
- * is equivalent to append("null").
- *
- * @param csq the character sequence to append or null.
- * @param start the index of the first character to append.
- * @param end the index after the last character to append.
- * @return this
- * @throws IndexOutOfBoundsException if (start < 0) || (end < 0)
- * || (start > end) || (end > csq.length())
- */
- public final TextBuilder append(CharSequence csq, int start,
- int end) {
- if (csq == null)
- return append("null");
- if ((start < 0) || (end < 0) || (start > end) || (end > csq.length()))
- throw new IndexOutOfBoundsException();
- for (int i = start; i < end;) {
- append(csq.charAt(i++));
- }
- return this;
- }
-
- /**
- * Appends the specified string to this text builder.
- * If the specified string is null this method
- * is equivalent to append("null").
- *
- * @param str the string to append or null.
- * @return this
- */
- public final TextBuilder append(String str) {
- return (str == null) ? append("null") : append(str, 0, str.length());
- }
-
- /**
- * Appends a subsequence of the specified string.
- * If the specified character sequence is null this method
- * is equivalent to append("null").
- *
- * @param str the string to append or null.
- * @param start the index of the first character to append.
- * @param end the index after the last character to append.
- * @return this
- * @throws IndexOutOfBoundsException if (start < 0) || (end < 0)
- * || (start > end) || (end > str.length())
- */
- public final TextBuilder append(String str, int start, int end) {
- if (str == null)
- return append("null");
- if ((start < 0) || (end < 0) || (start > end) || (end > str.length()))
- throw new IndexOutOfBoundsException("start: " + start + ", end: "
- + end + ", str.length(): " + str.length());
- int newLength = _length + end - start;
- while (_capacity < newLength) {
- increaseCapacity();
- }
- for (int i = start, j = _length; i < end;) {
- char[] chars = _high[j >> B1];
- int dstBegin = j & M1;
- int inc = Math.min(C1 - dstBegin, end - i);
- str.getChars(i, (i += inc), chars, dstBegin);
- j += inc;
- }
- _length = newLength;
- return this;
- }
-
- /**
- * Appends the characters from the char array argument.
- *
- * @param chars the character array source.
- * @return this
- */
- public final TextBuilder append(char chars[]) {
- append(chars, 0, chars.length);
- return this;
- }
-
- /**
- * Appends the characters from a subarray of the char array argument.
- *
- * @param chars the character array source.
- * @param offset the index of the first character to append.
- * @param length the number of character to append.
- * @return this
- * @throws IndexOutOfBoundsException if (offset < 0) ||
- * (length < 0) || ((offset + length) > chars.length)
- */
- public final TextBuilder append(char chars[], int offset, int length) {
- final int end = offset + length;
- if ((offset < 0) || (length < 0) || (end > chars.length))
- throw new IndexOutOfBoundsException();
- int newLength = _length + length;
- while (_capacity < newLength) {
- increaseCapacity();
- }
- for (int i = offset, j = _length; i < end;) {
- char[] dstChars = _high[j >> B1];
- int dstBegin = j & M1;
- int inc = Math.min(C1 - dstBegin, end - i);
- System.arraycopy(chars, i, dstChars, dstBegin, inc);
- i += inc;
- j += inc;
- }
- _length = newLength;
- return this;
- }
-
- /**
- * Appends the textual representation of the specified boolean
- * argument.
- *
- * @param b the boolean to format.
- * @return this
- */
- public final TextBuilder append(boolean b) {
- return b ? append("true") : append("false");
- }
-
- /**
- * Appends the decimal representation of the specified int
- * argument.
- *
- * @param i the int to format.
- * @return this
- */
- public final TextBuilder append(int i) {
- if (i <= 0) {
- if (i == 0)
- return append("0");
- if (i == Integer.MIN_VALUE) // Negation would overflow.
- return append("-2147483648");
- append('-');
- i = -i;
- }
- int digits = digitLength(i);
- if (_capacity < _length + digits)
- increaseCapacity();
- _length += digits;
- for (int index = _length - 1;; index--) {
- int j = i / 10;
- _high[index >> B1][index & M1] = (char) ('0' + i - (j * 10));
- if (j == 0)
- return this;
- i = j;
- }
- }
-
- /**
- * Appends the radix representation of the specified int
- * argument.
- *
- * @param i the int to format.
- * @param radix the radix (e.g. 16 for hexadecimal).
- * @return this
- */
- public final TextBuilder append(int i, int radix) {
- if (radix == 10)
- return append(i); // Faster.
- if (radix < 2 || radix > 36)
- throw new IllegalArgumentException("radix: " + radix);
- if (i < 0) {
- append('-');
- if (i == Integer.MIN_VALUE) { // Negative would overflow.
- appendPositive(-(i / radix), radix);
- return (TextBuilder) append(DIGIT_TO_CHAR[-(i % radix)]);
- }
- i = -i;
- }
- appendPositive(i, radix);
- return this;
- }
-
- private void appendPositive(int l1, int radix) {
- if (l1 >= radix) {
- int l2 = l1 / radix;
- // appendPositive(l2, radix);
- if (l2 >= radix) {
- int l3 = l2 / radix;
- // appendPositive(l3, radix);
- if (l3 >= radix) {
- int l4 = l3 / radix;
- appendPositive(l4, radix);
- append(DIGIT_TO_CHAR[l3 - (l4 * radix)]);
- } else
- append(DIGIT_TO_CHAR[l3]);
- append(DIGIT_TO_CHAR[l2 - (l3 * radix)]);
- } else
- append(DIGIT_TO_CHAR[l2]);
- append(DIGIT_TO_CHAR[l1 - (l2 * radix)]);
- } else
- append(DIGIT_TO_CHAR[l1]);
- }
-
- private final static char[] DIGIT_TO_CHAR = { '0', '1', '2', '3', '4', '5',
- '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
- 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
- 'w', 'x', 'y', 'z' };
-
- /**
- * Appends the decimal representation of the specified long
- * argument.
- *
- * @param l the long to format.
- * @return this
- */
- public final TextBuilder append(long l) {
- if (l <= 0) {
- if (l == 0)
- return append("0");
- if (l == Long.MIN_VALUE) // Negation would overflow.
- return append("-9223372036854775808");
- append('-');
- l = -l;
- }
- if (l <= Integer.MAX_VALUE)
- return append((int) l);
- append(l / 1000000000);
- int i = (int) (l % 1000000000);
- int digits = digitLength(i);
- append("000000000", 0, 9 - digits);
- return append(i);
- }
-
- /**
- * Appends the radix representation of the specified long
- * argument.
- *
- * @param l the long to format.
- * @param radix the radix (e.g. 16 for hexadecimal).
- * @return this
- */
- public final TextBuilder append(long l, int radix) {
- if (radix == 10)
- return append(l); // Faster.
- if (radix < 2 || radix > 36)
- throw new IllegalArgumentException("radix: " + radix);
- if (l < 0) {
- append('-');
- if (l == Long.MIN_VALUE) { // Negative would overflow.
- appendPositive(-(l / radix), radix);
- return (TextBuilder) append(DIGIT_TO_CHAR[(int) -(l % radix)]);
- }
- l = -l;
- }
- appendPositive(l, radix);
- return this;
- }
-
- private void appendPositive(long l1, int radix) {
- if (l1 >= radix) {
- long l2 = l1 / radix;
- // appendPositive(l2, radix);
- if (l2 >= radix) {
- long l3 = l2 / radix;
- // appendPositive(l3, radix);
- if (l3 >= radix) {
- long l4 = l3 / radix;
- appendPositive(l4, radix);
- append(DIGIT_TO_CHAR[(int) (l3 - (l4 * radix))]);
- } else
- append(DIGIT_TO_CHAR[(int) l3]);
- append(DIGIT_TO_CHAR[(int) (l2 - (l3 * radix))]);
- } else
- append(DIGIT_TO_CHAR[(int) l2]);
- append(DIGIT_TO_CHAR[(int) (l1 - (l2 * radix))]);
- } else
- append(DIGIT_TO_CHAR[(int) l1]);
- }
-
- private static final long[] POW10_LONG = new long[] { 1L, 10L, 100L, 1000L,
- 10000L, 100000L, 1000000L, 10000000L, 100000000L, 1000000000L,
- 10000000000L, 100000000000L, 1000000000000L, 10000000000000L,
- 100000000000000L, 1000000000000000L, 10000000000000000L,
- 100000000000000000L, 1000000000000000000L };
-
- /**
- * Inserts the specified character sequence at the specified location.
- *
- * @param index the insertion position.
- * @param csq the character sequence being inserted.
- * @return this
- * @throws IndexOutOfBoundsException if (index < 0) ||
- * (index > this.length())
- */
- public final TextBuilder insert(int index, java.lang.CharSequence csq) {
- if ((index < 0) || (index > _length))
- throw new IndexOutOfBoundsException("index: " + index);
- final int shift = csq.length();
- int newLength = _length + shift;
- while (newLength >= _capacity) {
- increaseCapacity();
- }
- _length = newLength;
- for (int i = _length - shift; --i >= index;) {
- this.setCharAt(i + shift, this.charAt(i));
- }
- for (int i = csq.length(); --i >= 0;) {
- this.setCharAt(index + i, csq.charAt(i));
- }
- return this;
- }
-
- /**
- * Removes all the characters of this text builder
- * (equivalent to this.delete(start, this.length())).
- *
- * @return this.delete(0, this.length())
- */
- public final TextBuilder clear() {
- _length = 0;
- return this;
- }
-
- /**
- * Removes the characters between the specified indices.
- *
- * @param start the beginning index, inclusive.
- * @param end the ending index, exclusive.
- * @return this
- * @throws IndexOutOfBoundsException if (start < 0) || (end < 0)
- * || (start > end) || (end > this.length())
- */
- public final TextBuilder delete(int start, int end) {
- if ((start < 0) || (end < 0) || (start > end) || (end > this.length()))
- throw new IndexOutOfBoundsException();
- for (int i = end, j = start; i < _length;) {
- this.setCharAt(j++, this.charAt(i++));
- }
- _length -= end - start;
- return this;
- }
-
- /**
- * Reverses this character sequence.
- *
- * @return this
- */
- public final TextBuilder reverse() {
- final int n = _length - 1;
- for (int j = (n - 1) >> 1; j >= 0;) {
- char c = charAt(j);
- setCharAt(j, charAt(n - j));
- setCharAt(n - j--, c);
- }
- return this;
- }
-
- /**
- * Returns the hash code for this text builder.
- *
- * @return the hash code value.
- */
- @Override
- public final int hashCode() {
- int h = 0;
- for (int i = 0; i < _length;) {
- h = 31 * h + charAt(i++);
- }
- return h;
- }
-
- /**
- * Compares this text builder against the specified object for equality.
- * Returns true if the specified object is a text builder
- * having the same character content.
- *
- * @param obj the object to compare with or null.
- * @return true if that is a text builder with the same
- * character content as this text; false otherwise.
- */
- @Override
- public final boolean equals(Object obj) {
- if (this == obj)
- return true;
- if (!(obj instanceof TextBuilder))
- return false;
- TextBuilder that = (TextBuilder) obj;
- if (this._length != that._length)
- return false;
- for (int i = 0; i < _length;) {
- if (this.charAt(i) != that.charAt(i++))
- return false;
- }
- return true;
- }
-
- /**
- * Indicates if this text builder has the same character content as the
- * specified character sequence.
- *
- * @param csq the character sequence to compare with.
- * @return true if the specified character sequence has the
- * same character content as this text; false otherwise.
- */
- public final boolean contentEquals(java.lang.CharSequence csq) {
- if (csq.length() != _length)
- return false;
- for (int i = 0; i < _length;) {
- char c = _high[i >> B1][i & M1];
- if (csq.charAt(i++) != c)
- return false;
- }
- return true;
- }
-
- /**
- * Increases this text builder capacity.
- */
- private void increaseCapacity() {
- if (_capacity < C1) { // For small capacity, resize.
- _capacity <<= 1;
- char[] tmp = new char[_capacity];
- System.arraycopy(_low, 0, tmp, 0, _length);
- _low = tmp;
- _high[0] = tmp;
- } else { // Add a new low block of 1024 elements.
- int j = _capacity >> B1;
- if (j >= _high.length) { // Resizes _high.
- char[][] tmp = new char[_high.length * 2][];
- System.arraycopy(_high, 0, tmp, 0, _high.length);
- _high = tmp;
- }
- _high[j] = new char[C1];
- _capacity += C1;
- }
- }
-
- /**
- * Returns the number of digits of the decimal representation of the
- * specified int value, excluding the sign character if any.
- *
- * @param i the int value for which the digit length is returned.
- * @return String.valueOf(i).length() for zero or positive values;
- * String.valueOf(i).length() - 1 for negative values.
- */
- private static int digitLength(int i) {
- if (i >= 0)
- return (i >= 100000) ? (i >= 10000000) ? (i >= 1000000000) ? 10
- : (i >= 100000000) ? 9 : 8 : (i >= 1000000) ? 7 : 6
- : (i >= 100) ? (i >= 10000) ? 5 : (i >= 1000) ? 4 : 3
- : (i >= 10) ? 2 : 1;
- if (i == Integer.MIN_VALUE)
- return 10; // "2147483648".length()
- return digitLength(-i); // No overflow possible.
- }
-}
diff --git a/core/src/main/java/pl/tkowalcz/tjahzi/utils/TextBuilder.java b/core/src/main/java/pl/tkowalcz/tjahzi/utils/TextBuilder.java
new file mode 100644
index 0000000..640618d
--- /dev/null
+++ b/core/src/main/java/pl/tkowalcz/tjahzi/utils/TextBuilder.java
@@ -0,0 +1,84 @@
+package pl.tkowalcz.tjahzi.utils;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+public class TextBuilder implements Appendable, CharSequence {
+
+ private static final int INITIAL_SIZE = 8;
+ private static final int CAPACITY_INCREMENT = 64;
+
+ private char[] chars = new char[INITIAL_SIZE];
+ private int length;
+
+ public TextBuilder clear() {
+ length = 0;
+ return this;
+ }
+
+ public TextBuilder append(CharSequence string) {
+ ensureCapacity(string.length());
+
+ for (int i = 0; i < string.length(); i++) {
+ chars[length++] = string.charAt(i);
+ }
+
+ return this;
+ }
+
+ @Override
+ public TextBuilder append(CharSequence sequence, int start, int end) {
+ Objects.checkFromToIndex(start, end, sequence.length());
+ ensureCapacity(sequence.length());
+
+ for (int i = start; i < end; i++) {
+ chars[length++] = sequence.charAt(i);
+ }
+
+ return this;
+ }
+
+ @Override
+ public TextBuilder append(char c) {
+ ensureCapacity(1);
+ chars[length++] = c;
+ return this;
+ }
+
+ @Override
+ public int length() {
+ return length;
+ }
+
+ @Override
+ public char charAt(int index) {
+ Objects.checkIndex(index, length);
+ return chars[index];
+ }
+
+ @Override
+ public CharSequence subSequence(int start, int end) {
+ Objects.checkFromToIndex(start, end, length);
+ return new String(chars, start, end - start);
+ }
+
+ public TextBuilder setCharAt(int index, char c) {
+ Objects.checkIndex(index, length);
+ chars[index] = c;
+
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return new String(chars, 0, length);
+ }
+
+ private void ensureCapacity(int requiredCapacity) {
+ if (length + requiredCapacity >= chars.length) {
+ int missingCapacity = length + requiredCapacity - chars.length;
+ int expandedSize = chars.length + Math.max(CAPACITY_INCREMENT, missingCapacity);
+ chars = Arrays.copyOf(chars, expandedSize);
+ }
+ }
+}
diff --git a/core/src/test/java/pl/tkowalcz/tjahzi/utils/TextBuilderTest.java b/core/src/test/java/pl/tkowalcz/tjahzi/utils/TextBuilderTest.java
new file mode 100644
index 0000000..32e7a4f
--- /dev/null
+++ b/core/src/test/java/pl/tkowalcz/tjahzi/utils/TextBuilderTest.java
@@ -0,0 +1,212 @@
+package pl.tkowalcz.tjahzi.utils;
+
+import org.apache.commons.lang3.StringUtils;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class TextBuilderTest {
+
+ @Test
+ void shouldTrackLength() {
+ // Given
+ TextBuilder textBuilder = new TextBuilder();
+
+ // When
+ textBuilder.append("abcd");
+ textBuilder.append('f');
+ textBuilder.append("1234567890", 3, 7);
+ textBuilder.setCharAt(3, 'n');
+
+ // Then
+ assertThat(textBuilder.length()).isEqualTo(9);
+ assertThat(textBuilder.toString()).isEqualTo("abcnf4567");
+ }
+
+ @Test
+ void shouldClearBuilder() {
+ // Given
+ TextBuilder textBuilder = new TextBuilder();
+ textBuilder.append("abcd");
+
+ // When
+ textBuilder.clear();
+
+ // Then
+ assertThat(textBuilder.length()).isZero();
+ assertThat(textBuilder.toString()).isEmpty();
+ }
+
+ @Test
+ void shouldSetCharAtIndex() {
+ // Given
+ TextBuilder textBuilder = new TextBuilder();
+ textBuilder.append("1234567890");
+
+ // When
+ textBuilder.setCharAt(3, 'f');
+
+ // Then
+ assertThat(textBuilder.length()).isEqualTo(10);
+ assertThat(textBuilder.toString()).isEqualTo("123f567890");
+ }
+
+ @Test
+ void shouldNotAllowSettingCharAtWrongIndex() {
+ // Given
+ TextBuilder textBuilder = new TextBuilder();
+ textBuilder.append("1234567890");
+
+ // When & Then
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> textBuilder.setCharAt(-10, 'f')
+ );
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> textBuilder.setCharAt(11, 'f')
+ );
+ }
+
+ @Test
+ void shouldReturnCharAt() {
+ // Given
+ TextBuilder textBuilder = new TextBuilder();
+ textBuilder.append("1234567890");
+
+ // When
+ char actual = textBuilder.charAt(6);
+
+ // Then
+ assertThat(actual).isEqualTo('7');
+ }
+
+ @Test
+ void shouldNotAllowReturningCharAtWrongIndex() {
+ // Given
+ TextBuilder textBuilder = new TextBuilder();
+ textBuilder.append("1234567890");
+
+ // When & Then
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> textBuilder.charAt(-10)
+ );
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> textBuilder.charAt(11)
+ );
+ }
+
+ @Test
+ void shouldReturnSubstring() {
+ // Given
+ TextBuilder textBuilder = new TextBuilder();
+ textBuilder.append("1234567890");
+
+ // When
+ CharSequence actual = textBuilder.subSequence(3, 8);
+
+ // Then
+ assertThat(actual).isEqualTo("45678");
+ }
+
+ @Test
+ void shouldVerifySubstringArguments() {
+ // Given
+ TextBuilder textBuilder = new TextBuilder();
+ textBuilder.append("1234567890");
+
+ // When & Then
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> textBuilder.subSequence(-1, 8)
+ );
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> textBuilder.subSequence(50, 8)
+ );
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> textBuilder.subSequence(3, -1)
+ );
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> textBuilder.subSequence(3, 120)
+ );
+
+ assertThrows(
+ IndexOutOfBoundsException.class,
+ () -> textBuilder.subSequence(6, 5)
+ );
+ }
+
+ @Test
+ void shouldReturnStringRepresentation() {
+ // Given
+ TextBuilder textBuilder = new TextBuilder();
+ textBuilder.append("1234567890");
+
+ // When
+ CharSequence actual = textBuilder.toString();
+
+ // Then
+ assertThat(actual).isEqualTo("1234567890");
+ }
+
+ @Test
+ void shouldConstructJSON() {
+ // Given
+ TextBuilder textBuilder = new TextBuilder();
+
+ // When
+ textBuilder.append("{ ");
+ textBuilder.append("aaaa=bbb");
+ textBuilder.append(',');
+ textBuilder.append("foo=bar");
+ textBuilder.append(',');
+ textBuilder.setCharAt(textBuilder.length() - 1, '}');
+
+ // Then
+ CharSequence actual = textBuilder.toString();
+ assertThat(actual).isEqualTo("{ aaaa=bbb,foo=bar}");
+ }
+
+ @Test
+ void shouldConstructEmptyJSON() {
+ // Given
+ TextBuilder textBuilder = new TextBuilder();
+
+ // When
+ textBuilder.append("{ ");
+ textBuilder.setCharAt(textBuilder.length() - 1, '}');
+
+ // Then
+ CharSequence actual = textBuilder.toString();
+ assertThat(actual).isEqualTo("{}");
+ }
+
+ @Test
+ void shouldGrowBackendStorage() {
+ // Given
+ String expected = StringUtils.repeat("abcd", 121);
+ TextBuilder textBuilder = new TextBuilder();
+
+ // When
+ int i;
+ for (i = 0; i < 121; i++) {
+ textBuilder.append('a');
+ textBuilder.append("bcd");
+ }
+
+ // Then
+ assertThat(textBuilder.length()).isEqualTo(expected.length());
+ assertThat(textBuilder.toString()).isEqualTo(expected);
+ }
+}