diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000000..5b4750958a
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,25 @@
+# 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.
+
+version: 2
+updates:
+ - package-ecosystem: "maven"
+ directory: "/"
+ schedule:
+ interval: "daily"
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "daily"
diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
new file mode 100644
index 0000000000..bc2303719a
--- /dev/null
+++ b/.github/workflows/maven.yml
@@ -0,0 +1,48 @@
+# 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.
+
+name: Java CI
+
+on: [push, pull_request]
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+ continue-on-error: ${{ matrix.experimental }}
+ strategy:
+ matrix:
+ java: [ 8, 11, 15 ]
+ experimental: [false]
+ include:
+ - java: 16-ea
+ experimental: true
+ - java: 17-ea
+ experimental: true
+
+ steps:
+ - uses: actions/checkout@v2.3.4
+ - uses: actions/cache@v2
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
+ - name: Set up JDK ${{ matrix.java }}
+ uses: actions/setup-java@v1.4.3
+ with:
+ java-version: ${{ matrix.java }}
+ - name: Build with Maven
+ run: mvn -V apache-rat:check spotbugs:check javadoc:javadoc -Ddoclint=all package --file pom.xml --no-transfer-progress
diff --git a/.gitignore b/.gitignore
index b9493d72e7..92a1b2a4a3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,3 +17,4 @@ site-content
.classpath
.project
.externalToolBuilders
+/.checkstyle
diff --git a/.travis.yml b/.travis.yml
index 6e09a29e78..c15d1d2734 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -14,15 +14,19 @@
# limitations under the License.
language: java
-sudo: false
jdk:
- - openjdk7
- - oraclejdk8
- - oraclejdk9
+ - openjdk8
+ - openjdk11
+ - openjdk15
+ - openjdk-ea
+
+matrix:
+ allow_failures:
+ - jdk: openjdk-ea
script:
- - mvn
+ - mvn -V --no-transfer-progress
after_success:
- - mvn clean test jacoco:report coveralls:report -Ptravis-jacoco
+ - mvn -V --no-transfer-progress clean test jacoco:report coveralls:report -Ptravis-jacoco
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e4cb4aaa40..a079867e8c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -25,7 +25,7 @@
| commons-build-plugin/trunk/src/main/resources/commons-xdoc-templates |
+======================================================================+
| |
- | 1) Re-generate using: mvn commons:contributing-md |
+ | 1) Re-generate using: mvn commons-build:contributing-md |
| |
| 2) Set the following properties in the component's pom: |
| - commons.jira.id (required, alphabetic, upper case) |
@@ -50,7 +50,7 @@ Getting Started
+ Make sure you have a [JIRA account](https://issues.apache.org/jira/).
+ Make sure you have a [GitHub account](https://github.com/signup/free).
-+ If you're planning to implement a new feature it makes sense to discuss you're changes on the [dev list](https://commons.apache.org/mail-lists.html) first. This way you can make sure you're not wasting your time on something that isn't considered to be in Apache Commons Text's scope.
++ If you're planning to implement a new feature it makes sense to discuss your changes on the [dev list](https://commons.apache.org/mail-lists.html) first. This way you can make sure you're not wasting your time on something that isn't considered to be in Apache Commons Text's scope.
+ Submit a [Jira Ticket][jira] for your issue, assuming one does not already exist.
+ Clearly describe the issue including steps to reproduce when it is a bug.
+ Make sure you fill in the earliest version that you know has the issue.
@@ -107,7 +107,7 @@ Additional Resources
+ [Apache Commons Text JIRA project page][jira]
+ [Contributor License Agreement][cla]
+ [General GitHub documentation](https://help.github.com/)
-+ [GitHub pull request documentation](https://help.github.com/send-pull-requests/)
++ [GitHub pull request documentation](https://help.github.com/articles/creating-a-pull-request/)
+ [Apache Commons Twitter Account](https://twitter.com/ApacheCommons)
+ `#apache-commons` IRC channel on `irc.freenode.net`
diff --git a/NOTICE.txt b/NOTICE.txt
index faf1f27d4f..2731b62593 100644
--- a/NOTICE.txt
+++ b/NOTICE.txt
@@ -1,5 +1,5 @@
Apache Commons Text
-Copyright 2001-2017 The Apache Software Foundation
+Copyright 2014-2020 The Apache Software Foundation
This product includes software developed at
-The Apache Software Foundation (http://www.apache.org/).
+The Apache Software Foundation (https://www.apache.org/).
diff --git a/README.md b/README.md
index cd91e30cb9..3c5565daf3 100644
--- a/README.md
+++ b/README.md
@@ -1,104 +1,105 @@
-
-
-Apache Commons Text
-===================
-
-[](https://travis-ci.org/apache/commons-text)
-[](https://coveralls.io/r/apache/commons-text)
-[](https://maven-badges.herokuapp.com/maven-central/org.apache.commons/commons-text/)
-
-Apache Commons Text is a library focused on algorithms working on strings.
-
-Documentation
--------------
-
-More information can be found on the [Apache Commons Text homepage](https://commons.apache.org/proper/commons-text).
-The [JavaDoc](https://commons.apache.org/proper/commons-text/javadocs/api-release) can be browsed.
-Questions related to the usage of Apache Commons Text should be posted to the [user mailing list][ml].
-
-Where can I get the latest release?
------------------------------------
-You can download source and binaries from our [download page](https://commons.apache.org/proper/commons-text/download_text.cgi).
-
-Alternatively you can pull it from the central Maven repositories:
-
-```xml
-
* Convert from one alphabet to another, with the possibility of leaving certain
@@ -45,20 +48,20 @@
* chars, which will be of length 1
*
* Example Builder:
+ * Case manipulation operations on Strings that contain words. This class tries to handle This class tries to handle {@code null} input gracefully.
+ * An exception will not be thrown for a {@code null} input.
+ * Each method documents its behavior in more detail. {@code CaseUtils} instances should NOT be constructed in
* standard programming. Instead, the class should be used as
- * Sample usage
+ * Sample usage
*
*
- * Character[] originals; // a, b, c, d
- * Character[] encoding; // 0, 1, d
+ * Character[] originals; // a, b, c, d
+ * Character[] encoding; // 0, 1, d
* Character[] doNotEncode; // d
*
* AlphabetConverter ac = AlphabetConverter.createConverterFromChars(originals,
* encoding, doNotEncode);
*
- * ac.encode("a"); // 00
- * ac.encode("b"); // 01
- * ac.encode("c"); // 0d
- * ac.encode("d"); // d
+ * ac.encode("a"); // 00
+ * ac.encode("b"); // 01
+ * ac.encode("c"); // 0d
+ * ac.encode("d"); // d
* ac.encode("abcd"); // 00010dd
*
*
@@ -109,7 +112,7 @@ private AlphabetConverter(final Map
* class FontBuilder implements Builder<Font> {
* private Font font;
@@ -82,7 +83,7 @@ public interface Buildernull input gracefully.
- * An exception will not be thrown for a null input.
- * Each method documents its behaviour in more detail.CaseUtils instances should NOT be constructed in
+ * CaseUtils.toCamelCase("foo bar", true, new char[]{'-'});.
This constructor is public to permit tools that require a JavaBean * instance to operate.
*/ public CaseUtils() { - super(); } /** *Converts all the delimiter separated words in a String into camelCase, - * that is each word is made up of a titlecase character and then a series of + * that is each word is made up of a title case character and then a series of * lowercase characters.
* *The delimiters represent a set of characters understood to separate words. @@ -54,9 +54,12 @@ public CaseUtils() { * character may or may not be capitalized and it's determined by the user input for capitalizeFirstLetter * variable.
* - *A null input String returns null.
+ *
A {@code null} input String returns {@code null}.
+ * + *A input string with only delimiter characters returns {@code ""}.
+ * * Capitalization uses the Unicode title case, normally equivalent to - * upper case and cannot perform locale-sensitive mappings. + * upper case and cannot perform locale-sensitive mappings. * *
* CaseUtils.toCamelCase(null, false) = null
@@ -66,12 +69,13 @@ public CaseUtils() {
* CaseUtils.toCamelCase("To.Camel.Case", false, new char[]{'.'}) = "toCamelCase"
* CaseUtils.toCamelCase(" to @ Camel case", true, new char[]{'@'}) = "ToCamelCase"
* CaseUtils.toCamelCase(" @to @ Camel case", false, new char[]{'@'}) = "toCamelCase"
+ * CaseUtils.toCamelCase(" @", false, new char[]{'@'}) = ""
*
*
* @param str the String to be converted to camelCase, may be null
* @param capitalizeFirstLetter boolean that determines if the first character of first word should be title case.
* @param delimiters set of characters to determine capitalization, null and/or empty array means whitespace
- * @return camelCase of String, null if null String input
+ * @return camelCase of String, {@code null} if null String input
*/
public static String toCamelCase(String str, final boolean capitalizeFirstLetter, final char... delimiters) {
if (StringUtils.isEmpty(str)) {
@@ -90,10 +94,7 @@ public static String toCamelCase(String str, final boolean capitalizeFirstLetter
final int codePoint = str.codePointAt(index);
if (delimiterSet.contains(codePoint)) {
- capitalizeNext = true;
- if (outOffset == 0) {
- capitalizeNext = false;
- }
+ capitalizeNext = outOffset != 0;
index += Character.charCount(codePoint);
} else if (capitalizeNext || outOffset == 0 && capitalizeFirstLetter) {
final int titleCaseCodePoint = Character.toTitleCase(codePoint);
@@ -105,11 +106,8 @@ public static String toCamelCase(String str, final boolean capitalizeFirstLetter
index += Character.charCount(codePoint);
}
}
- if (outOffset != 0) {
- return new String(newCodePoints, 0, outOffset);
- } else {
- return str;
- }
+
+ return new String(newCodePoints, 0, outOffset);
}
/**
@@ -122,7 +120,7 @@ public static String toCamelCase(String str, final boolean capitalizeFirstLetter
private static SettoAppendTo
+ * @return {@code toAppendTo}
* @see Format#format(Object, StringBuffer, FieldPosition)
*/
@Override // Therefore has to use StringBuffer
@@ -76,7 +76,7 @@ public StringBuffer format(final Object obj, final StringBuffer toAppendTo,
* @param pos the ParsePosition containing the position to parse from, will
* be updated according to parsing success (index) or failure
* (error index)
- * @return the parsed Object
+ * @return The parsed Object
* @see Format#parseObject(String, ParsePosition)
*/
@Override
diff --git a/src/main/java/org/apache/commons/text/ExtendedMessageFormat.java b/src/main/java/org/apache/commons/text/ExtendedMessageFormat.java
index dcf0766c17..d7694a6be0 100644
--- a/src/main/java/org/apache/commons/text/ExtendedMessageFormat.java
+++ b/src/main/java/org/apache/commons/text/ExtendedMessageFormat.java
@@ -27,39 +27,42 @@
import java.util.Map;
import java.util.Objects;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.text.matcher.StringMatcherFactory;
+
/**
- * Extends java.text.MessageFormat to allow pluggable/additional formatting
+ * Extends {@code java.text.MessageFormat} to allow pluggable/additional formatting
* options for embedded format elements. Client code should specify a registry
- * of FormatFactory instances associated with String
+ * of {@code FormatFactory} instances associated with {@code String}
* format names. This registry will be consulted when the format elements are
* parsed from the message pattern. In this way custom patterns can be specified,
- * and the formats supported by java.text.MessageFormat can be overridden
+ * and the formats supported by {@code java.text.MessageFormat} can be overridden
* at the format and/or format style level (see MessageFormat). A "format element"
* embedded in the message pattern is specified (()? signifies optionality):{argument-number(,format-name
- * (,format-style)?)?}
+ * {@code {}argument-number({@code ,}format-name
+ * ({@code ,}format-style)?)?{@code }}
*
*
* format-name and format-style values are trimmed of surrounding whitespace
- * in the manner of java.text.MessageFormat. If format-name denotes
- * FormatFactory formatFactoryInstance in registry, a Format
+ * in the manner of {@code java.text.MessageFormat}. If format-name denotes
+ * {@code FormatFactory formatFactoryInstance} in {@code registry}, a {@code Format}
* matching format-name and format-style is requested from
- * formatFactoryInstance. If this is successful, the Format
+ * {@code formatFactoryInstance}. If this is successful, the {@code Format}
* found is used for this format element.
*
NOTICE: The various subformat mutator methods are considered unnecessary; they exist on the parent
* class to allow the type of customization which it is the job of this class to provide in
* a configurable fashion. These methods have thus been disabled and will throw
- * UnsupportedOperationException if called.
+ * {@code UnsupportedOperationException} if called.
*
Limitations inherited from java.text.MessageFormat:
Limitations inherited from {@code java.text.MessageFormat}:
*Formats, including MessageFormat and thus
- * ExtendedMessageFormat, is not guaranteed.pos
+ * @return {@code pos}
*/
private ParsePosition next(final ParsePosition pos) {
pos.setIndex(pos.getIndex() + 1);
@@ -504,13 +510,13 @@ private ParsePosition next(final ParsePosition pos) {
}
/**
- * Consume a quoted string, adding it to appendTo if
+ * Consume a quoted string, adding it to {@code appendTo} if
* specified.
*
* @param pattern pattern to parse
* @param pos current parse position
* @param appendTo optional StringBuilder to append
- * @return appendTo
+ * @return {@code appendTo}
*/
private StringBuilder appendQuotedString(final String pattern, final ParsePosition pos,
final StringBuilder appendTo) {
@@ -553,7 +559,7 @@ private void getQuotedString(final String pattern, final ParsePosition pos) {
/**
* Learn whether the specified Collection contains non-null elements.
* @param coll to check
- * @return true if some Object was found, false otherwise.
+ * @return {@code true} if some Object was found, {@code false} otherwise.
*/
private boolean containsElements(final Collection> coll) {
if (coll == null || coll.isEmpty()) {
diff --git a/src/main/java/org/apache/commons/text/FormatFactory.java b/src/main/java/org/apache/commons/text/FormatFactory.java
index ceee01bc1a..d2908d3f0d 100644
--- a/src/main/java/org/apache/commons/text/FormatFactory.java
+++ b/src/main/java/org/apache/commons/text/FormatFactory.java
@@ -31,8 +31,8 @@ public interface FormatFactory {
*
* @param name The format type name
* @param arguments Arguments used to create the format instance. This allows the
- * FormatFactory to implement the "format style"
- * concept from java.text.MessageFormat.
+ * {@code FormatFactory} to implement the "format style"
+ * concept from {@code java.text.MessageFormat}.
* @param locale The locale, may be null
* @return The format instance
*/
diff --git a/src/main/java/org/apache/commons/text/FormattableUtils.java b/src/main/java/org/apache/commons/text/FormattableUtils.java
index 043cbf177b..78b5bc6941 100644
--- a/src/main/java/org/apache/commons/text/FormattableUtils.java
+++ b/src/main/java/org/apache/commons/text/FormattableUtils.java
@@ -16,10 +16,12 @@
*/
package org.apache.commons.text;
+import static java.util.FormattableFlags.LEFT_JUSTIFY;
+
import java.util.Formattable;
import java.util.Formatter;
-import static java.util.FormattableFlags.LEFT_JUSTIFY;
+import org.apache.commons.lang3.StringUtils;
/**
* Provides utilities for working with the {@code Formattable} interface.
@@ -47,7 +49,6 @@ public class FormattableUtils { * instance to operate. */ public FormattableUtils() { - super(); } //----------------------------------------------------------------------- @@ -56,7 +57,7 @@ public FormattableUtils() { * {@code Formattable}. * * @param formattable the instance to convert to a string, not null - * @return the resulting string, not null + * @return The resulting string, not null */ public static String toString(final Formattable formattable) { return String.format(SIMPLEST_FORMAT, formattable); @@ -72,7 +73,7 @@ public static String toString(final Formattable formattable) { * @param flags the flags for formatting, see {@code Formattable} * @param width the width of the output, see {@code Formattable} * @param precision the precision of the output, see {@code Formattable} - * @return the {@code formatter} instance, not null + * @return The {@code formatter} instance, not null */ public static Formatter append(final CharSequence seq, final Formatter formatter, final int flags, final int width, final int precision) { @@ -89,7 +90,7 @@ public static Formatter append(final CharSequence seq, final Formatter formatter * @param width the width of the output, see {@code Formattable} * @param precision the precision of the output, see {@code Formattable} * @param padChar the pad character to use - * @return the {@code formatter} instance, not null + * @return The {@code formatter} instance, not null */ public static Formatter append(final CharSequence seq, final Formatter formatter, final int flags, final int width, final int precision, final char padChar) { @@ -107,7 +108,7 @@ public static Formatter append(final CharSequence seq, final Formatter formatter * @param precision the precision of the output, see {@code Formattable} * @param ellipsis the ellipsis to use when precision dictates truncation, null or * empty causes a hard truncation - * @return the {@code formatter} instance, not null + * @return The {@code formatter} instance, not null */ public static Formatter append(final CharSequence seq, final Formatter formatter, final int flags, final int width, final int precision, final CharSequence ellipsis) { @@ -125,21 +126,21 @@ public static Formatter append(final CharSequence seq, final Formatter formatter * @param padChar the pad character to use * @param ellipsis the ellipsis to use when precision dictates truncation, null or * empty causes a hard truncation - * @return the {@code formatter} instance, not null + * @return The {@code formatter} instance, not null */ public static Formatter append(final CharSequence seq, final Formatter formatter, final int flags, final int width, final int precision, final char padChar, final CharSequence ellipsis) { if (!(ellipsis == null || precision < 0 || ellipsis.length() <= precision)) { throw new IllegalArgumentException( - String.format("Specified ellipsis '%1$s' exceeds precision of %2$s", + String.format("Specified ellipsis '%s' exceeds precision of %s", ellipsis, - Integer.valueOf(precision))); + precision)); } final StringBuilder buf = new StringBuilder(seq); if (precision >= 0 && precision < seq.length()) { final CharSequence _ellipsis; if (ellipsis == null) { - _ellipsis = ""; + _ellipsis = StringUtils.EMPTY; } else { _ellipsis = ellipsis; } diff --git a/src/main/java/org/apache/commons/text/RandomStringGenerator.java b/src/main/java/org/apache/commons/text/RandomStringGenerator.java index dd50f96166..3a36e5d3b9 100644 --- a/src/main/java/org/apache/commons/text/RandomStringGenerator.java +++ b/src/main/java/org/apache/commons/text/RandomStringGenerator.java @@ -16,14 +16,17 @@ */ package org.apache.commons.text; -import org.apache.commons.lang3.Validate; - import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ThreadLocalRandom; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; + /** ** Generates random Unicode strings containing the specified number of code points. @@ -48,7 +51,7 @@ * String randomLetters = generator.generate(20); * *
- * {@code RandomStringBuilder} instances are thread-safe when using the
+ * {@code RandomStringGenerator} instances are thread-safe when using the
* default random number generator (RNG). If a custom RNG is set by calling the method
* {@link Builder#usingRandom(TextRandomProvider) Builder.usingRandom(TextRandomProvider)}, thread-safety
* must be ensured externally.
@@ -113,7 +116,7 @@ private RandomStringGenerator(final int minimumCodePoint, final int maximumCodeP
* the minimum value allowed
* @param maxInclusive
* the maximum value allowed
- * @return the random number.
+ * @return The random number.
*/
private int generateRandomNumber(final int minInclusive, final int maxInclusive) {
if (random != null) {
@@ -127,7 +130,7 @@ private int generateRandomNumber(final int minInclusive, final int maxInclusive)
* or the user-supplied source of randomness.
*
* @param characterList predefined char list.
- * @return the random number.
+ * @return The random number.
*/
private int generateRandomNumber(final List A builder for generating {@code RandomStringGenerator} instances. The behaviour of a generator is controlled by properties set by this
+ * The behavior of a generator is controlled by properties set by this
* builder. Each property has a default value, which can be overridden by
* calling the methods defined in this class, prior to calling {@link #build()}.
* Passing {@code null} or an empty array to this method will revert to the
- * default behaviour of allowing any character. Multiple calls to this
+ * default behavior of allowing any character. Multiple calls to this
* method will replace the previously stored predicates.
*
* Overrides the default source of randomness. It is highly
* recommended that a random number generator library like
- * Apache Commons RNG
+ * Apache Commons RNG
* be used to provide the random number generation.
*
* Passing {@code null} or an empty array to this method will revert to the
- * default behaviour of allowing any character. Multiple calls to this
+ * default behavior of allowing any character. Multiple calls to this
* method will replace the previously stored Character.
* Builds the {@code RandomStringGenerator} using the properties specified.
- * This method is the same as {@link #length()} and is provided to match the - * API of Collections. + * Appends a double value to the string builder using {@code String.valueOf}. * - * @return the length + * @param value the value to append + * @return this, to enable chaining */ - public int size() { - return size; + public StrBuilder append(final double value) { + return append(String.valueOf(value)); } /** - * Checks is the string builder is empty (convenience Collections API style method). - *
- * This method is the same as checking {@link #length()} and is provided to match the
- * API of Collections.
+ * Appends a float value to the string builder using {@code String.valueOf}.
*
- * @return true if the size is 0.
+ * @param value the value to append
+ * @return this, to enable chaining
*/
- public boolean isEmpty() {
- return size == 0;
+ public StrBuilder append(final float value) {
+ return append(String.valueOf(value));
}
/**
- * Clears the string builder (convenience Collections API style method).
- *
- * This method does not reduce the size of the internal character buffer.
- * To do that, call clear() followed by {@link #minimizeCapacity()}.
- *
- * This method is the same as {@link #setLength(int)} called with zero - * and is provided to match the API of Collections. + * Appends an int value to the string builder using {@code String.valueOf}. * + * @param value the value to append * @return this, to enable chaining */ - public StrBuilder clear() { - size = 0; - return this; + public StrBuilder append(final int value) { + return append(String.valueOf(value)); } - //----------------------------------------------------------------------- /** - * Gets the character at the specified index. + * Appends a long value to the string builder using {@code String.valueOf}. * - * @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 + * @param value the value to append + * @return this, to enable chaining */ - @Override - public char charAt(final int index) { - if (index < 0 || index >= length()) { - throw new StringIndexOutOfBoundsException(index); - } - return buffer[index]; + public StrBuilder append(final long value) { + return append(String.valueOf(value)); } /** - * Sets the character at the specified index. + * Appends an object to this string builder. + * Appending null will call {@link #appendNull()}. * - * @see #charAt(int) - * @see #deleteCharAt(int) - * @param index the index to set - * @param ch the new character + * @param obj the object to append * @return this, to enable chaining - * @throws IndexOutOfBoundsException if the index is invalid */ - public StrBuilder setCharAt(final int index, final char ch) { - if (index < 0 || index >= length()) { - throw new StringIndexOutOfBoundsException(index); + public StrBuilder append(final Object obj) { + if (obj == null) { + return appendNull(); } - buffer[index] = ch; - return this; + if (obj instanceof CharSequence) { + return append((CharSequence) obj); + } + return append(obj.toString()); } /** - * Deletes the character at the specified index. + * Appends another string builder to this string builder. + * Appending null will call {@link #appendNull()}. * - * @see #charAt(int) - * @see #setCharAt(int, char) - * @param index the index to delete + * @param str the string builder to append * @return this, to enable chaining - * @throws IndexOutOfBoundsException if the index is invalid */ - public StrBuilder deleteCharAt(final int index) { - if (index < 0 || index >= size) { - throw new StringIndexOutOfBoundsException(index); + public StrBuilder append(final StrBuilder str) { + if (str == null) { + return appendNull(); + } + final int strLen = str.length(); + if (strLen > 0) { + final int len = length(); + ensureCapacity(len + strLen); + System.arraycopy(str.buffer, 0, buffer, len, strLen); + size += strLen; } - deleteImpl(index, index + 1, 1); return this; } - //----------------------------------------------------------------------- /** - * Copies the builder's character array into a new character array. + * Appends part of a string builder to this string builder. + * Appending null will call {@link #appendNull()}. * - * @return a new array that represents the contents of the builder + * @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 char[] toCharArray() { - if (size == 0) { - return new char[0]; + public StrBuilder append(final StrBuilder str, final int startIndex, final int length) { + if (str == null) { + return appendNull(); } - final char[] chars = new char[size]; - System.arraycopy(buffer, 0, chars, 0, size); - return chars; + 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; } /** - * Copies part of the builder's character array into a new character array. + * Appends a string to this string builder. + * Appending null will call {@link #appendNull()}. * - * @param startIndex the start index, inclusive, must be valid - * @param endIndex the end index, exclusive, must be valid except that - * if too large it is treated as end of string - * @return a new array that holds part of the contents of the builder - * @throws IndexOutOfBoundsException if startIndex is invalid, - * or if endIndex is invalid (but endIndex greater than size is valid) - */ - public char[] toCharArray(final int startIndex, int endIndex) { - endIndex = validateRange(startIndex, endIndex); - final int len = endIndex - startIndex; - if (len == 0) { - return new char[0]; - } - final char[] chars = new char[len]; - System.arraycopy(buffer, startIndex, chars, 0, len); - return chars; - } - - /** - * Copies the character array into the specified array. - * - * @param destination the destination array, null will cause an array to be created - * @return the input array, unless that was null or too small - */ - public char[] getChars(char[] destination) { - final int len = length(); - if (destination == null || destination.length < len) { - destination = new char[len]; - } - System.arraycopy(buffer, 0, destination, 0, len); - return destination; - } - - /** - * Copies the character array into the specified array. - * - * @param startIndex first index to copy, inclusive, must be valid - * @param endIndex last index, exclusive, must be valid - * @param destination the destination array, must not be null or too small - * @param destinationIndex the index to start copying in destination - * @throws NullPointerException if the array is null - * @throws IndexOutOfBoundsException if any index is invalid - */ - public void getChars(final int startIndex, - final int endIndex, - final char[] destination, - final int destinationIndex) { - if (startIndex < 0) { - throw new StringIndexOutOfBoundsException(startIndex); - } - if (endIndex < 0 || endIndex > length()) { - throw new StringIndexOutOfBoundsException(endIndex); - } - if (startIndex > endIndex) { - throw new StringIndexOutOfBoundsException("end < start"); - } - System.arraycopy(buffer, startIndex, destination, destinationIndex, endIndex - startIndex); - } - - //----------------------------------------------------------------------- - /** - * If possible, reads chars from the provided {@link Readable} directly into underlying - * character buffer without making extra copies. - * - * @param readable object to read from - * @return the number of characters read - * @throws IOException if an I/O error occurs - * - * @see #appendTo(Appendable) - */ - public int readFrom(final Readable readable) throws IOException { - final int oldSize = size; - if (readable instanceof Reader) { - final Reader r = (Reader) readable; - ensureCapacity(size + 1); - int read; - while ((read = r.read(buffer, size, buffer.length - size)) != -1) { - size += read; - ensureCapacity(size + 1); - } - } else if (readable instanceof CharBuffer) { - final CharBuffer cb = (CharBuffer) readable; - final int remaining = cb.remaining(); - ensureCapacity(size + remaining); - cb.get(buffer, size, remaining); - size += remaining; - } else { - while (true) { - ensureCapacity(size + 1); - final CharBuffer buf = CharBuffer.wrap(buffer, size, buffer.length - size); - final int read = readable.read(buf); - if (read == -1) { - break; - } - size += read; - } - } - return size - oldSize; - } - - //----------------------------------------------------------------------- - /** - * Appends the new line string to this string builder. - *
- * The new line string can be altered using {@link #setNewLineText(String)}.
- * This might be used to force the output to always use Unix line endings
- * even when on Windows.
- *
- * @return this, to enable chaining
- */
- public StrBuilder appendNewLine() {
- if (newLine == null) {
- append(System.lineSeparator());
- return this;
- }
- return append(newLine);
- }
-
- /**
- * Appends the text representing
+ * 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.
*
- * @param str the string builder to append
* @return this, to enable chaining
*/
- public StrBuilder appendln(final StrBuilder str) {
- return append(str).appendNewLine();
+ public StrBuilder appendNewLine() {
+ if (newLine == null) {
+ append(System.lineSeparator());
+ return this;
+ }
+ return append(newLine);
}
/**
- * Appends part of a string builder followed by a new line to this string builder.
- * Appending null will call {@link #appendNull()}.
+ * Appends the text representing {@code null} to this string builder.
*
- * @param str the string to append
- * @param startIndex the start index, inclusive, must be valid
- * @param length the length to append, must be valid
* @return this, to enable chaining
*/
- public StrBuilder appendln(final StrBuilder str, final int startIndex, final int length) {
- return append(str, startIndex, length).appendNewLine();
+ public StrBuilder appendNull() {
+ if (nullText == null) {
+ return this;
+ }
+ return append(nullText);
}
+ //-----------------------------------------------------------------------
/**
- * Appends a char array followed by a new line to the string builder.
- * Appending null will call {@link #appendNull()}.
+ * Appends the pad character to the builder the specified number of times.
*
- * @param chars the char array to append
+ * @param length the length to append, negative means no append
+ * @param padChar the character to append
* @return this, to enable chaining
*/
- public StrBuilder appendln(final char[] chars) {
- return append(chars).appendNewLine();
+ public StrBuilder appendPadding(final int length, final char padChar) {
+ if (length >= 0) {
+ ensureCapacity(size + length);
+ for (int i = 0; i < length; i++) {
+ buffer[size++] = padChar;
+ }
+ }
+ return this;
}
/**
- * Appends a char array followed by a new line to the string builder.
- * Appending null will call {@link #appendNull()}.
+ * Appends a separator if the builder is currently non-empty.
+ * The separator is appended using {@link #append(char)}.
+ *
+ * This method is useful for adding a separator each time around the
+ * loop except the first.
+ *
+ * This method is useful for adding a separator each time around the
+ * loop except the first.
+ *
+ * This method is useful for adding a separator each time around the
+ * loop except the first.
+ *
+ * This method is useful for adding a separator each time around the
+ * loop except the first.
+ *
+ * This method is for example useful for constructing queries
+ *
+ * This method tries to avoid doing any extra copies of contents.
*
- * @param
+ * This method allows the contents of the builder to be read
+ * using any standard method that expects a Reader.
+ *
+ * To use, simply create a {@code StrBuilder}, populate it with
+ * data, call {@code asReader}, and then read away.
+ *
+ * The internal character array is shared between the builder and the reader.
+ * This allows you to append to the builder after creating the reader,
+ * and the changes will be picked up.
+ * Note however, that no synchronization occurs, so you must perform
+ * all operations with the builder and the reader in one thread.
+ *
+ * The returned reader supports marking, and ignores the flush method.
*
- * @param iterable the iterable to append
- * @param separator the separator to use, null means no separator
- * @return this, to enable chaining
+ * @return a reader that reads from this builder
*/
- public StrBuilder appendWithSeparators(final Iterable> iterable, final String separator) {
- if (iterable != null) {
- final String sep = Objects.toString(separator, "");
- final Iterator> it = iterable.iterator();
- while (it.hasNext()) {
- append(it.next());
- if (it.hasNext()) {
- append(sep);
- }
- }
- }
- return this;
+ public Reader asReader() {
+ return new StrBuilderReader();
}
+ //-----------------------------------------------------------------------
/**
- * 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)}.
+ * Creates a tokenizer that can tokenize the contents of this builder.
+ *
+ * This method allows the contents of this builder to be tokenized.
+ * The tokenizer will be setup by default to tokenize on space, tab,
+ * newline and form feed (as per StringTokenizer). These values can be
+ * changed on the tokenizer class, before retrieving the tokens.
+ *
+ * The returned tokenizer is linked to this builder. You may intermix
+ * calls to the builder and tokenizer within certain limits, however
+ * there is no synchronization. Once the tokenizer has been used once,
+ * it must be {@link StrTokenizer#reset() reset} to pickup the latest
+ * changes in the builder. For example:
+ *
+ * Calling {@link StrTokenizer#reset(String)} or {@link StrTokenizer#reset(char[])}
+ * with a non-null value will break the link with the builder.
*
- * @param it the iterator to append
- * @param separator the separator to use, null means no separator
- * @return this, to enable chaining
+ * @return a tokenizer that is linked to this builder
*/
- public StrBuilder appendWithSeparators(final Iterator> it, final String separator) {
- if (it != null) {
- final String sep = Objects.toString(separator, "");
- while (it.hasNext()) {
- append(it.next());
- if (it.hasNext()) {
- append(sep);
- }
- }
- }
- return this;
+ public StrTokenizer asTokenizer() {
+ return new StrBuilderTokenizer();
}
//-----------------------------------------------------------------------
/**
- * 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)}.
+ * Gets this builder as a Writer that can be written to.
*
- * This method is useful for adding a separator each time around the
- * loop except the first.
- *
+ * To use, simply create a {@code StrBuilder},
+ * call {@code asWriter}, and populate away. The data is available
+ * at any time using the methods of the {@code StrBuilder}.
+ *
+ * The internal character array is shared between the builder and the writer.
+ * This allows you to intermix calls that append to the builder and
+ * write using the writer and the changes will be occur correctly.
+ * Note however, that no synchronization occurs, so you must perform
+ * all operations with the builder and the writer in one thread.
+ *
+ * The returned writer ignores the close and flush methods.
*
- * @param separator the separator to use, null means no separator
- * @return this, to enable chaining
+ * @return a writer that populates this builder
*/
- public StrBuilder appendSeparator(final String separator) {
- return appendSeparator(separator, null);
+ public Writer asWriter() {
+ return new StrBuilderWriter();
}
/**
- * Appends one of both separators to the StrBuilder.
- * If the builder is currently empty it will append the defaultIfEmpty-separator
- * Otherwise it will append the standard-separator
- *
- * Appending a null separator will have no effect.
- * The separator is appended using {@link #append(String)}.
- *
- * This method is for example useful for constructing queries
- *
- * This method is useful for adding a separator each time around the
- * loop except the first.
- *
- * This method is useful for adding a separator each time around the
- * loop except the first.
- *
+ * This method is the same as {@link #setLength(int)} called with zero
+ * and is provided to match the API of Collections.
*
- * @param separator the separator to use, null means no separator
- * @param loopIndex the loop index
* @return this, to enable chaining
*/
- public StrBuilder appendSeparator(final String separator, final int loopIndex) {
- if (separator != null && loopIndex > 0) {
- append(separator);
- }
+ public StrBuilder clear() {
+ size = 0;
return this;
}
+ //-----------------------------------------------------------------------
/**
- * Appends a separator to the builder if the loop index is greater than zero.
- * The separator is appended using {@link #append(char)}.
- *
- * This method is useful for adding a separator each time around the
- * loop except the first.
- *
+ * Matchers can be used to perform advanced searching behavior.
+ * For example you could write a matcher to search for the character
+ * 'a' followed by a number.
*
- * @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
+ * @param matcher the matcher to use, null returns -1
+ * @return true if the matcher finds a match in the builder
+ */
+ public boolean contains(final StrMatcher matcher) {
+ return indexOf(matcher, 0) >= 0;
+ }
+ /**
+ * Deletes the characters between the two specified indices.
+ *
+ * @param startIndex the start index, inclusive, must be valid
+ * @param endIndex the end index, exclusive, must be valid except
+ * that if too large it is treated as end of string
* @return this, to enable chaining
+ * @throws IndexOutOfBoundsException if the index is invalid
*/
- public StrBuilder 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;
+ public StrBuilder delete(final int startIndex, int endIndex) {
+ endIndex = validateRange(startIndex, endIndex);
+ final int len = endIndex - startIndex;
+ if (len > 0) {
+ deleteImpl(startIndex, endIndex, len);
}
return this;
}
+ //-----------------------------------------------------------------------
/**
- * Appends an object to the builder padding on the left to a fixed width.
- * The
+ * Matchers can be used to perform advanced deletion behavior.
+ * For example you could write a matcher to delete all occurrences
+ * where the character 'a' is followed by a number.
*
- * @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
+ * @param matcher the matcher to use to find the deletion, null causes no action
* @return this, to enable chaining
*/
- public StrBuilder appendFixedWidthPadRight(final int value, final int width, final char padChar) {
- return appendFixedWidthPadRight(String.valueOf(value), width, padChar);
+ public StrBuilder deleteAll(final StrMatcher matcher) {
+ return replace(matcher, null, 0, size, -1);
}
- //-----------------------------------------------------------------------
/**
- * Inserts the string representation of an object into this builder.
- * Inserting null will use the stored null text value.
+ * Deletes the character at the specified index.
*
- * @param index the index to add at, must be valid
- * @param obj the object to insert
+ * @see #charAt(int)
+ * @see #setCharAt(int, char)
+ * @param index the index to delete
* @return this, to enable chaining
* @throws IndexOutOfBoundsException if the index is invalid
*/
- public StrBuilder insert(final int index, final Object obj) {
- if (obj == null) {
- return insert(index, nullText);
+ public StrBuilder deleteCharAt(final int index) {
+ if (index < 0 || index >= size) {
+ throw new StringIndexOutOfBoundsException(index);
}
- return insert(index, obj.toString());
+ deleteImpl(index, index + 1, 1);
+ return this;
}
/**
- * Inserts the string into this builder.
- * Inserting null will use the stored null text value.
+ * Deletes the character wherever it occurs in the builder.
*
- * @param index the index to add at, must be valid
- * @param str the string to insert
+ * @param ch the character to delete
* @return this, to enable chaining
- * @throws IndexOutOfBoundsException if the index is invalid
*/
- public StrBuilder insert(final int index, String str) {
- validateIndex(index);
- if (str == null) {
- str = nullText;
- }
- if (str != null) {
- final int strLen = str.length();
- if (strLen > 0) {
- final int newSize = size + strLen;
- ensureCapacity(newSize);
- System.arraycopy(buffer, index, buffer, index + strLen, size - index);
- size = newSize;
- str.getChars(0, strLen, buffer, index);
+ public StrBuilder deleteFirst(final char ch) {
+ for (int i = 0; i < size; i++) {
+ if (buffer[i] == ch) {
+ deleteImpl(i, i + 1, 1);
+ break;
}
}
return this;
}
/**
- * Inserts the character array into this builder.
- * Inserting null will use the stored null text value.
+ * Deletes the string wherever it occurs in the builder.
*
- * @param index the index to add at, must be valid
- * @param chars the char array to insert
+ * @param str the string to delete, null causes no action
* @return this, to enable chaining
- * @throws IndexOutOfBoundsException if the index is invalid
*/
- public StrBuilder insert(final int index, final char[] chars) {
- validateIndex(index);
- if (chars == null) {
- return insert(index, nullText);
- }
- final int len = chars.length;
+ public StrBuilder deleteFirst(final String str) {
+ final int len = str == null ? 0 : str.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;
+ final int index = indexOf(str, 0);
+ if (index >= 0) {
+ deleteImpl(index, index + len, len);
+ }
}
return this;
}
/**
- * Inserts part of the character array into this builder.
- * Inserting null will use the stored null text value.
+ * Deletes the first match within the builder using the specified matcher.
+ *
+ * Matchers can be used to perform advanced deletion behavior.
+ * For example you could write a matcher to delete
+ * where the character 'a' is followed by a number.
*
- * @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
+ * @param matcher the matcher to use to find the deletion, null causes no action
* @return this, to enable chaining
- * @throws IndexOutOfBoundsException if any index is invalid
*/
- public StrBuilder insert(final int index, final char[] chars, final int offset, final int length) {
- validateIndex(index);
- if (chars == null) {
- return insert(index, nullText);
- }
- if (offset < 0 || offset > chars.length) {
- throw new StringIndexOutOfBoundsException("Invalid offset: " + offset);
- }
- if (length < 0 || offset + length > chars.length) {
- throw new StringIndexOutOfBoundsException("Invalid length: " + length);
- }
- if (length > 0) {
- ensureCapacity(size + length);
- System.arraycopy(buffer, index, buffer, index + length, size - index);
- System.arraycopy(chars, offset, buffer, index, length);
- size += length;
- }
- return this;
+ public StrBuilder deleteFirst(final StrMatcher matcher) {
+ return replace(matcher, null, 0, size, 1);
}
+ //-----------------------------------------------------------------------
/**
- * Inserts the value into this builder.
+ * Internal method to delete a range without validation.
*
- * @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
+ * @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
*/
- public StrBuilder insert(int index, final boolean value) {
- validateIndex(index);
- if (value) {
- ensureCapacity(size + 4);
- System.arraycopy(buffer, index, buffer, index + 4, size - index);
- buffer[index++] = 't';
- buffer[index++] = 'r';
- buffer[index++] = 'u';
- buffer[index] = 'e';
- size += 4;
- } else {
- ensureCapacity(size + 5);
- System.arraycopy(buffer, index, buffer, index + 5, size - index);
- buffer[index++] = 'f';
- buffer[index++] = 'a';
- buffer[index++] = 'l';
- buffer[index++] = 's';
- buffer[index] = 'e';
- size += 5;
+ private void deleteImpl(final int startIndex, final int endIndex, final int len) {
+ System.arraycopy(buffer, endIndex, buffer, startIndex, size - endIndex);
+ size -= len;
+ }
+
+ /**
+ * Checks whether this builder ends with the specified string.
+ *
+ * Note that this method handles null input quietly, unlike String.
+ *
+ * @param str the string to search for, null returns false
+ * @return true if the builder ends with the string
+ */
+ public boolean endsWith(final String str) {
+ if (str == null) {
+ return false;
}
- return this;
+ 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;
}
/**
- * Inserts the value into this builder.
+ * Checks the capacity and ensures that it is at least the size specified.
*
- * @param index the index to add at, must be valid
- * @param value the value to insert
+ * @param capacity the capacity to ensure
* @return this, to enable chaining
- * @throws IndexOutOfBoundsException if the index is invalid
*/
- public StrBuilder insert(final int index, final char value) {
- validateIndex(index);
- ensureCapacity(size + 1);
- System.arraycopy(buffer, index, buffer, index + 1, size - index);
- buffer[index] = value;
- size++;
+ public StrBuilder ensureCapacity(final int capacity) {
+ if (capacity > buffer.length) {
+ final char[] old = buffer;
+ buffer = new char[capacity * 2];
+ System.arraycopy(old, 0, buffer, 0, size);
+ }
return this;
}
/**
- * Inserts the value into this builder.
+ * Checks the contents of this builder against another to see if they
+ * contain the same character content.
*
- * @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
+ * @param obj the object to check, null returns false
+ * @return true if the builders contain the same characters in the same order
*/
- public StrBuilder insert(final int index, final int value) {
- return insert(index, String.valueOf(value));
+ @Override
+ public boolean equals(final Object obj) {
+ return obj instanceof StrBuilder
+ && equals((StrBuilder) obj);
}
/**
- * Inserts the value into this builder.
+ * Checks the contents of this builder against another to see if they
+ * contain the same character content.
*
- * @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
+ * @param other the object to check, null returns false
+ * @return true if the builders contain the same characters in the same order
*/
- public StrBuilder insert(final int index, final long value) {
- return insert(index, String.valueOf(value));
+ public boolean equals(final StrBuilder other) {
+ if (this == other) {
+ return true;
+ }
+ if (other == null) {
+ return false;
+ }
+ if (this.size != other.size) {
+ return false;
+ }
+ final char[] thisBuf = this.buffer;
+ final char[] otherBuf = other.buffer;
+ for (int i = size - 1; i >= 0; i--) {
+ if (thisBuf[i] != otherBuf[i]) {
+ return false;
+ }
+ }
+ return true;
}
/**
- * Inserts the value into this builder.
+ * Checks the contents of this builder against another to see if they
+ * contain the same character content ignoring case.
*
- * @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
+ * @param other the object to check, null returns false
+ * @return true if the builders contain the same characters in the same order
*/
- public StrBuilder insert(final int index, final float value) {
- return insert(index, String.valueOf(value));
+ public boolean equalsIgnoreCase(final StrBuilder other) {
+ if (this == other) {
+ return true;
+ }
+ if (this.size != other.size) {
+ return false;
+ }
+ final char[] thisBuf = this.buffer;
+ final char[] otherBuf = other.buffer;
+ for (int i = size - 1; i >= 0; i--) {
+ final char c1 = thisBuf[i];
+ final char c2 = otherBuf[i];
+ if (c1 != c2 && Character.toUpperCase(c1) != Character.toUpperCase(c2)) {
+ return false;
+ }
+ }
+ return true;
}
/**
- * Inserts the value into this builder.
+ * Copies the character array into the specified array.
*
- * @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
+ * @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 StrBuilder insert(final int index, final double value) {
- return insert(index, String.valueOf(value));
+ 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;
}
- //-----------------------------------------------------------------------
/**
- * Internal method to delete a range without validation.
+ * Copies the character array into the specified array.
*
- * @param startIndex the start index, must be valid
- * @param endIndex the end index (exclusive), must be valid
- * @param len the length, must be valid
+ * @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
*/
- private void deleteImpl(final int startIndex, final int endIndex, final int len) {
- System.arraycopy(buffer, endIndex, buffer, startIndex, size - endIndex);
- size -= len;
+ 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);
}
+ //-----------------------------------------------------------------------
/**
- * Deletes the characters between the two specified indices.
+ * Gets the text to be appended when a new line is added.
*
- * @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
+ * @return The new line text, null means use system default
*/
- public StrBuilder delete(final int startIndex, int endIndex) {
- endIndex = validateRange(startIndex, endIndex);
- final int len = endIndex - startIndex;
- if (len > 0) {
- deleteImpl(startIndex, endIndex, len);
- }
- return this;
+ public String getNewLineText() {
+ return newLine;
}
//-----------------------------------------------------------------------
/**
- * Deletes the character wherever it occurs in the builder.
+ * Gets the text to be appended when null is added.
*
- * @param ch the character to delete
- * @return this, to enable chaining
+ * @return The null text, null means no append
*/
- public StrBuilder deleteAll(final char ch) {
- for (int i = 0; i < size; i++) {
- if (buffer[i] == ch) {
- final int start = i;
- while (++i < size) {
- if (buffer[i] != ch) {
- break;
- }
- }
- final int len = i - start;
- deleteImpl(start, i, len);
- i -= len;
- }
- }
- return this;
+ public String getNullText() {
+ return nullText;
}
/**
- * Deletes the character wherever it occurs in the builder.
+ * Gets a suitable hash code for this builder.
*
- * @param ch the character to delete
- * @return this, to enable chaining
+ * @return a hash code
*/
- public StrBuilder deleteFirst(final char ch) {
- for (int i = 0; i < size; i++) {
- if (buffer[i] == ch) {
- deleteImpl(i, i + 1, 1);
- break;
- }
+ @Override
+ public int hashCode() {
+ final char[] buf = buffer;
+ int hash = 0;
+ for (int i = size - 1; i >= 0; i--) {
+ hash = 31 * hash + buf[i];
}
- return this;
+ return hash;
}
//-----------------------------------------------------------------------
/**
- * Deletes the string wherever it occurs in the builder.
+ * Searches the string builder to find the first reference to the specified char.
*
- * @param str the string to delete, null causes no action
- * @return this, to enable chaining
+ * @param ch the character to find
+ * @return The first index of the character, or -1 if not found
*/
- public StrBuilder deleteAll(final String str) {
- final int len = (str == null ? 0 : str.length());
- if (len > 0) {
- int index = indexOf(str, 0);
- while (index >= 0) {
- deleteImpl(index, index + len, len);
- index = indexOf(str, index);
- }
- }
- return this;
+ public int indexOf(final char ch) {
+ return indexOf(ch, 0);
}
/**
- * Deletes the string wherever it occurs in the builder.
+ * Searches the string builder to find the first reference to the specified char.
*
- * @param str the string to delete, null causes no action
- * @return this, to enable chaining
+ * @param ch the character to find
+ * @param startIndex the index to start at, invalid index rounded to edge
+ * @return The first index of the character, or -1 if not found
*/
- public StrBuilder deleteFirst(final String str) {
- final int len = (str == null ? 0 : str.length());
- if (len > 0) {
- final int index = indexOf(str, 0);
- if (index >= 0) {
- deleteImpl(index, index + len, len);
+ public int indexOf(final char ch, int startIndex) {
+ startIndex = startIndex < 0 ? 0 : startIndex;
+ if (startIndex >= size) {
+ return -1;
+ }
+ final char[] thisBuf = buffer;
+ for (int i = startIndex; i < size; i++) {
+ if (thisBuf[i] == ch) {
+ return i;
}
}
- return this;
+ return -1;
}
- //-----------------------------------------------------------------------
/**
- * Deletes all parts of the builder that the matcher matches.
+ * Searches the string builder to find the first reference to the specified string.
*
- * 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.
+ * Note that a null input string will return -1, whereas the JDK throws an exception.
*
- * @param matcher the matcher to use to find the deletion, null causes no action
- * @return this, to enable chaining
+ * @param str the string to find, null returns -1
+ * @return The first index of the string, or -1 if not found
*/
- public StrBuilder deleteAll(final StrMatcher matcher) {
- return replace(matcher, null, 0, size, -1);
+ public int indexOf(final String str) {
+ return indexOf(str, 0);
}
/**
- * Deletes the first match within the builder using the specified matcher.
+ * Searches the string builder to find the first reference to the specified
+ * string starting searching from the given index.
*
- * 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.
+ * Note that a null input string will return -1, whereas the JDK throws an exception.
*
- * @param matcher the matcher to use to find the deletion, null causes no action
- * @return this, to enable chaining
+ * @param str the string to find, null returns -1
+ * @param startIndex the index to start at, invalid index rounded to edge
+ * @return The first index of the string, or -1 if not found
*/
- public StrBuilder deleteFirst(final StrMatcher matcher) {
- return replace(matcher, null, 0, size, 1);
+ public int indexOf(final String str, int startIndex) {
+ startIndex = startIndex < 0 ? 0 : startIndex;
+ if (str == null || startIndex >= size) {
+ return -1;
+ }
+ final int strLen = str.length();
+ if (strLen == 1) {
+ return indexOf(str.charAt(0), startIndex);
+ }
+ if (strLen == 0) {
+ return startIndex;
+ }
+ if (strLen > size) {
+ return -1;
+ }
+ final char[] thisBuf = buffer;
+ final int len = size - strLen + 1;
+ outer:
+ for (int i = startIndex; i < len; i++) {
+ for (int j = 0; j < strLen; j++) {
+ if (str.charAt(j) != thisBuf[i + j]) {
+ continue outer;
+ }
+ }
+ return i;
+ }
+ return -1;
}
- //-----------------------------------------------------------------------
/**
- * Internal method to delete a range without validation.
+ * Searches the string builder using the matcher to find the first match.
+ *
+ * Matchers can be used to perform advanced searching behavior.
+ * For example you could write a matcher to find the character 'a'
+ * followed by a number.
*
- * @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
+ * @param matcher the matcher to use, null returns -1
+ * @return The first index matched, or -1 if not found
*/
- 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);
- }
+ public int indexOf(final StrMatcher matcher) {
+ return indexOf(matcher, 0);
}
/**
- * Replaces a portion of the string builder with another string.
- * The length of the inserted string does not have to match the removed length.
+ * Searches the string builder using the matcher to find the first
+ * match searching from the given index.
+ *
+ * Matchers can be used to perform advanced searching behavior.
+ * For example you could write a matcher to find the character 'a'
+ * followed by a number.
*
- * @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
+ * @param matcher the matcher to use, null returns -1
+ * @param startIndex the index to start at, invalid index rounded to edge
+ * @return The first index matched, or -1 if not found
*/
- public StrBuilder replace(final int startIndex, int endIndex, final String replaceStr) {
- endIndex = validateRange(startIndex, endIndex);
- final int insertLen = (replaceStr == null ? 0 : replaceStr.length());
- replaceImpl(startIndex, endIndex, endIndex - startIndex, replaceStr, insertLen);
- return this;
+ public int indexOf(final StrMatcher matcher, int startIndex) {
+ startIndex = startIndex < 0 ? 0 : startIndex;
+ if (matcher == null || startIndex >= size) {
+ return -1;
+ }
+ final int len = size;
+ final char[] buf = buffer;
+ for (int i = startIndex; i < len; i++) {
+ if (matcher.isMatch(buf, i, startIndex, len) > 0) {
+ return i;
+ }
+ }
+ return -1;
}
- //-----------------------------------------------------------------------
/**
- * Replaces the search character with the replace character
- * throughout the builder.
+ * Inserts the value into this builder.
*
- * @param search the search character
- * @param replace the replace character
+ * @param index the index to add at, must be valid
+ * @param value the value to insert
* @return this, to enable chaining
+ * @throws IndexOutOfBoundsException if the index is invalid
*/
- public StrBuilder replaceAll(final char search, final char replace) {
- if (search != replace) {
- for (int i = 0; i < size; i++) {
- if (buffer[i] == search) {
- buffer[i] = replace;
- }
- }
+ public StrBuilder insert(int index, final boolean value) {
+ validateIndex(index);
+ if (value) {
+ ensureCapacity(size + 4);
+ System.arraycopy(buffer, index, buffer, index + 4, size - index);
+ buffer[index++] = 't';
+ buffer[index++] = 'r';
+ buffer[index++] = 'u';
+ buffer[index] = 'e';
+ size += 4;
+ } else {
+ ensureCapacity(size + 5);
+ System.arraycopy(buffer, index, buffer, index + 5, size - index);
+ buffer[index++] = 'f';
+ buffer[index++] = 'a';
+ buffer[index++] = 'l';
+ buffer[index++] = 's';
+ buffer[index] = 'e';
+ size += 5;
}
return this;
}
/**
- * Replaces the first instance of the search character with the
- * replace character in the builder.
+ * Inserts the value into this builder.
*
- * @param search the search character
- * @param replace the replace character
+ * @param index the index to add at, must be valid
+ * @param value the value to insert
* @return this, to enable chaining
+ * @throws IndexOutOfBoundsException if the index is invalid
*/
- public StrBuilder 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;
- }
- }
- }
+ public StrBuilder insert(final int index, final char value) {
+ validateIndex(index);
+ ensureCapacity(size + 1);
+ System.arraycopy(buffer, index, buffer, index + 1, size - index);
+ buffer[index] = value;
+ size++;
return this;
}
- //-----------------------------------------------------------------------
/**
- * Replaces the search string with the replace string throughout the builder.
+ * Inserts the character array into this builder.
+ * Inserting null will use the stored null text value.
*
- * @param searchStr the search string, null causes no action to occur
- * @param replaceStr the replace string, null is equivalent to an empty string
+ * @param index the index to add at, must be valid
+ * @param chars the char array to insert
* @return this, to enable chaining
+ * @throws IndexOutOfBoundsException if the index is invalid
*/
- public StrBuilder 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);
- }
+ public StrBuilder insert(final int index, final char[] chars) {
+ validateIndex(index);
+ if (chars == null) {
+ return insert(index, nullText);
+ }
+ final int len = chars.length;
+ if (len > 0) {
+ ensureCapacity(size + len);
+ System.arraycopy(buffer, index, buffer, index + len, size - index);
+ System.arraycopy(chars, 0, buffer, index, len);
+ size += len;
}
return this;
}
/**
- * Replaces the first instance of the search string with the replace string.
+ * Inserts part of the character array into this builder.
+ * Inserting null will use the stored null text value.
*
- * @param searchStr the search string, null causes no action to occur
- * @param replaceStr the replace string, null is equivalent to an empty string
+ * @param index the index to add at, must be valid
+ * @param chars the char array to insert
+ * @param offset the offset into the character array to start at, must be valid
+ * @param length the length of the character array part to copy, must be positive
* @return this, to enable chaining
+ * @throws IndexOutOfBoundsException if any index is invalid
*/
- public StrBuilder 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);
- }
+ public StrBuilder insert(final int index, final char[] chars, final int offset, final int length) {
+ validateIndex(index);
+ if (chars == null) {
+ return insert(index, nullText);
+ }
+ if (offset < 0 || offset > chars.length) {
+ throw new StringIndexOutOfBoundsException("Invalid offset: " + offset);
+ }
+ if (length < 0 || offset + length > chars.length) {
+ throw new StringIndexOutOfBoundsException("Invalid length: " + length);
+ }
+ if (length > 0) {
+ ensureCapacity(size + length);
+ System.arraycopy(buffer, index, buffer, index + length, size - index);
+ System.arraycopy(chars, offset, buffer, index, length);
+ size += length;
}
return this;
}
- //-----------------------------------------------------------------------
/**
- * Replaces all matches within the builder with the replace string.
- *
- * Matchers can be used to perform advanced replace behaviour.
- * For example you could write a matcher to replace all occurrences
- * where the character 'a' is followed by a number.
+ * Inserts the value into this builder.
*
- * @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
+ * @param index the index to add at, must be valid
+ * @param value the value to insert
* @return this, to enable chaining
+ * @throws IndexOutOfBoundsException if the index is invalid
*/
- public StrBuilder replaceAll(final StrMatcher matcher, final String replaceStr) {
- return replace(matcher, replaceStr, 0, size, -1);
+ public StrBuilder insert(final int index, final double value) {
+ return insert(index, String.valueOf(value));
}
/**
- * Replaces the first match within the builder with the replace string.
- *
- * Matchers can be used to perform advanced replace behaviour.
- * For example you could write a matcher to replace
- * where the character 'a' is followed by a number.
+ * Inserts the value into this builder.
*
- * @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
+ * @param index the index to add at, must be valid
+ * @param value the value to insert
* @return this, to enable chaining
+ * @throws IndexOutOfBoundsException if the index is invalid
*/
- public StrBuilder replaceFirst(final StrMatcher matcher, final String replaceStr) {
- return replace(matcher, replaceStr, 0, size, 1);
+ public StrBuilder insert(final int index, final float value) {
+ return insert(index, String.valueOf(value));
}
- // -----------------------------------------------------------------------
/**
- * Advanced search and replaces within the builder using a matcher.
- *
- * Matchers can be used to perform advanced behaviour.
- * For example you could write a matcher to delete all occurrences
- * where the character 'a' is followed by a number.
+ * Inserts the value into this builder.
*
- * @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
+ * @param index the index to add at, must be valid
+ * @param value the value to insert
* @return this, to enable chaining
- * @throws IndexOutOfBoundsException if start index is invalid
+ * @throws IndexOutOfBoundsException if the index is invalid
*/
- public StrBuilder replace(
- final StrMatcher matcher, final String replaceStr,
- final int startIndex, int endIndex, final int replaceCount) {
- endIndex = validateRange(startIndex, endIndex);
- return replaceImpl(matcher, replaceStr, startIndex, endIndex, replaceCount);
+ public StrBuilder insert(final int index, final int value) {
+ return insert(index, String.valueOf(value));
}
/**
- * Replaces within the builder using a matcher.
- *
- * Matchers can be used to perform advanced behaviour.
- * For example you could write a matcher to delete all occurrences
- * where the character 'a' is followed by a number.
+ * Inserts the value into this builder.
*
- * @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
+ * @param index the index to add at, must be valid
+ * @param value the value to insert
* @return this, to enable chaining
- * @throws IndexOutOfBoundsException if any index is invalid
+ * @throws IndexOutOfBoundsException if the index is invalid
*/
- private StrBuilder replaceImpl(
- final StrMatcher matcher, final String replaceStr,
- final int from, int to, int replaceCount) {
- if (matcher == null || size == 0) {
- return this;
- }
- final int replaceLen = (replaceStr == null ? 0 : replaceStr.length());
- for (int i = from; i < to && replaceCount != 0; i++) {
- final char[] buf = buffer;
- final int removeLen = matcher.isMatch(buf, i, from, to);
- if (removeLen > 0) {
- replaceImpl(i, i + removeLen, removeLen, replaceStr, replaceLen);
- to = to - removeLen + replaceLen;
- i = i + replaceLen - 1;
- if (replaceCount > 0) {
- replaceCount--;
- }
- }
- }
- return this;
+ public StrBuilder insert(final int index, final long value) {
+ return insert(index, String.valueOf(value));
}
//-----------------------------------------------------------------------
/**
- * Reverses the string builder placing each character in the opposite index.
+ * Inserts the string representation of an object into this builder.
+ * Inserting null will use the stored null text value.
*
+ * @param index the index to add at, must be valid
+ * @param obj the object to insert
* @return this, to enable chaining
+ * @throws IndexOutOfBoundsException if the index is invalid
*/
- public StrBuilder 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;
+ public StrBuilder insert(final int index, final Object obj) {
+ if (obj == null) {
+ return insert(index, nullText);
}
- return this;
+ return insert(index, obj.toString());
}
- //-----------------------------------------------------------------------
/**
- * Trims the builder by removing characters less than or equal to a space
- * from the beginning and end.
+ * Inserts the string into this builder.
+ * Inserting null will use the stored null text value.
*
+ * @param index the index to add at, must be valid
+ * @param str the string to insert
* @return this, to enable chaining
+ * @throws IndexOutOfBoundsException if the index is invalid
*/
- public StrBuilder 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);
+ public StrBuilder insert(final int index, String str) {
+ validateIndex(index);
+ if (str == null) {
+ str = nullText;
}
- if (pos > 0) {
- delete(0, pos);
+ 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;
}
- //-----------------------------------------------------------------------
/**
- * Checks whether this builder starts with the specified string.
+ * Tests if the string builder is empty (convenience Collections API style method).
*
- * Note that this method handles null input quietly, unlike String.
+ * This method is the same as checking {@link #length()} and is provided to match the
+ * API of Collections.
*
- * @param str the string to search for, null returns false
- * @return true if the builder starts with the string
+ * @return {@code true} if the size is {@code 0}.
*/
- 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;
+ public boolean isEmpty() {
+ return size == 0;
}
/**
- * Checks whether this builder ends with the specified string.
+ * Tests if the string builder is not empty (convenience Collections API style method).
*
- * Note that this method handles null input quietly, unlike String.
+ * This method is the same as checking {@link #length()} and is provided to match the
+ * API of Collections.
*
- * @param str the string to search for, null returns false
- * @return true if the builder ends with the string
+ * @return {@code true} if the size is greater than {@code 0}.
+ * @since 1.10.0
*/
- 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;
+ public boolean isNotEmpty() {
+ return size > 0;
}
//-----------------------------------------------------------------------
/**
- * {@inheritDoc}
+ * Searches the string builder to find the last reference to the specified char.
+ *
+ * @param ch the character to find
+ * @return The last index of the character, or -1 if not found
*/
- @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);
+ public int lastIndexOf(final char ch) {
+ return lastIndexOf(ch, size - 1);
}
/**
- * Extracts a portion of this string builder as a string.
+ * Searches the string builder to find the last reference to the specified char.
*
- * @param start the start index, inclusive, must be valid
- * @return the new string
- * @throws IndexOutOfBoundsException if the index is invalid
+ * @param ch the character to find
+ * @param startIndex the index to start at, invalid index rounded to edge
+ * @return The last index of the character, or -1 if not found
*/
- public String substring(final int start) {
- return substring(start, size);
+ public int lastIndexOf(final char ch, int startIndex) {
+ startIndex = startIndex >= size ? size - 1 : startIndex;
+ if (startIndex < 0) {
+ return -1;
+ }
+ for (int i = startIndex; i >= 0; i--) {
+ if (buffer[i] == ch) {
+ return i;
+ }
+ }
+ return -1;
}
/**
- * Extracts a portion of this string builder as a string.
+ * Searches the string builder to find the last reference to the specified string.
*
- * Note: This method treats an endIndex greater than the length of the
- * builder as equal to the length of the builder, and continues
- * without error, unlike StringBuffer or String.
+ * Note that a null input string will return -1, whereas the JDK throws an exception.
*
- * @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
+ * @param str the string to find, null returns -1
+ * @return The last index of the string, or -1 if not found
*/
- public String substring(final int startIndex, int endIndex) {
- endIndex = validateRange(startIndex, endIndex);
- return new String(buffer, startIndex, endIndex - startIndex);
+ public int lastIndexOf(final String str) {
+ return lastIndexOf(str, size - 1);
}
/**
- * Extracts the leftmost characters from the string builder without
- * throwing an exception.
+ * Searches the string builder to find the last reference to the specified
+ * string starting searching from the given index.
*
- * This method extracts the left
+ * Matchers can be used to perform advanced searching behavior.
+ * For example you could write a matcher to find the character 'a'
+ * followed by a number.
+ *
+ * @param matcher the matcher to use, null returns -1
+ * @return The last index matched, or -1 if not found
+ */
+ public int lastIndexOf(final StrMatcher matcher) {
+ return lastIndexOf(matcher, size);
+ }
+
+ /**
+ * Searches the string builder using the matcher to find the last
+ * match searching from the given index.
+ *
+ * Matchers can be used to perform advanced searching behavior.
+ * For example you could write a matcher to find the character 'a'
+ * followed by a number.
+ *
+ * @param matcher the matcher to use, null returns -1
+ * @param startIndex the index to start at, invalid index rounded to edge
+ * @return The last index matched, or -1 if not found
+ */
+ public int lastIndexOf(final StrMatcher matcher, int startIndex) {
+ startIndex = startIndex >= size ? size - 1 : startIndex;
+ if (matcher == null || startIndex < 0) {
+ return -1;
+ }
+ final char[] buf = buffer;
+ final int endIndex = startIndex + 1;
+ for (int i = startIndex; i >= 0; i--) {
+ if (matcher.isMatch(buf, i, 0, endIndex) > 0) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Extracts the leftmost characters from the string builder without
* throwing an exception.
*
- * This method extracts the right
- * This method extracts
- * Matchers can be used to perform advanced searching behaviour.
- * For example you could write a matcher to search for the character
- * 'a' followed by a number.
+ * Matchers can be used to perform advanced behavior.
+ * 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, null returns -1
- * @return true if the matcher finds a match in the builder
+ * @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 boolean contains(final StrMatcher matcher) {
- return indexOf(matcher, 0) >= 0;
+ public StrBuilder replace(
+ final StrMatcher matcher, final String replaceStr,
+ final int startIndex, int endIndex, final int replaceCount) {
+ endIndex = validateRange(startIndex, endIndex);
+ return replaceImpl(matcher, replaceStr, startIndex, endIndex, replaceCount);
}
//-----------------------------------------------------------------------
/**
- * Searches the string builder to find the first reference to the specified char.
+ * Replaces the search character with the replace character
+ * throughout the builder.
*
- * @param ch the character to find
- * @return the first index of the character, or -1 if not found
+ * @param search the search character
+ * @param replace the replace character
+ * @return this, to enable chaining
*/
- public int indexOf(final char ch) {
- return indexOf(ch, 0);
+ public StrBuilder replaceAll(final char search, final char replace) {
+ if (search != replace) {
+ for (int i = 0; i < size; i++) {
+ if (buffer[i] == search) {
+ buffer[i] = replace;
+ }
+ }
+ }
+ return this;
}
+ //-----------------------------------------------------------------------
/**
- * Searches the string builder to find the first reference to the specified char.
+ * Replaces the search string with the replace string throughout the builder.
*
- * @param ch the character to find
- * @param startIndex the index to start at, invalid index rounded to edge
- * @return the first index of the character, or -1 if not found
+ * @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 int indexOf(final char ch, int startIndex) {
- startIndex = (startIndex < 0 ? 0 : startIndex);
- if (startIndex >= size) {
- return -1;
- }
- final char[] thisBuf = buffer;
- for (int i = startIndex; i < size; i++) {
- if (thisBuf[i] == ch) {
- return i;
+ public StrBuilder replaceAll(final String searchStr, final String replaceStr) {
+ final int searchLen = searchStr == null ? 0 : searchStr.length();
+ if (searchLen > 0) {
+ final int replaceLen = replaceStr == null ? 0 : replaceStr.length();
+ int index = indexOf(searchStr, 0);
+ while (index >= 0) {
+ replaceImpl(index, index + searchLen, searchLen, replaceStr, replaceLen);
+ index = indexOf(searchStr, index + replaceLen);
}
}
- return -1;
+ return this;
}
+ //-----------------------------------------------------------------------
/**
- * Searches the string builder to find the first reference to the specified string.
+ * Replaces all matches within the builder with the replace string.
*
- * Note that a null input string will return -1, whereas the JDK throws an exception.
+ * Matchers can be used to perform advanced replace behavior.
+ * For example you could write a matcher to replace all occurrences
+ * where the character 'a' is followed by a number.
*
- * @param str the string to find, null returns -1
- * @return the first index of the string, or -1 if not found
+ * @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 int indexOf(final String str) {
- return indexOf(str, 0);
+ public StrBuilder replaceAll(final StrMatcher matcher, final String replaceStr) {
+ return replace(matcher, replaceStr, 0, size, -1);
}
/**
- * Searches the string builder to find the first reference to the specified
- * string starting searching from the given index.
- *
- * Note that a null input string will return -1, whereas the JDK throws an exception.
+ * Replaces the first instance of the search character with the
+ * replace character in the builder.
*
- * @param str the string to find, null returns -1
- * @param startIndex the index to start at, invalid index rounded to edge
- * @return the first index of the string, or -1 if not found
+ * @param search the search character
+ * @param replace the replace character
+ * @return this, to enable chaining
*/
- public int indexOf(final String str, int startIndex) {
- startIndex = (startIndex < 0 ? 0 : startIndex);
- if (str == null || startIndex >= size) {
- return -1;
- }
- final int strLen = str.length();
- if (strLen == 1) {
- return indexOf(str.charAt(0), startIndex);
- }
- if (strLen == 0) {
- return startIndex;
- }
- if (strLen > size) {
- return -1;
- }
- final char[] thisBuf = buffer;
- final int len = size - strLen + 1;
- outer:
- for (int i = startIndex; i < len; i++) {
- for (int j = 0; j < strLen; j++) {
- if (str.charAt(j) != thisBuf[i + j]) {
- continue outer;
+ public StrBuilder replaceFirst(final char search, final char replace) {
+ if (search != replace) {
+ for (int i = 0; i < size; i++) {
+ if (buffer[i] == search) {
+ buffer[i] = replace;
+ break;
}
}
- return i;
}
- return -1;
+ return this;
}
/**
- * Searches the string builder using the matcher to find the first match.
- *
- * Matchers can be used to perform advanced searching behaviour.
- * For example you could write a matcher to find the character 'a'
- * followed by a number.
+ * Replaces the first instance of the search string with the replace string.
*
- * @param matcher the matcher to use, null returns -1
- * @return the first index matched, or -1 if not found
+ * @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 int indexOf(final StrMatcher matcher) {
- return indexOf(matcher, 0);
+ public StrBuilder replaceFirst(final String searchStr, final String replaceStr) {
+ final int searchLen = searchStr == null ? 0 : searchStr.length();
+ if (searchLen > 0) {
+ final int index = indexOf(searchStr, 0);
+ if (index >= 0) {
+ final int replaceLen = replaceStr == null ? 0 : replaceStr.length();
+ replaceImpl(index, index + searchLen, searchLen, replaceStr, replaceLen);
+ }
+ }
+ return this;
}
/**
- * Searches the string builder using the matcher to find the first
- * match searching from the given index.
+ * Replaces the first match within the builder with the replace string.
*
- * Matchers can be used to perform advanced searching behaviour.
- * For example you could write a matcher to find the character 'a'
- * followed by a number.
+ * Matchers can be used to perform advanced replace behavior.
+ * For example you could write a matcher to replace
+ * where the character 'a' is followed by a number.
*
- * @param matcher the matcher to use, null returns -1
- * @param startIndex the index to start at, invalid index rounded to edge
- * @return the first index matched, or -1 if not found
+ * @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 int indexOf(final StrMatcher matcher, int startIndex) {
- startIndex = (startIndex < 0 ? 0 : startIndex);
- if (matcher == null || startIndex >= size) {
- return -1;
- }
- final int len = size;
- final char[] buf = buffer;
- for (int i = startIndex; i < len; i++) {
- if (matcher.isMatch(buf, i, startIndex, len) > 0) {
- return i;
- }
- }
- return -1;
+ public StrBuilder replaceFirst(final StrMatcher matcher, final String replaceStr) {
+ return replace(matcher, replaceStr, 0, size, 1);
}
//-----------------------------------------------------------------------
/**
- * Searches the string builder to find the last reference to the specified char.
+ * Internal method to delete a range without validation.
*
- * @param ch the character to find
- * @return the last index of the character, or -1 if not found
+ * @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
*/
- public int lastIndexOf(final char ch) {
- return lastIndexOf(ch, size - 1);
+ 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);
+ }
}
/**
- * Searches the string builder to find the last reference to the specified char.
+ * Replaces within the builder using a matcher.
+ *
+ * Matchers can be used to perform advanced behavior.
+ * For example you could write a matcher to delete all occurrences
+ * where the character 'a' is followed by a number.
*
- * @param ch the character to find
- * @param startIndex the index to start at, invalid index rounded to edge
- * @return the last index of the character, or -1 if not found
+ * @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
*/
- public int lastIndexOf(final char ch, int startIndex) {
- startIndex = (startIndex >= size ? size - 1 : startIndex);
- if (startIndex < 0) {
- return -1;
+ private StrBuilder replaceImpl(
+ final StrMatcher matcher, final String replaceStr,
+ final int from, int to, int replaceCount) {
+ if (matcher == null || size == 0) {
+ return this;
}
- for (int i = startIndex; i >= 0; i--) {
- if (buffer[i] == ch) {
- return i;
+ 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 -1;
+ return this;
}
+ //-----------------------------------------------------------------------
/**
- * Searches the string builder to find the last reference to the specified string.
- *
- * Note that a null input string will return -1, whereas the JDK throws an exception.
+ * Reverses the string builder placing each character in the opposite index.
*
- * @param str the string to find, null returns -1
- * @return the last index of the string, or -1 if not found
+ * @return this, to enable chaining
*/
- public int lastIndexOf(final String str) {
- return lastIndexOf(str, size - 1);
+ public StrBuilder reverse() {
+ if (size == 0) {
+ return this;
+ }
+
+ final int half = size / 2;
+ final char[] buf = buffer;
+ for (int leftIdx = 0, rightIdx = size - 1; leftIdx < half; leftIdx++, rightIdx--) {
+ final char swap = buf[leftIdx];
+ buf[leftIdx] = buf[rightIdx];
+ buf[rightIdx] = swap;
+ }
+ return this;
}
/**
- * Searches the string builder to find the last reference to the specified
- * string starting searching from the given index.
+ * Extracts the rightmost characters from the string builder without
+ * throwing an exception.
*
- * Note that a null input string will return -1, whereas the JDK throws an exception.
+ * This method extracts the right {@code length} characters from
+ * the builder. If this many characters are not available, the whole
+ * builder is returned. Thus the returned string may be shorter than the
+ * length requested.
*
- * @param str the string to find, null returns -1
- * @param startIndex the index to start at, invalid index rounded to edge
- * @return the last index of the string, or -1 if not found
+ * @param length the number of characters to extract, negative returns empty string
+ * @return The new string
*/
- public int lastIndexOf(final String str, int startIndex) {
- startIndex = (startIndex >= size ? size - 1 : startIndex);
- if (str == null || startIndex < 0) {
- return -1;
- }
- final int strLen = str.length();
- if (strLen > 0 && strLen <= size) {
- if (strLen == 1) {
- return lastIndexOf(str.charAt(0), startIndex);
- }
-
- outer:
- for (int i = startIndex - strLen + 1; i >= 0; i--) {
- for (int j = 0; j < strLen; j++) {
- if (str.charAt(j) != buffer[i + j]) {
- continue outer;
- }
- }
- return i;
- }
-
- } else if (strLen == 0) {
- return startIndex;
+ public String rightString(final int length) {
+ if (length <= 0) {
+ return StringUtils.EMPTY;
+ } else if (length >= size) {
+ return new String(buffer, 0, size);
+ } else {
+ return new String(buffer, size - length, length);
}
- return -1;
}
/**
- * Searches the string builder using the matcher to find the last match.
- *
- * Matchers can be used to perform advanced searching behaviour.
- * For example you could write a matcher to find the character 'a'
- * followed by a number.
+ * Sets the character at the specified index.
*
- * @param matcher the matcher to use, null returns -1
- * @return the last index matched, or -1 if not found
+ * @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 int lastIndexOf(final StrMatcher matcher) {
- return lastIndexOf(matcher, size);
+ public StrBuilder setCharAt(final int index, final char ch) {
+ if (index < 0 || index >= length()) {
+ throw new StringIndexOutOfBoundsException(index);
+ }
+ buffer[index] = ch;
+ return this;
}
/**
- * Searches the string builder using the matcher to find the last
- * match searching from the given index.
- *
- * Matchers can be used to perform advanced searching behaviour.
- * For example you could write a matcher to find the character 'a'
- * followed by a number.
+ * Updates the length of the builder by either dropping the last characters
+ * or adding filler of Unicode zero.
*
- * @param matcher the matcher to use, null returns -1
- * @param startIndex the index to start at, invalid index rounded to edge
- * @return the last index matched, or -1 if not found
+ * @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 int lastIndexOf(final StrMatcher matcher, int startIndex) {
- startIndex = (startIndex >= size ? size - 1 : startIndex);
- if (matcher == null || startIndex < 0) {
- return -1;
+ public StrBuilder setLength(final int length) {
+ if (length < 0) {
+ throw new StringIndexOutOfBoundsException(length);
}
- final char[] buf = buffer;
- final int endIndex = startIndex + 1;
- for (int i = startIndex; i >= 0; i--) {
- if (matcher.isMatch(buf, i, 0, endIndex) > 0) {
- return i;
+ 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 -1;
+ return this;
}
- //-----------------------------------------------------------------------
/**
- * Creates a tokenizer that can tokenize the contents of this builder.
- *
- * This method allows the contents of this builder to be tokenized.
- * The tokenizer will be setup by default to tokenize on space, tab,
- * newline and formfeed (as per StringTokenizer). These values can be
- * changed on the tokenizer class, before retrieving the tokens.
- *
- * The returned tokenizer is linked to this builder. You may intermix
- * calls to the builder and tokenizer within certain limits, however
- * there is no synchronization. Once the tokenizer has been used once,
- * it must be {@link StrTokenizer#reset() reset} to pickup the latest
- * changes in the builder. For example:
- *
- * Calling {@link StrTokenizer#reset(String)} or {@link StrTokenizer#reset(char[])}
- * with a non-null value will break the link with the builder.
+ * Sets the text to be appended when a new line is added.
*
- * @return a tokenizer that is linked to this builder
+ * @param newLine the new line text, null means use system default
+ * @return this, to enable chaining
*/
- public StrTokenizer asTokenizer() {
- return new StrBuilderTokenizer();
+ public StrBuilder setNewLineText(final String newLine) {
+ this.newLine = newLine;
+ return this;
}
- //-----------------------------------------------------------------------
/**
- * Gets the contents of this builder as a Reader.
- *
- * This method allows the contents of the builder to be read
- * using any standard method that expects a Reader.
- *
- * To use, simply create a
- * The internal character array is shared between the builder and the reader.
- * This allows you to append to the builder after creating the reader,
- * and the changes will be picked up.
- * Note however, that no synchronization occurs, so you must perform
- * all operations with the builder and the reader in one thread.
- *
- * The returned reader supports marking, and ignores the flush method.
+ * Sets the text to be appended when null is added.
*
- * @return a reader that reads from this builder
+ * @param nullText the null text, null means no append
+ * @return this, to enable chaining
*/
- public Reader asReader() {
- return new StrBuilderReader();
+ public StrBuilder setNullText(String nullText) {
+ if (nullText != null && nullText.isEmpty()) {
+ nullText = null;
+ }
+ this.nullText = nullText;
+ return this;
}
//-----------------------------------------------------------------------
/**
- * Gets this builder as a Writer that can be written to.
- *
- * This method allows you to populate the contents of the builder
- * using any standard method that takes a Writer.
- *
- * To use, simply create a
- * The internal character array is shared between the builder and the writer.
- * This allows you to intermix calls that append to the builder and
- * write using the writer and the changes will be occur correctly.
- * Note however, that no synchronization occurs, so you must perform
- * all operations with the builder and the writer in one thread.
+ * Gets the length of the string builder.
*
- * The returned writer ignores the close and flush methods.
+ * This method is the same as {@link #length()} and is provided to match the
+ * API of Collections.
*
- * @return a writer that populates this builder
+ * @return The length
*/
- public Writer asWriter() {
- return new StrBuilderWriter();
+ public int size() {
+ return size;
}
+ //-----------------------------------------------------------------------
/**
- * Appends current contents of this
- * This method tries to avoid doing any extra copies of contents.
- *
- * @param appendable the appendable to append data to
- * @throws IOException if an I/O error occurs
+ * Note that this method handles null input quietly, unlike String.
*
- * @see #readFrom(Readable)
+ * @param str the string to search for, null returns false
+ * @return true if the builder starts with the string
*/
- public void appendTo(final Appendable appendable) throws IOException {
- if (appendable instanceof Writer) {
- ((Writer) appendable).write(buffer, 0, size);
- } else if (appendable instanceof StringBuilder) {
- ((StringBuilder) appendable).append(buffer, 0, size);
- } else if (appendable instanceof StringBuffer) {
- ((StringBuffer) appendable).append(buffer, 0, size);
- } else if (appendable instanceof CharBuffer) {
- ((CharBuffer) appendable).put(buffer, 0, size);
- } else {
- appendable.append(this);
+ public boolean startsWith(final String str) {
+ if (str == null) {
+ return false;
}
- }
-
- /**
- * Checks the contents of this builder against another to see if they
- * contain the same character content ignoring case.
- *
- * @param other the object to check, null returns false
- * @return true if the builders contain the same characters in the same order
- */
- public boolean equalsIgnoreCase(final StrBuilder other) {
- if (this == other) {
+ final int len = str.length();
+ if (len == 0) {
return true;
}
- if (this.size != other.size) {
+ if (len > size) {
return false;
}
- final char[] thisBuf = this.buffer;
- final char[] otherBuf = other.buffer;
- for (int i = size - 1; i >= 0; i--) {
- final char c1 = thisBuf[i];
- final char c2 = otherBuf[i];
- if (c1 != c2 && Character.toUpperCase(c1) != Character.toUpperCase(c2)) {
+ for (int i = 0; i < len; i++) {
+ if (buffer[i] != str.charAt(i)) {
return false;
}
}
return true;
}
+ //-----------------------------------------------------------------------
/**
- * Checks the contents of this builder against another to see if they
- * contain the same character content.
+ * {@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 other the object to check, null returns false
- * @return true if the builders contain the same characters in the same order
+ * @param start the start index, inclusive, must be valid
+ * @return The new string
+ * @throws IndexOutOfBoundsException if the index is invalid
*/
- public boolean equals(final StrBuilder other) {
- if (this == other) {
- return true;
- }
- if (other == null) {
- return false;
- }
- if (this.size != other.size) {
- return false;
- }
- final char[] thisBuf = this.buffer;
- final char[] otherBuf = other.buffer;
- for (int i = size - 1; i >= 0; i--) {
- if (thisBuf[i] != otherBuf[i]) {
- return false;
- }
- }
- return true;
+ public String substring(final int start) {
+ return substring(start, size);
}
/**
- * Checks the contents of this builder against another to see if they
- * contain the same character content.
+ * Extracts a portion of this string builder as a string.
+ *
+ * Note: This method treats an endIndex greater than the length of the
+ * builder as equal to the length of the builder, and continues
+ * without error, unlike StringBuffer or String.
*
- * @param obj the object to check, null returns false
- * @return true if the builders contain the same characters in the same order
+ * @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
*/
- @Override
- public boolean equals(final Object obj) {
- return obj instanceof StrBuilder
- && equals((StrBuilder) obj);
+ public String substring(final int startIndex, int endIndex) {
+ endIndex = validateRange(startIndex, endIndex);
+ return new String(buffer, startIndex, endIndex - startIndex);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Copies the builder's character array into a new character array.
+ *
+ * @return a new array that represents the contents of the builder
+ */
+ public char[] toCharArray() {
+ if (size == 0) {
+ return ArrayUtils.EMPTY_CHAR_ARRAY;
+ }
+ final char[] chars = new char[size];
+ System.arraycopy(buffer, 0, chars, 0, size);
+ return chars;
}
/**
- * Gets a suitable hash code for this builder.
+ * Copies part of the builder's character array into a new character array.
*
- * @return a hash code
+ * @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)
*/
- @Override
- public int hashCode() {
- final char[] buf = buffer;
- int hash = 0;
- for (int i = size - 1; i >= 0; i--) {
- hash = 31 * hash + buf[i];
+ public char[] toCharArray(final int startIndex, int endIndex) {
+ endIndex = validateRange(startIndex, endIndex);
+ final int len = endIndex - startIndex;
+ if (len == 0) {
+ return ArrayUtils.EMPTY_CHAR_ARRAY;
}
- return hash;
+ final char[] chars = new char[len];
+ System.arraycopy(buffer, startIndex, chars, 0, len);
+ return chars;
}
//-----------------------------------------------------------------------
@@ -2805,7 +2979,7 @@ public int hashCode() {
* Note that unlike StringBuffer, the string version returned is
* independent of the string builder.
*
- * @return the builder as a String
+ * @return The builder as a String
*/
@Override
public String toString() {
@@ -2816,7 +2990,7 @@ public String toString() {
* Gets a StringBuffer version of the string builder, creating a
* new instance each time the method is called.
*
- * @return the builder as a StringBuffer
+ * @return The builder as a StringBuffer
*/
public StringBuffer toStringBuffer() {
return new StringBuffer(size).append(buffer, 0, size);
@@ -2826,43 +3000,39 @@ public StringBuffer toStringBuffer() {
* Gets a StringBuilder version of the string builder, creating a
* new instance each time the method is called.
*
- * @return the builder as a StringBuilder
+ * @return The builder as a StringBuilder
*/
public StringBuilder toStringBuilder() {
return new StringBuilder(size).append(buffer, 0, size);
}
- /**
- * Implement the {@link Builder} interface.
- * @return the builder as a String
- * @see #toString()
- */
- @Override
- public String build() {
- return toString();
- }
-
//-----------------------------------------------------------------------
/**
- * Validates parameters defining a range of the builder.
+ * Trims the builder by removing characters less than or equal to a space
+ * from the beginning and end.
*
- * @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
+ * @return this, to enable chaining
*/
- protected int validateRange(final int startIndex, int endIndex) {
- if (startIndex < 0) {
- throw new StringIndexOutOfBoundsException(startIndex);
+ public StrBuilder trim() {
+ if (size == 0) {
+ return this;
}
- if (endIndex > size) {
- endIndex = size;
+ int len = size;
+ final char[] buf = buffer;
+ int pos = 0;
+ while (pos < len && buf[pos] <= ' ') {
+ pos++;
}
- if (startIndex > endIndex) {
- throw new StringIndexOutOfBoundsException("end < start");
+ while (pos < len && buf[len - 1] <= ' ') {
+ len--;
}
- return endIndex;
+ if (len < size) {
+ delete(len, size);
+ }
+ if (pos > 0) {
+ delete(0, pos);
+ }
+ return this;
}
/**
@@ -2879,183 +3049,25 @@ protected void validateIndex(final int index) {
//-----------------------------------------------------------------------
/**
- * Inner class to allow StrBuilder to operate as a tokenizer.
- */
- class StrBuilderTokenizer extends StrTokenizer {
-
- /**
- * Default constructor.
- */
- StrBuilderTokenizer() {
- super();
- }
-
- /** {@inheritDoc} */
- @Override
- protected List
@@ -33,8 +36,10 @@
*
* @param
- * The internal implementation may use any mechanism to return the value. The simplest implementation is to use a
- * Map. However, virtually any implementation is possible.
- *
- * For example, it would be possible to implement a lookup that used the key as a primary key, and looked up the
- * value on demand from the database Or, a numeric based implementation could be created that treats the key as an
- * integer, increments the value and return the result as a string - converting 1 to 2, 15 to 16 etc.
- *
- * The {@link #lookup(String)} method always returns a String, regardless of the underlying data, by converting it
- * as necessary. For example:
- *
- *
* This method is called to check for a match.
- * The parameter
* The character array may be larger than the active area to be matched.
* Only values in the buffer between the specified indices may be accessed.
*
* The matching code may check one character or many.
- * It may check characters preceding
* It must return zero for no match, or a positive number if a match was found.
@@ -237,28 +249,28 @@ protected StrMatcher() {
* @param pos the starting position for the match, valid for buffer
* @param bufferStart the first active index in the buffer, valid for buffer
* @param bufferEnd the end index (exclusive) of the active buffer, valid for buffer
- * @return the number of matching characters, zero for no match
+ * @return The number of matching characters, or zero if there is no match
*/
public abstract int isMatch(char[] buffer, int pos, int bufferStart, int bufferEnd);
/**
- * Returns the number of matching characters, zero for no match.
+ * Returns the number of matching characters, or zero if there is no match.
*
* This method is called to check for a match.
- * The parameter
* The matching code may check one character or many.
- * It may check characters preceding
* It must return zero for no match, or a positive number if a match was found.
* The number indicates the number of characters that matched.
*
* @param buffer the text content to match against, do not change
* @param pos the starting position for the match, valid for buffer
- * @return the number of matching characters, zero for no match
+ * @return The number of matching characters, or zero if there is no match
*/
public int isMatch(final char[] buffer, final int pos) {
return isMatch(buffer, pos, 0, buffer.length);
@@ -278,19 +290,18 @@ static final class CharSetMatcher extends StrMatcher {
* @param chars the characters to match, must not be null
*/
CharSetMatcher(final char[] chars) {
- super();
this.chars = chars.clone();
Arrays.sort(this.chars);
}
/**
- * Returns whether or not the given character matches.
+ * Returns {@code 1} if there is a match, or {@code 0} if there is no match.
*
* @param buffer the text content to match against, do not change
* @param pos the starting position for the match, valid for buffer
* @param bufferStart the first active index in the buffer, valid for buffer
* @param bufferEnd the end index of the active buffer, valid for buffer
- * @return the number of matching characters, zero for no match
+ * @return The number of matching characters, or zero if there is no match
*/
@Override
public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) {
@@ -312,18 +323,17 @@ static final class CharMatcher extends StrMatcher {
* @param ch the character to match
*/
CharMatcher(final char ch) {
- super();
this.ch = ch;
}
/**
- * Returns whether or not the given character matches.
+ * Returns {@code 1} if there is a match, or {@code 0} if there is no match.
*
* @param buffer the text content to match against, do not change
* @param pos the starting position for the match, valid for buffer
* @param bufferStart the first active index in the buffer, valid for buffer
* @param bufferEnd the end index of the active buffer, valid for buffer
- * @return the number of matching characters, zero for no match
+ * @return The number of matching characters, or zero if there is no match
*/
@Override
public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) {
@@ -345,18 +355,17 @@ static final class StringMatcher extends StrMatcher {
* @param str the string to match, must not be null
*/
StringMatcher(final String str) {
- super();
chars = str.toCharArray();
}
/**
- * Returns whether or not the given text matches the stored string.
+ * Returns the number of matching characters, or zero if there is no match.
*
* @param buffer the text content to match against, do not change
* @param pos the starting position for the match, valid for buffer
* @param bufferStart the first active index in the buffer, valid for buffer
* @param bufferEnd the end index of the active buffer, valid for buffer
- * @return the number of matching characters, zero for no match
+ * @return The number of matching characters, or zero if there is no match
*/
@Override
public int isMatch(final char[] buffer, int pos, final int bufferStart, final int bufferEnd) {
@@ -386,20 +395,19 @@ public String toString() {
static final class NoMatcher extends StrMatcher {
/**
- * Constructs a new instance of
* This class takes a piece of text and substitutes all the variables within it.
- * The default definition of a variable is
* Variable values are typically resolved from a map, but could also be resolved
@@ -44,12 +44,12 @@
* Typical usage of this class follows the following pattern: First an instance is created
* and initialized with the map that contains the values for the available variables.
* If a prefix and/or suffix for variables should be used other than the default ones,
- * the appropriate settings can be performed. After that the This class is not thread safe.
- * The variable default value delimiter is the character or characters that delimite the
+ * The variable default value delimiter is the character or characters that delimit the
* variable name and the variable default value. This delimiter is expressed in terms of a matcher
* allowing advanced variable default value delimiter matches.
*
* If it returns null, then the variable default value resolution is disabled.
*
- * @return the variable default value delimiter matcher in use, may be null
+ * @return The variable default value delimiter matcher in use, may be null
*/
public StrMatcher getValueDelimiterMatcher() {
return valueDelimiterMatcher;
@@ -1106,11 +1118,11 @@ public StrMatcher getValueDelimiterMatcher() {
/**
* Sets the variable default value delimiter matcher to use.
*
- * The variable default value delimiter is the character or characters that delimite the
+ * The variable default value delimiter is the character or characters that delimit the
* variable name and the variable default value. This delimiter is expressed in terms of a matcher
* allowing advanced variable default value delimiter matches.
*
- * If the
- * The variable default value delimiter is the character or characters that delimite the
+ * The variable default value delimiter is the character or characters that delimit the
* variable name and the variable default value. This method allows a single character
* variable default value delimiter to be easily set.
*
@@ -1138,18 +1150,18 @@ public StrSubstitutor setValueDelimiter(final char valueDelimiter) {
/**
* Sets the variable default value delimiter to use.
*
- * The variable default value delimiter is the character or characters that delimite the
+ * The variable default value delimiter is the character or characters that delimit the
* variable name and the variable default value. This method allows a string
* variable default value delimiter to be easily set.
*
- * If the
* The input String is split into a number of tokens.
* Each token is separated from the next String by a delimiter.
@@ -54,11 +57,9 @@
* " a, b , c " - Three tokens "a","b","c" (default CSV processing trims whitespace)
* "a, ", b ,", c" - Three tokens "a, " , " b ", ", c" (quoted text untouched)
*
*
- * This tokenizer has the following properties and options:
- *
- *
* This constructor is normally used with {@link #reset(String)}.
*/
public StrTokenizer() {
- super();
this.chars = null;
}
/**
- * Constructs a tokenizer splitting on space, tab, newline and formfeed
+ * Constructs a tokenizer splitting on space, tab, newline and form feed
* as per StringTokenizer.
*
- * @param input the string which is to be parsed
+ * @param input the string which is to be parsed, not cloned
*/
- public StrTokenizer(final String input) {
- super();
- if (input != null) {
- chars = input.toCharArray();
+ public StrTokenizer(final char[] input) {
+ if (input == null) {
+ this.chars = null;
} else {
- chars = null;
+ this.chars = input.clone();
}
}
/**
- * Constructs a tokenizer splitting on the specified delimiter character.
+ * Constructs a tokenizer splitting on the specified character.
*
- * @param input the string which is to be parsed
- * @param delim the field delimiter character
+ * @param input the string which is to be parsed, not cloned
+ * @param delim the field delimiter character
*/
- public StrTokenizer(final String input, final char delim) {
+ public StrTokenizer(final char[] input, final char delim) {
this(input);
setDelimiterChar(delim);
}
/**
- * Constructs a tokenizer splitting on the specified delimiter string.
+ * Constructs a tokenizer splitting on the specified delimiter character
+ * and handling quotes using the specified quote character.
*
- * @param input the string which is to be parsed
- * @param delim the field delimiter string
+ * @param input the string which is to be parsed, not cloned
+ * @param delim the field delimiter character
+ * @param quote the field quoted string character
*/
- public StrTokenizer(final String input, final String delim) {
- this(input);
- setDelimiterString(delim);
+ public StrTokenizer(final char[] input, final char delim, final char quote) {
+ this(input, delim);
+ setQuoteChar(quote);
}
/**
- * Constructs a tokenizer splitting using the specified delimiter matcher.
+ * Constructs a tokenizer splitting on the specified string.
*
- * @param input the string which is to be parsed
- * @param delim the field delimiter matcher
+ * @param input the string which is to be parsed, not cloned
+ * @param delim the field delimiter string
*/
- public StrTokenizer(final String input, final StrMatcher delim) {
+ public StrTokenizer(final char[] input, final String delim) {
this(input);
- setDelimiterMatcher(delim);
+ setDelimiterString(delim);
}
/**
- * Constructs a tokenizer splitting on the specified delimiter character
- * and handling quotes using the specified quote character.
+ * Constructs a tokenizer splitting using the specified delimiter matcher.
*
- * @param input the string which is to be parsed
- * @param delim the field delimiter character
- * @param quote the field quoted string character
+ * @param input the string which is to be parsed, not cloned
+ * @param delim the field delimiter matcher
*/
- public StrTokenizer(final String input, final char delim, final char quote) {
- this(input, delim);
- setQuoteChar(quote);
+ public StrTokenizer(final char[] input, final StrMatcher delim) {
+ this(input);
+ setDelimiterMatcher(delim);
}
/**
* Constructs a tokenizer splitting using the specified delimiter matcher
* and handling quotes using the specified quote matcher.
*
- * @param input the string which is to be parsed
- * @param delim the field delimiter matcher
- * @param quote the field quoted string matcher
+ * @param input the string which is to be parsed, not cloned
+ * @param delim the field delimiter character
+ * @param quote the field quoted string character
*/
- public StrTokenizer(final String input, final StrMatcher delim, final StrMatcher quote) {
+ public StrTokenizer(final char[] input, final StrMatcher delim, final StrMatcher quote) {
this(input, delim);
setQuoteMatcher(quote);
}
/**
- * Constructs a tokenizer splitting on space, tab, newline and formfeed
+ * Constructs a tokenizer splitting on space, tab, newline and form feed
* as per StringTokenizer.
*
- * @param input the string which is to be parsed, not cloned
+ * @param input the string which is to be parsed
*/
- public StrTokenizer(final char[] input) {
- super();
- if (input == null) {
- this.chars = null;
+ public StrTokenizer(final String input) {
+ if (input != null) {
+ chars = input.toCharArray();
} else {
- this.chars = input.clone();
+ chars = null;
}
}
/**
- * Constructs a tokenizer splitting on the specified character.
+ * Constructs a tokenizer splitting on the specified delimiter character.
*
- * @param input the string which is to be parsed, not cloned
- * @param delim the field delimiter character
+ * @param input the string which is to be parsed
+ * @param delim the field delimiter character
*/
- public StrTokenizer(final char[] input, final char delim) {
+ public StrTokenizer(final String input, final char delim) {
this(input);
setDelimiterChar(delim);
}
/**
- * Constructs a tokenizer splitting on the specified string.
+ * Constructs a tokenizer splitting on the specified delimiter character
+ * and handling quotes using the specified quote character.
*
- * @param input the string which is to be parsed, not cloned
- * @param delim the field delimiter string
+ * @param input the string which is to be parsed
+ * @param delim the field delimiter character
+ * @param quote the field quoted string character
*/
- public StrTokenizer(final char[] input, final String delim) {
- this(input);
- setDelimiterString(delim);
+ public StrTokenizer(final String input, final char delim, final char quote) {
+ this(input, delim);
+ setQuoteChar(quote);
}
/**
- * Constructs a tokenizer splitting using the specified delimiter matcher.
+ * Constructs a tokenizer splitting on the specified delimiter string.
*
- * @param input the string which is to be parsed, not cloned
- * @param delim the field delimiter matcher
+ * @param input the string which is to be parsed
+ * @param delim the field delimiter string
*/
- public StrTokenizer(final char[] input, final StrMatcher delim) {
+ public StrTokenizer(final String input, final String delim) {
this(input);
- setDelimiterMatcher(delim);
+ setDelimiterString(delim);
}
/**
- * Constructs a tokenizer splitting on the specified delimiter character
- * and handling quotes using the specified quote character.
+ * Constructs a tokenizer splitting using the specified delimiter matcher.
*
- * @param input the string which is to be parsed, not cloned
- * @param delim the field delimiter character
- * @param quote the field quoted string character
+ * @param input the string which is to be parsed
+ * @param delim the field delimiter matcher
*/
- public StrTokenizer(final char[] input, final char delim, final char quote) {
- this(input, delim);
- setQuoteChar(quote);
+ public StrTokenizer(final String input, final StrMatcher delim) {
+ this(input);
+ setDelimiterMatcher(delim);
}
/**
* Constructs a tokenizer splitting using the specified delimiter matcher
* and handling quotes using the specified quote matcher.
*
- * @param input the string which is to be parsed, not cloned
- * @param delim the field delimiter character
- * @param quote the field quoted string character
+ * @param input the string which is to be parsed
+ * @param delim the field delimiter matcher
+ * @param quote the field quoted string matcher
*/
- public StrTokenizer(final char[] input, final StrMatcher delim, final StrMatcher quote) {
+ public StrTokenizer(final String input, final StrMatcher delim, final StrMatcher quote) {
this(input, delim);
setQuoteMatcher(quote);
}
- // API
- //-----------------------------------------------------------------------
/**
- * Gets the number of tokens found in the String.
- *
- * @return the number of matched tokens
+ * Unsupported ListIterator operation.
+ * @param obj this parameter ignored.
+ * @throws UnsupportedOperationException always
*/
- public int size() {
- checkTokenized();
- return tokens.length;
+ @Override
+ public void add(final String obj) {
+ throw new UnsupportedOperationException("add() is unsupported");
}
/**
- * Gets the next token from the String.
- * Equivalent to {@link #next()} except it returns null rather than
- * throwing {@link NoSuchElementException} when no tokens remain.
+ * Adds a token to a list, paying attention to the parameters we've set.
*
- * @return the next sequential token, or null when no more tokens are found
+ * @param list the list to add to
+ * @param tok the token to add
*/
- public String nextToken() {
- if (hasNext()) {
- return tokens[tokenPos++];
+ private void addToken(final List
- * This method allows the same tokenizer to be reused for the same String.
+ * Creates a new instance of this Tokenizer. The new instance is reset so that
+ * it will be at the start of the token list.
*
- * @return this, to enable chaining
+ * @return a new instance of this Tokenizer which has been reset.
+ * @throws CloneNotSupportedException if there is a problem cloning
*/
- public StrTokenizer reset() {
- tokenPos = 0;
- tokens = null;
- return this;
+ Object cloneReset() throws CloneNotSupportedException {
+ // this method exists to enable 100% test coverage
+ final StrTokenizer cloned = (StrTokenizer) super.clone();
+ if (cloned.chars != null) {
+ cloned.chars = cloned.chars.clone();
+ }
+ cloned.reset();
+ return cloned;
}
+ //-----------------------------------------------------------------------
/**
- * Reset this tokenizer, giving it a new input string to parse.
- * In this manner you can re-use a tokenizer with the same settings
- * on multiple input lines.
+ * Gets the String content that the tokenizer is parsing.
*
- * @param input the new string to tokenize, null sets no text to parse
- * @return this, to enable chaining
+ * @return The string content being parsed
*/
- public StrTokenizer reset(final String input) {
- reset();
- if (input != null) {
- this.chars = input.toCharArray();
- } else {
- this.chars = null;
+ public String getContent() {
+ if (chars == null) {
+ return null;
}
- return this;
+ return new String(chars);
}
+ // Delimiter
+ //-----------------------------------------------------------------------
/**
- * Reset this tokenizer, giving it a new input string to parse.
- * In this manner you can re-use a tokenizer with the same settings
- * on multiple input lines.
+ * Gets the field delimiter matcher.
*
- * @param input the new character array to tokenize, not cloned, null sets no text to parse
- * @return this, to enable chaining
+ * @return The delimiter matcher in use
*/
- public StrTokenizer reset(final char[] input) {
- reset();
- if (input != null) {
- this.chars = input.clone();
- } else {
- this.chars = null;
- }
- return this;
+ public StrMatcher getDelimiterMatcher() {
+ return this.delimMatcher;
}
- // ListIterator
+ // Ignored
//-----------------------------------------------------------------------
/**
- * Checks whether there are any more tokens.
+ * Gets the ignored character matcher.
+ *
+ * These characters are ignored when parsing the String, unless they are
+ * within a quoted region.
+ * The default value is not to ignore anything.
*
- * @return true if there are more tokens
+ * @return The ignored matcher in use
*/
- @Override
- public boolean hasNext() {
+ public StrMatcher getIgnoredMatcher() {
+ return ignoredMatcher;
+ }
+
+ // Quote
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the quote matcher currently in use.
+ *
+ * The quote character is used to wrap data between the tokens.
+ * This enables delimiters to be entered as data.
+ * The default value is '"' (double quote).
+ *
+ * @return The quote matcher in use
+ */
+ public StrMatcher getQuoteMatcher() {
+ return quoteMatcher;
+ }
+
+ /**
+ * Gets a copy of the full token list as an independent modifiable array.
+ *
+ * @return The tokens as a String array
+ */
+ public String[] getTokenArray() {
checkTokenized();
- return tokenPos < tokens.length;
+ return tokens.clone();
}
/**
- * Gets the next token.
+ * Gets a copy of the full token list as an independent modifiable list.
*
- * @return the next String token
- * @throws NoSuchElementException if there are no more elements
+ * @return The tokens as a String array
*/
- @Override
- public String next() {
- if (hasNext()) {
- return tokens[tokenPos++];
- }
- throw new NoSuchElementException();
+ public List
+ * These characters are trimmed off on each side of the delimiter
+ * until the token or quote is found.
+ * The default value is not to trim anything.
+ *
+ * @return The trimmer matcher in use
+ */
+ public StrMatcher getTrimmerMatcher() {
+ return trimmerMatcher;
+ }
+
+ // ListIterator
+ //-----------------------------------------------------------------------
+ /**
+ * Checks whether there are any more tokens.
*
- * @return the next token index
+ * @return true if there are more tokens
*/
@Override
- public int nextIndex() {
- return tokenPos;
+ public boolean hasNext() {
+ checkTokenized();
+ return tokenPos < tokens.length;
}
/**
@@ -547,134 +584,123 @@ public boolean hasPrevious() {
return tokenPos > 0;
}
+ //-----------------------------------------------------------------------
/**
- * Gets the token previous to the last returned token.
+ * Gets whether the tokenizer currently returns empty tokens as null.
+ * The default for this property is false.
*
- * @return the previous token
+ * @return true if empty tokens are returned as null
*/
- @Override
- public String previous() {
- if (hasPrevious()) {
- return tokens[--tokenPos];
- }
- throw new NoSuchElementException();
+ public boolean isEmptyTokenAsNull() {
+ return this.emptyAsNull;
}
+ //-----------------------------------------------------------------------
/**
- * Gets the index of the previous token.
+ * Gets whether the tokenizer currently ignores empty tokens.
+ * The default for this property is true.
*
- * @return the previous token index
+ * @return true if empty tokens are not returned
*/
- @Override
- public int previousIndex() {
- return tokenPos - 1;
+ public boolean isIgnoreEmptyTokens() {
+ return ignoreEmptyTokens;
}
/**
- * Unsupported ListIterator operation.
+ * Checks if the characters at the index specified match the quote
+ * already matched in readNextToken().
*
- * @throws UnsupportedOperationException always
+ * @param srcChars the character array being tokenized
+ * @param pos the position to check for a quote
+ * @param len the length of the character array being tokenized
+ * @param quoteStart the start position of the matched quote, 0 if no quoting
+ * @param quoteLen the length of the matched quote, 0 if no quoting
+ * @return true if a quote is matched
*/
- @Override
- public void remove() {
- throw new UnsupportedOperationException("remove() is unsupported");
+ private boolean isQuote(final char[] srcChars,
+ final int pos,
+ final int len,
+ final int quoteStart,
+ final int quoteLen) {
+ for (int i = 0; i < quoteLen; i++) {
+ if (pos + i >= len || srcChars[pos + i] != srcChars[quoteStart + i]) {
+ return false;
+ }
+ }
+ return true;
}
/**
- * Unsupported ListIterator operation.
- * @param obj this parameter ignored.
- * @throws UnsupportedOperationException always
+ * Gets the next token.
+ *
+ * @return The next String token
+ * @throws NoSuchElementException if there are no more elements
*/
@Override
- public void set(final String obj) {
- throw new UnsupportedOperationException("set() is unsupported");
+ public String next() {
+ if (hasNext()) {
+ return tokens[tokenPos++];
+ }
+ throw new NoSuchElementException();
}
/**
- * Unsupported ListIterator operation.
- * @param obj this parameter ignored.
- * @throws UnsupportedOperationException always
+ * Gets the index of the next token to return.
+ *
+ * @return The next token index
*/
@Override
- public void add(final String obj) {
- throw new UnsupportedOperationException("add() is unsupported");
+ public int nextIndex() {
+ return tokenPos;
}
- // Implementation
- //-----------------------------------------------------------------------
/**
- * Checks if tokenization has been done, and if not then do it.
+ * Gets the next token from the String.
+ * Equivalent to {@link #next()} except it returns null rather than
+ * throwing {@link NoSuchElementException} when no tokens remain.
+ *
+ * @return The next sequential token, or null when no more tokens are found
*/
- private void checkTokenized() {
- if (tokens == null) {
- if (chars == null) {
- // still call tokenize as subclass may do some work
- final List
- * Most users of this class do not need to call this method. This method
- * will be called automatically by other (public) methods when required.
- *
- * This method exists to allow subclasses to add code before or after the
- * tokenization. For example, a subclass could alter the character array,
- * offset or count to be parsed, or call the tokenizer multiple times on
- * multiple strings. It is also be possible to filter the results.
- *
- *
+ * This method allows the same tokenizer to be reused for the same String.
+ *
+ * @return this, to enable chaining
+ */
+ public StrTokenizer reset() {
+ tokenPos = 0;
+ tokens = null;
+ return this;
+ }
+
+ /**
+ * Reset this tokenizer, giving it a new input string to parse.
+ * In this manner you can re-use a tokenizer with the same settings
+ * on multiple input lines.
+ *
+ * @param input the new character array to tokenize, not cloned, null sets no text to parse
+ * @return this, to enable chaining
+ */
+ public StrTokenizer reset(final char[] input) {
+ reset();
+ if (input != null) {
+ this.chars = input.clone();
+ } else {
+ this.chars = null;
+ }
+ return this;
+ }
+
+ /**
+ * Reset this tokenizer, giving it a new input string to parse.
+ * In this manner you can re-use a tokenizer with the same settings
+ * on multiple input lines.
+ *
+ * @param input the new string to tokenize, null sets no text to parse
+ * @return this, to enable chaining
+ */
+ public StrTokenizer reset(final String input) {
+ reset();
+ if (input != null) {
+ this.chars = input.toCharArray();
+ } else {
+ this.chars = null;
+ }
+ return this;
+ }
+
+ /**
+ * Unsupported ListIterator operation.
+ * @param obj this parameter ignored.
+ * @throws UnsupportedOperationException always
*/
- private boolean isQuote(final char[] srcChars,
- final int pos,
- final int len,
- final int quoteStart,
- final int quoteLen) {
- for (int i = 0; i < quoteLen; i++) {
- if (pos + i >= len || srcChars[pos + i] != srcChars[quoteStart + i]) {
- return false;
- }
- }
- return true;
+ @Override
+ public void set(final String obj) {
+ throw new UnsupportedOperationException("set() is unsupported");
}
- // Delimiter
- //-----------------------------------------------------------------------
/**
- * Gets the field delimiter matcher.
+ * Sets the field delimiter character.
*
- * @return the delimiter matcher in use
+ * @param delim the delimiter character to use
+ * @return this, to enable chaining
*/
- public StrMatcher getDelimiterMatcher() {
- return this.delimMatcher;
+ public StrTokenizer setDelimiterChar(final char delim) {
+ return setDelimiterMatcher(StrMatcher.charMatcher(delim));
}
/**
@@ -879,16 +949,6 @@ public StrTokenizer setDelimiterMatcher(final StrMatcher delim) {
return this;
}
- /**
- * Sets the field delimiter character.
- *
- * @param delim the delimiter character to use
- * @return this, to enable chaining
- */
- public StrTokenizer setDelimiterChar(final char delim) {
- return setDelimiterMatcher(StrMatcher.charMatcher(delim));
- }
-
/**
* Sets the field delimiter string.
*
@@ -899,63 +959,29 @@ public StrTokenizer setDelimiterString(final String delim) {
return setDelimiterMatcher(StrMatcher.stringMatcher(delim));
}
- // Quote
- //-----------------------------------------------------------------------
- /**
- * Gets the quote matcher currently in use.
- *
- * The quote character is used to wrap data between the tokens.
- * This enables delimiters to be entered as data.
- * The default value is '"' (double quote).
- *
- * @return the quote matcher in use
- */
- public StrMatcher getQuoteMatcher() {
- return quoteMatcher;
- }
-
/**
- * Set the quote matcher to use.
- *
- * The quote character is used to wrap data between the tokens.
- * This enables delimiters to be entered as data.
+ * Sets whether the tokenizer should return empty tokens as null.
+ * The default for this property is false.
*
- * @param quote the quote matcher to use, null ignored
+ * @param emptyAsNull whether empty tokens are returned as null
* @return this, to enable chaining
*/
- public StrTokenizer setQuoteMatcher(final StrMatcher quote) {
- if (quote != null) {
- this.quoteMatcher = quote;
- }
+ public StrTokenizer setEmptyTokenAsNull(final boolean emptyAsNull) {
+ this.emptyAsNull = emptyAsNull;
return this;
}
/**
- * Sets the quote character to use.
- *
- * The quote character is used to wrap data between the tokens.
- * This enables delimiters to be entered as data.
- *
- * @param quote the quote character to use
- * @return this, to enable chaining
- */
- public StrTokenizer setQuoteChar(final char quote) {
- return setQuoteMatcher(StrMatcher.charMatcher(quote));
- }
-
- // Ignored
- //-----------------------------------------------------------------------
- /**
- * Gets the ignored character matcher.
+ * Set the character to ignore.
*
- * These characters are ignored when parsing the String, unless they are
+ * This character is ignored when parsing the String, unless it is
* within a quoted region.
- * The default value is not to ignore anything.
*
- * @return the ignored matcher in use
+ * @param ignored the ignored character to use
+ * @return this, to enable chaining
*/
- public StrMatcher getIgnoredMatcher() {
- return ignoredMatcher;
+ public StrTokenizer setIgnoredChar(final char ignored) {
+ return setIgnoredMatcher(StrMatcher.charMatcher(ignored));
}
/**
@@ -975,31 +1001,44 @@ public StrTokenizer setIgnoredMatcher(final StrMatcher ignored) {
}
/**
- * Set the character to ignore.
+ * Sets whether the tokenizer should ignore and not return empty tokens.
+ * The default for this property is true.
+ *
+ * @param ignoreEmptyTokens whether empty tokens are not returned
+ * @return this, to enable chaining
+ */
+ public StrTokenizer setIgnoreEmptyTokens(final boolean ignoreEmptyTokens) {
+ this.ignoreEmptyTokens = ignoreEmptyTokens;
+ return this;
+ }
+
+ /**
+ * Sets the quote character to use.
*
- * This character is ignored when parsing the String, unless it is
- * within a quoted region.
+ * The quote character is used to wrap data between the tokens.
+ * This enables delimiters to be entered as data.
*
- * @param ignored the ignored character to use
+ * @param quote the quote character to use
* @return this, to enable chaining
*/
- public StrTokenizer setIgnoredChar(final char ignored) {
- return setIgnoredMatcher(StrMatcher.charMatcher(ignored));
+ public StrTokenizer setQuoteChar(final char quote) {
+ return setQuoteMatcher(StrMatcher.charMatcher(quote));
}
- // Trimmer
- //-----------------------------------------------------------------------
/**
- * Gets the trimmer character matcher.
+ * Set the quote matcher to use.
*
- * These characters are trimmed off on each side of the delimiter
- * until the token or quote is found.
- * The default value is not to trim anything.
+ * The quote character is used to wrap data between the tokens.
+ * This enables delimiters to be entered as data.
*
- * @return the trimmer matcher in use
+ * @param quote the quote matcher to use, null ignored
+ * @return this, to enable chaining
*/
- public StrMatcher getTrimmerMatcher() {
- return trimmerMatcher;
+ public StrTokenizer setQuoteMatcher(final StrMatcher quote) {
+ if (quote != null) {
+ this.quoteMatcher = quote;
+ }
+ return this;
}
/**
@@ -1018,104 +1057,64 @@ public StrTokenizer setTrimmerMatcher(final StrMatcher trimmer) {
return this;
}
+ // API
//-----------------------------------------------------------------------
/**
- * Gets whether the tokenizer currently returns empty tokens as null.
- * The default for this property is false.
- *
- * @return true if empty tokens are returned as null
- */
- public boolean isEmptyTokenAsNull() {
- return this.emptyAsNull;
- }
-
- /**
- * Sets whether the tokenizer should return empty tokens as null.
- * The default for this property is false.
- *
- * @param emptyAsNull whether empty tokens are returned as null
- * @return this, to enable chaining
- */
- public StrTokenizer setEmptyTokenAsNull(final boolean emptyAsNull) {
- this.emptyAsNull = emptyAsNull;
- return this;
- }
-
- //-----------------------------------------------------------------------
- /**
- * Gets whether the tokenizer currently ignores empty tokens.
- * The default for this property is true.
- *
- * @return true if empty tokens are not returned
- */
- public boolean isIgnoreEmptyTokens() {
- return ignoreEmptyTokens;
- }
-
- /**
- * Sets whether the tokenizer should ignore and not return empty tokens.
- * The default for this property is true.
+ * Gets the number of tokens found in the String.
*
- * @param ignoreEmptyTokens whether empty tokens are not returned
- * @return this, to enable chaining
+ * @return The number of matched tokens
*/
- public StrTokenizer setIgnoreEmptyTokens(final boolean ignoreEmptyTokens) {
- this.ignoreEmptyTokens = ignoreEmptyTokens;
- return this;
+ public int size() {
+ checkTokenized();
+ return tokens.length;
}
- //-----------------------------------------------------------------------
/**
- * Gets the String content that the tokenizer is parsing.
+ * Internal method to performs the tokenization.
+ *
+ * Most users of this class do not need to call this method. This method
+ * will be called automatically by other (public) methods when required.
+ *
+ * This method exists to allow subclasses to add code before or after the
+ * tokenization. For example, a subclass could alter the character array,
+ * offset or count to be parsed, or call the tokenizer multiple times on
+ * multiple strings. It is also be possible to filter the results.
+ *
+ * {@code StrTokenizer} will always pass a zero offset and a count
+ * equal to the length of the array to this method, however a subclass
+ * may pass other values, or even an entirely different array.
*
- * @return the string content being parsed
+ * @param srcChars the character array being tokenized, may be null
+ * @param offset the start position within the character array, must be valid
+ * @param count the number of characters to tokenize, must be valid
+ * @return The modifiable list of String tokens, unmodifiable if null array or zero count
*/
- public String getContent() {
- if (chars == null) {
- return null;
+ protected List Escapes and unescapes {@code String}s for
- * Java, Java Script, HTML and XML. #ThreadSafe#
+ * Escapes and unescapes {@code String}s for Java, Java Script, HTML and XML.
+ *
+ * #ThreadSafe#
+ *
* This code has been adapted from Apache Commons Lang 3.5.
@@ -108,7 +110,7 @@ public class StringEscapeUtils {
ESCAPE_JSON = new AggregateTranslator(
new LookupTranslator(Collections.unmodifiableMap(escapeJsonMap)),
new LookupTranslator(EntityArrays.JAVA_CTRL_CHARS_ESCAPE),
- JavaUnicodeEscaper.outsideOf(32, 0x7f)
+ JavaUnicodeEscaper.outsideOf(32, 0x7e)
);
}
@@ -248,8 +250,8 @@ public class StringEscapeUtils {
escapeXsiMap.put("'", "\\'");
escapeXsiMap.put(" ", "\\ ");
escapeXsiMap.put("\t", "\\\t");
- escapeXsiMap.put("\r\n", "");
- escapeXsiMap.put("\n", "");
+ escapeXsiMap.put("\r\n", StringUtils.EMPTY);
+ escapeXsiMap.put("\n", StringUtils.EMPTY);
escapeXsiMap.put("*", "\\*");
escapeXsiMap.put("?", "\\?");
escapeXsiMap.put("[", "\\[");
@@ -277,7 +279,7 @@ public class StringEscapeUtils {
unescapeJavaMap.put("\\\\", "\\");
unescapeJavaMap.put("\\\"", "\"");
unescapeJavaMap.put("\\'", "'");
- unescapeJavaMap.put("\\", "");
+ unescapeJavaMap.put("\\", StringUtils.EMPTY);
UNESCAPE_JAVA = new AggregateTranslator(
new OctalUnescaper(), // .between('\1', '\377'),
new UnicodeUnescaper(),
@@ -302,14 +304,7 @@ public class StringEscapeUtils {
* object allows the Json unescaping functionality to be used
* as the foundation for a custom translator.
*/
- public static final CharSequenceTranslator UNESCAPE_JSON;
- static {
- UNESCAPE_JSON = new AggregateTranslator(
- new OctalUnescaper(), // .between('\1', '\377'),
- new UnicodeUnescaper(),
- new LookupTranslator(EntityArrays.JAVA_CTRL_CHARS_UNESCAPE)
- );
- }
+ public static final CharSequenceTranslator UNESCAPE_JSON = UNESCAPE_JAVA;
/**
* Translator object for unescaping escaped HTML 3.0.
@@ -383,7 +378,7 @@ static class XsiUnescaper extends CharSequenceTranslator {
private static final char BACKSLASH = '\\';
@Override
- public int translate(final CharSequence input, final int index, final Writer out) throws IOException {
+ public int translate(final CharSequence input, final int index, final Writer writer) throws IOException {
if (index != 0) {
throw new IllegalStateException("XsiUnescaper should never reach the [1] index");
@@ -397,12 +392,12 @@ public int translate(final CharSequence input, final int index, final Writer out
final int pos = s.indexOf(BACKSLASH, searchOffset);
if (pos == -1) {
if (segmentStart < s.length()) {
- out.write(s.substring(segmentStart));
+ writer.write(s.substring(segmentStart));
}
break;
}
if (pos > segmentStart) {
- out.write(s.substring(segmentStart, pos));
+ writer.write(s.substring(segmentStart, pos));
}
segmentStart = pos + 1;
searchOffset = pos + 2;
@@ -425,7 +420,6 @@ public int translate(final CharSequence input, final int index, final Writer out
* instance to operate. Return the escaped string. The only difference between Java strings and EcmaScript strings
* is that in EcmaScript, a single quote and forward-slash (/) are escaped. Note that EcmaScript is best known by the JavaScript and ActionScript dialects. Note that EcmaScript is best known by the JavaScript and ActionScript dialects. Example: The only difference between Java strings and Json strings
* is that in Json, forward-slash (/) is escaped. See http://www.ietf.org/rfc/rfc4627.txt for further details. See http://www.ietf.org/rfc/rfc4627.txt for further details. Example:
* For example:
* {@code "bread" & "butter"}
- * Supports all known HTML 4.0 entities, including funky accents.
* Note that the commonly used apostrophe escape character (')
- * is not a legal entity and so is not supported). Escapes the characters in a {@code String} using HTML entities. Supports only the HTML 3.0 entities. Supports only the HTML 3.0 entities. Note that numerical \\u Unicode codes are unescaped to their respective
- * Unicode characters. This may change in future releases. Returns a {@code String} value for an unescaped CSV column. Returns a {@code String} value for an unescaped CSV column. If the value is enclosed in double quotes, and contains a comma, newline
* or double quote, then quotes are removed.
* Any double quote escaped characters (a pair of double quotes) are unescaped
- * to just one double quote. If the value is not enclosed in double quotes, or is and does not contain a
* comma, newline or double quote, then the String value is returned unchanged.
+ * This class takes a piece of text and substitutes all the variables within it. The default definition of a variable is
+ * {@code ${variableName}}. The prefix and suffix can be changed via constructors and set methods.
+ *
+ * Variable values are typically resolved from a map, but could also be resolved from system properties, or by supplying
+ * a custom variable resolver.
+ *
+ * The simplest example is to use this class to replace Java System properties. For example:
+ *
+ * Typical usage of this class follows the following pattern:
+ *
+ * For example:
+ *
+ * yielding:
+ *
+ * You can set a default value for unresolved variables. The default value for a variable can be appended to the
+ * variable name after the variable default value delimiter. The default value of the variable default value delimiter
+ * is ":-", as in bash and other *nix shells.
+ *
+ * You can set the variable value delimiter with {@link #setValueDelimiterMatcher(StringMatcher)},
+ * {@link #setValueDelimiter(char)} or {@link #setValueDelimiter(String)}.
+ *
+ * For example:
+ *
+ * yielding:
+ *
+ * {@code StringSubstitutor} supports throwing exceptions for unresolved variables, you enable this by setting calling
+ * {@link #setEnableUndefinedVariableException(boolean)} with {@code true}.
+ *
+ * Static shortcut methods cover the most common use cases. If multiple replace operations are to be performed, creating
+ * and reusing an instance of this class will be more efficient.
+ *
+ * The default interpolator let's you use string lookups like:
+ *
+ * For documentation of each lookup, see {@link StringLookupFactory}.
+ *
+ * Variable replacement can work recursively by calling {@link #setEnableSubstitutionInVariables(boolean)} with
+ * {@code true}. If a variable value contains a variable then that variable will also be replaced. Cyclic replacements
+ * are detected and will throw an exception.
+ *
+ * You can get the replace result to contain a variable prefix. For example:
+ *
+ * If the value of the "name" variable is "x", then only the variable "name" is replaced resulting in:
+ *
+ * To achieve this effect there are two possibilities: Either set a different prefix and suffix for variables which do
+ * not conflict with the result text you want to produce. The other possibility is to use the escape character, by
+ * default '$'. If this character is placed before a variable reference, this reference is ignored and won't be
+ * replaced. For example:
+ *
+ * In some complex scenarios you might even want to perform substitution in the names of variables, for instance
+ *
+ * {@code StringSubstitutor} supports this recursive substitution in variable names, but it has to be enabled explicitly
+ * by calling {@link #setEnableSubstitutionInVariables(boolean)} with {@code true}.
+ *
+ * This class is not thread safe.
+ *
+ * This StringSubstitutor lets you perform substituions like:
+ *
+ * The variable default value delimiter is the character or characters that delimit the variable name and the
+ * variable default value. This delimiter is expressed in terms of a matcher allowing advanced variable default
+ * value delimiter matches.
+ *
+ * If it returns null, then the variable default value resolution is disabled.
+ *
+ * @return The variable default value delimiter matcher in use, may be null
+ */
+ public StringMatcher getValueDelimiterMatcher() {
+ return valueDelimiterMatcher;
+ }
+
+ /**
+ * Gets the variable prefix matcher currently in use.
+ *
+ * The variable prefix is the character or characters that identify the start of a variable. This prefix is
+ * expressed in terms of a matcher allowing advanced prefix matches.
+ *
+ * The variable suffix is the character or characters that identify the end of a variable. This suffix is expressed
+ * in terms of a matcher allowing advanced suffix matches.
+ *
+ * Only the specified portion of the array will be processed. The rest of the array is not processed, and is not
+ * returned.
+ *
+ * Only the specified portion of the buffer will be processed. The rest of the buffer is not processed, and is not
+ * returned.
+ *
+ * Only the specified portion of the string will be processed. The rest of the string is not processed, and is not
+ * returned.
+ *
+ * Only the specified portion of the buffer will be processed. The rest of the buffer is not processed, and is not
+ * returned.
+ *
+ * Only the specified portion of the builder will be processed. The rest of the builder is not processed, and is not
+ * returned.
+ *
+ * Only the specified portion of the buffer will be processed. The rest of the buffer is not processed, but it is
+ * not deleted.
+ *
+ * Only the specified portion of the buffer will be processed. The rest of the buffer is not processed, but it is
+ * not deleted.
+ *
+ * Only the specified portion of the builder will be processed. The rest of the builder is not processed, but it is
+ * not deleted.
+ *
+ * Most users of this class do not need to call this method. This method is called automatically by the substitution
+ * process.
+ *
+ * Writers of subclasses can override this method if they need to alter how each substitution occurs. The method is
+ * passed the variable's name and must return the corresponding value. This implementation uses the
+ * {@link #getStringLookup()} with the variable's name as the key.
+ *
+ * The variable default value delimiter is the character or characters that delimit the variable name and the
+ * variable default value. This method allows a single character variable default value delimiter to be easily set.
+ *
+ * The variable default value delimiter is the character or characters that delimit the variable name and the
+ * variable default value. This method allows a string variable default value delimiter to be easily set.
+ *
+ * If the {@code valueDelimiter} is null or empty string, then the variable default value resolution becomes
+ * disabled.
+ *
+ * The variable default value delimiter is the character or characters that delimit the variable name and the
+ * variable default value. This delimiter is expressed in terms of a matcher allowing advanced variable default
+ * value delimiter matches.
+ *
+ * If the {@code valueDelimiterMatcher} is null, then the variable default value resolution becomes disabled.
+ *
+ * The variable prefix is the character or characters that identify the start of a variable. This method allows a
+ * single character prefix to be easily set.
+ *
+ * The variable prefix is the character or characters that identify the start of a variable. This method allows a
+ * string prefix to be easily set.
+ *
+ * The variable prefix is the character or characters that identify the start of a variable. This prefix is
+ * expressed in terms of a matcher allowing advanced prefix matches.
+ *
+ * The variable suffix is the character or characters that identify the end of a variable. This method allows a
+ * single character suffix to be easily set.
+ *
+ * The variable suffix is the character or characters that identify the end of a variable. This method allows a
+ * string suffix to be easily set.
+ *
+ * The variable suffix is the character or characters that identify the end of a variable. This suffix is expressed
+ * in terms of a matcher allowing advanced suffix matches.
+ *
+ * Most users of this class do not need to call this method. This method will be called automatically by another
+ * (public) method.
+ *
+ * Writers of subclasses can override this method if they need access to the substitution process at the start or
+ * end.
+ *
+ * This class can split a String into many smaller strings. It aims to do a similar job to
+ * {@link java.util.StringTokenizer StringTokenizer}, however it offers much more control and flexibility including
+ * implementing the {@code ListIterator} interface. By default, it is set up like {@code StringTokenizer}.
+ *
+ * The input String is split into a number of tokens. Each token is separated from the next String by a
+ * delimiter. One or more delimiter characters must be specified.
+ *
+ * Each token may be surrounded by quotes. The quote matcher specifies the quote character(s). A quote may be
+ * escaped within a quoted section by duplicating itself.
+ *
+ * Between each token and the delimiter are potentially characters that need trimming. The trimmer matcher
+ * specifies these characters. One usage might be to trim whitespace characters.
+ *
+ * At any point outside the quotes there might potentially be invalid characters. The ignored matcher specifies
+ * these characters to be removed. One usage might be to remove new line characters.
+ *
+ * Empty tokens may be removed or returned as null.
+ *
+ *
+ * You must call a "reset" method to set the string which you want to parse.
+ *
+ * You must call a "reset" method to set the string which you want to parse.
+ *
+ * This constructor is normally used with {@link #reset(String)}.
+ *
+ * These characters are ignored when parsing the String, unless they are within a quoted region. The default value
+ * is not to ignore anything.
+ *
+ * The quote character is used to wrap data between the tokens. This enables delimiters to be entered as data. The
+ * default value is '"' (double quote).
+ *
+ * These characters are trimmed off on each side of the delimiter until the token or quote is found. The default
+ * value is not to trim anything.
+ *
+ * This method allows the same tokenizer to be reused for the same String.
+ *
+ * @return this, to enable chaining
+ */
+ public StringTokenizer reset() {
+ tokenPos = 0;
+ tokens = null;
+ return this;
+ }
+
+ /**
+ * Reset this tokenizer, giving it a new input string to parse. In this manner you can re-use a tokenizer with the
+ * same settings on multiple input lines.
+ *
+ * @param input
+ * the new character array to tokenize, not cloned, null sets no text to parse
+ * @return this, to enable chaining
+ */
+ public StringTokenizer reset(final char[] input) {
+ reset();
+ this.chars = input != null ? input.clone() : null;
+ return this;
+ }
+
+ /**
+ * Reset this tokenizer, giving it a new input string to parse. In this manner you can re-use a tokenizer with the
+ * same settings on multiple input lines.
+ *
+ * @param input
+ * the new string to tokenize, null sets no text to parse
+ * @return this, to enable chaining
+ */
+ public StringTokenizer reset(final String input) {
+ reset();
+ this.chars = input != null ? input.toCharArray() : null;
+ return this;
+ }
+
+ /**
+ * Unsupported ListIterator operation.
+ *
+ * @param obj
+ * this parameter ignored.
+ * @throws UnsupportedOperationException
+ * always
+ */
+ @Override
+ public void set(final String obj) {
+ throw new UnsupportedOperationException("set() is unsupported");
+ }
+
+ /**
+ * Sets the field delimiter character.
+ *
+ * @param delim
+ * the delimiter character to use
+ * @return this, to enable chaining
+ */
+ public StringTokenizer setDelimiterChar(final char delim) {
+ return setDelimiterMatcher(StringMatcherFactory.INSTANCE.charMatcher(delim));
+ }
+
+ /**
+ * Sets the field delimiter matcher.
+ *
+ * The delimiter is used to separate one token from another.
+ *
+ * This character is ignored when parsing the String, unless it is within a quoted region.
+ *
+ * These characters are ignored when parsing the String, unless they are within a quoted region.
+ *
+ * The quote character is used to wrap data between the tokens. This enables delimiters to be entered as data.
+ *
+ * The quote character is used to wrap data between the tokens. This enables delimiters to be entered as data.
+ *
+ * These characters are trimmed off on each side of the delimiter until the token or quote is found.
+ *
+ * @param trimmer
+ * the trimmer matcher to use, null ignored
+ * @return this, to enable chaining
+ */
+ public StringTokenizer setTrimmerMatcher(final StringMatcher trimmer) {
+ if (trimmer != null) {
+ this.trimmerMatcher = trimmer;
+ }
+ return this;
+ }
+
+ /**
+ * Gets the number of tokens found in the String.
+ *
+ * @return The number of matched tokens
+ */
+ public int size() {
+ checkTokenized();
+ return tokens.length;
+ }
+
+ /**
+ * Internal method to performs the tokenization.
+ *
+ * Most users of this class do not need to call this method. This method will be called automatically by other
+ * (public) methods when required.
+ *
+ * This method exists to allow subclasses to add code before or after the tokenization. For example, a subclass
+ * could alter the character array, offset or count to be parsed, or call the tokenizer multiple times on multiple
+ * strings. It is also be possible to filter the results.
+ *
+ * {@code StrTokenizer} will always pass a zero offset and a count equal to the length of the array to this
+ * method, however a subclass may pass other values, or even an entirely different array.
+ *
* TextRandomProvider implementations are used by {@link RandomStringGenerator}
* as a source of randomness. It is highly recommended that the
- * Apache Commons RNG
+ * Apache Commons RNG
* library be used to provide the random number generation.
*
+ * The main differences from StringBuffer/StringBuilder are:
+ *
+ * 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)}.
+ *
+ * This class is called {@code TextStringBuilder} instead of {@code StringBuilder} to avoid clashing with
+ * {@link java.lang.StringBuilder}.
+ *
+ * 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} to this string builder.
+ *
+ * @return this, to enable chaining
+ */
+ public TextStringBuilder appendNull() {
+ if (nullText == null) {
+ return this;
+ }
+ return append(nullText);
+ }
+
+ /**
+ * 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 a separator if the builder is currently non-empty. The separator is appended using {@link #append(char)}.
+ *
+ * This method is useful for adding a separator each time around the loop except the first.
+ *
+ * Note that for this simple example, you should use {@link #appendWithSeparators(Iterable, String)}.
+ *
+ * This method is useful for adding a separator each time around the loop except the first.
+ *
+ * Note that for this simple example, you should use {@link #appendWithSeparators(Iterable, String)}.
+ *
+ * This method is useful for adding a separator each time around the loop except the first.
+ *
+ * Note that for this simple example, you should use {@link #appendWithSeparators(Iterable, String)}.
+ *
+ * This method is useful for adding a separator each time around the loop except the first.
+ *
+ * Note that for this simple example, you should use {@link #appendWithSeparators(Iterable, String)}.
+ *
+ * This method is for example useful for constructing queries
+ *
+ * This method tries to avoid doing any extra copies of contents.
+ *
+ * This method allows the contents of the builder to be read using any standard method that expects a Reader.
+ *
+ * To use, simply create a {@code StrBuilder}, populate it with data, call {@code asReader}, and then read away.
+ *
+ * The internal character array is shared between the builder and the reader. This allows you to append to the
+ * builder after creating the reader, and the changes will be picked up. Note however, that no synchronization
+ * occurs, so you must perform all operations with the builder and the reader in one thread.
+ *
+ * The returned reader supports marking, and ignores the flush method.
+ *
+ * This method allows the contents of this builder to be tokenized. The tokenizer will be setup by default to
+ * tokenize on space, tab, newline and form feed (as per StringTokenizer). These values can be changed on the
+ * tokenizer class, before retrieving the tokens.
+ *
+ * The returned tokenizer is linked to this builder. You may intermix calls to the builder and tokenizer within
+ * certain limits, however there is no synchronization. Once the tokenizer has been used once, it must be
+ * {@link StringTokenizer#reset() reset} to pickup the latest changes in the builder. For example:
+ *
+ * In addition to simply intermixing appends and tokenization, you can also call the set methods on the tokenizer to
+ * alter how it tokenizes. Just remember to call reset when you want to pickup builder changes.
+ *
+ * Calling {@link StringTokenizer#reset(String)} or {@link StringTokenizer#reset(char[])} with a non-null value will
+ * break the link with the builder.
+ *
+ * This method allows you to populate the contents of the builder using any standard method that takes a Writer.
+ *
+ * To use, simply create a {@code StrBuilder}, call {@code asWriter}, and populate away. The data is available at
+ * any time using the methods of the {@code StrBuilder}.
+ *
+ * The internal character array is shared between the builder and the writer. This allows you to intermix calls that
+ * append to the builder and write using the writer and the changes will be occur correctly. Note however, that no
+ * synchronization occurs, so you must perform all operations with the builder and the writer in one thread.
+ *
+ * The returned writer ignores the close and flush methods.
+ *
+ * This method does not reduce the size of the internal character buffer. To do that, call {@code clear()} followed
+ * by {@link #minimizeCapacity()}.
+ *
+ * This method is the same as {@link #setLength(int)} called with zero and is provided to match the API of
+ * Collections.
+ *
+ * Matchers can be used to perform advanced searching behavior. For example you could write a matcher to search for
+ * the character 'a' followed by a number.
+ *
+ * Matchers can be used to perform advanced deletion behavior. 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 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) {
+ validateIndex(index);
+ deleteImpl(index, index + 1, 1);
+ 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 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 the first match within the builder using the specified matcher.
+ *
+ * Matchers can be used to perform advanced deletion behavior. 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 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;
+ }
+
+ /**
+ * Gets the character at the specified index before deleting it.
+ *
+ * @see #charAt(int)
+ * @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
+ * @since 1.9
+ */
+ public char drainChar(final int index) {
+ validateIndex(index);
+ final char c = buffer[index];
+ deleteCharAt(index);
+ return c;
+ }
+
+ /**
+ * Drains (copies, then deletes) this character sequence into the specified array. This is equivalent to copying the
+ * characters from this sequence into the target and then deleting those character from this sequence.
+ *
+ * @param startIndex first index to copy, inclusive.
+ * @param endIndex last index to copy, exclusive.
+ * @param target the target array, must not be {@code null}.
+ * @param targetIndex the index to start copying in the target.
+ * @return How many characters where copied (then deleted). If this builder is empty, return {@code 0}.
+ * @since 1.9
+ */
+ public int drainChars(final int startIndex, final int endIndex, final char[] target, final int targetIndex) {
+ final int length = endIndex - startIndex;
+ if (isEmpty() || length == 0 || target.length == 0) {
+ return 0;
+ }
+ final int actualLen = Math.min(Math.min(size, length), target.length - targetIndex);
+ getChars(startIndex, actualLen, target, targetIndex);
+ delete(startIndex, actualLen);
+ return actualLen;
+ }
+
+ /**
+ * Checks whether this builder ends with the specified string.
+ *
+ * Note that this method handles null input quietly, unlike String.
+ *
+ * Note that a null input string will return -1, whereas the JDK throws an exception.
+ *
+ * @param str the string to find, null returns -1
+ * @return The first index of the string, or -1 if not found
+ */
+ public int indexOf(final String str) {
+ return indexOf(str, 0);
+ }
+
+ /**
+ * Searches the string builder to find the first reference to the specified string starting searching from the given
+ * index.
+ *
+ * Note that a null input string will return -1, whereas the JDK throws an exception.
+ *
+ * Matchers can be used to perform advanced searching behavior. For example you could write a matcher to find the
+ * character 'a' followed by a number.
+ *
+ * Matchers can be used to perform advanced searching behavior. For example you could write a matcher to find the
+ * character 'a' followed by a number.
+ *
+ * This method is the same as checking {@link #length()} and is provided to match the API of Collections.
+ *
+ * This method is the same as checking {@link #length()}.
+ *
+ * Note that a null input string will return -1, whereas the JDK throws an exception.
+ *
+ * @param str the string to find, null returns -1
+ * @return The last index of the string, or -1 if not found
+ */
+ public int lastIndexOf(final String str) {
+ return lastIndexOf(str, size - 1);
+ }
+
+ /**
+ * Searches the string builder to find the last reference to the specified string starting searching from the given
+ * index.
+ *
+ * Note that a null input string will return -1, whereas the JDK throws an exception.
+ *
+ * Matchers can be used to perform advanced searching behavior. For example you could write a matcher to find the
+ * character 'a' followed by a number.
+ *
+ * Matchers can be used to perform advanced searching behavior. For example you could write a matcher to find the
+ * character 'a' followed by a number.
+ *
+ * This method extracts the left {@code length} characters from the builder. If this many characters are not
+ * available, the whole builder is returned. Thus the returned string may be shorter than the length requested.
+ *
+ * This method extracts {@code length} characters from the builder at the specified index. If the index is negative
+ * it is treated as zero. If the index is greater than the builder size, it is treated as the builder size. If the
+ * length is negative, the empty string is returned. If insufficient characters are available in the builder, as
+ * much as possible is returned. Thus the returned string may be shorter than the length requested.
+ *
+ * Matchers can be used to perform advanced behavior. For example you could write a matcher to delete all
+ * occurrences where the character 'a' is followed by a number.
+ *
+ * Matchers can be used to perform advanced replace behavior. For example you could write a matcher to replace all
+ * occurrences where the character 'a' is followed by a number.
+ *
+ * Matchers can be used to perform advanced replace behavior. For example you could write a matcher to replace where
+ * the character 'a' is followed by a number.
+ *
+ * Matchers can be used to perform advanced behavior. For example you could write a matcher to delete all
+ * occurrences where the character 'a' is followed by a number.
+ *
+ * This method extracts the right {@code length} characters from the builder. If this many characters are not
+ * available, the whole builder is returned. Thus the returned string may be shorter than the length requested.
+ *
+ * This method is the same as {@link #length()} and is provided to match the API of Collections.
+ *
+ * Note that this method handles null input quietly, unlike String.
+ *
+ * Note: This method treats an endIndex greater than the length of the builder as equal to the length of the
+ * builder, and continues without error, unlike StringBuffer or String.
+ *
+ * Note that unlike StringBuffer, the string version returned is independent of the string builder.
+ *
+ * @return The builder as a String
+ */
+ @Override
+ public String toString() {
+ return new String(buffer, 0, size);
+ }
+
+ /**
+ * Gets a StringBuffer version of the string builder, creating a new instance each time the method is called.
+ *
+ * @return The builder as a StringBuffer
+ */
+ public StringBuffer toStringBuffer() {
+ return new StringBuffer(size).append(buffer, 0, size);
+ }
+
+ /**
+ * Gets a StringBuilder version of the string builder, creating a new instance each time the method is called.
+ *
+ * @return The builder as a StringBuilder
+ */
+ public StringBuilder toStringBuilder() {
+ return new StringBuilder(size).append(buffer, 0, size);
+ }
+
+ /**
+ * 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] <= SPACE) {
+ pos++;
+ }
+ while (pos < len && buf[len - 1] <= SPACE) {
+ len--;
+ }
+ if (len < size) {
+ delete(len, size);
+ }
+ if (pos > 0) {
+ delete(0, pos);
+ }
+ return this;
+ }
+
+ /**
+ * Validates that an index is in the range {@code 0 <= index <= size}.
+ *
+ * @param index the index to test.
+ * @throws IndexOutOfBoundsException Thrown when the index is not the range {@code 0 <= index <= size}.
+ */
+ protected void validateIndex(final int index) {
+ if (index < 0 || index >= size) {
+ throw new StringIndexOutOfBoundsException(index);
+ }
+ }
+
+ /**
+ * Validates parameters defining a range of the builder.
+ *
+ * @param startIndex the start index, inclusive, must be valid
+ * @param endIndex the end index, exclusive, must be valid except that if too large it is treated as end of string
+ * @return A valid end index.
+ * @throws StringIndexOutOfBoundsException if the index is invalid
+ */
+ protected int validateRange(final int startIndex, int endIndex) {
+ if (startIndex < 0) {
+ throw new StringIndexOutOfBoundsException(startIndex);
+ }
+ if (endIndex > size) {
+ endIndex = size;
+ }
+ if (startIndex > endIndex) {
+ throw new StringIndexOutOfBoundsException("end < start");
+ }
+ return endIndex;
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/text/WordUtils.java b/src/main/java/org/apache/commons/text/WordUtils.java
index 1a6a407fd5..76306968b6 100644
--- a/src/main/java/org/apache/commons/text/WordUtils.java
+++ b/src/main/java/org/apache/commons/text/WordUtils.java
@@ -31,8 +31,8 @@
*
- * This class tries to handle {@code WordUtils} instances should NOT be constructed in
* standard programming. Instead, the class should be used as
- * This constructor is public to permit tools that require a JavaBean
* instance to operate. Wraps a single line of text, identifying words by Wraps a single line of text, identifying words by {@code ' '}. New lines will be separated by the system property line separator.
* Very long words, such as URLs will not be wrapped. Leading spaces on a new line are stripped.
* Trailing spaces are not stripped. Wraps a single line of text, identifying words by Wraps a single line of text, identifying words by {@code ' '}. Leading spaces on a new line are stripped.
* Trailing spaces are not stripped. Wraps a single line of text, identifying words by Wraps a single line of text, identifying words by {@code wrapOn}. Leading spaces on a new line are stripped.
* Trailing spaces are not stripped. Whitespace is defined by {@link Character#isWhitespace(char)}.
- * A The delimiters represent a set of characters understood to separate words.
* The first string character and the first non-delimiter character after a
- * delimiter will be capitalized. A A {@code null} input String returns {@code null}.
* Capitalization uses the Unicode title case, normally equivalent to
* upper case. Converts all the whitespace separated words in a String into capitalized words,
* that is each word is made up of a titlecase character and then a series of
- * lowercase characters. Whitespace is defined by {@link Character#isWhitespace(char)}.
- * A Converts all the delimiter separated words in a String into capitalized words,
* that is each word is made up of a titlecase character and then a series of
- * lowercase characters. The delimiters represent a set of characters understood to separate words.
* The first string character and the first non-delimiter character after a
- * delimiter will be capitalized. A A {@code null} input String returns {@code null}.
* Capitalization uses the Unicode title case, normally equivalent to
* upper case. Whitespace is defined by {@link Character#isWhitespace(char)}.
- * A The delimiters represent a set of characters understood to separate words.
* The first string character and the first non-delimiter character after a
- * delimiter will be uncapitalized. Whitespace is defined by {@link Character#isWhitespace(char)}.
- * A Whitespace is defined by {@link Character#isWhitespace(char)}.
- * A Whitespace is defined by {@link Character#isWhitespace(char)}.
- * A If the delimiters array is null, then Whitespace is used.
* Whitespace is defined by {@link Character#isWhitespace(char)}.
- * A
@@ -69,7 +69,7 @@ protected EditCommand(final T object) {
/**
* Returns the object associated with this command.
*
- * @return the object on which the command is applied
+ * @return The object on which the command is applied
*/
protected T getObject() {
return object;
diff --git a/src/main/java/org/apache/commons/text/diff/EditScript.java b/src/main/java/org/apache/commons/text/diff/EditScript.java
index bf4b1853d2..e179818449 100644
--- a/src/main/java/org/apache/commons/text/diff/EditScript.java
+++ b/src/main/java/org/apache/commons/text/diff/EditScript.java
@@ -32,7 +32,7 @@
* commands} come from the second sequence and that the objects embedded in
* either the {@link DeleteCommand delete commands} or {@link KeepCommand keep
* commands} come from the first sequence. This can be important if subclassing
- * is used for some elements in the first sequence and the
- * When one object of the first sequence
- * The replacement is defined as replacing the
* It is guaranteed that the comparisons will always be done as
- *
* Comparison can be seen from two points of view: either as giving the smallest
* modification allowing to transform the first sequence into the second one, or
* as giving the longest sequence which is a subsequence of both initial
- * sequences. The
* It is guaranteed that the comparisons will always be done as
- *
+ * Using this Reader avoids reading a whole file into memory as a {@code String} to perform string substitution, for
+ * example, when a Servlet filters a file to a client.
+ *
+ * This class is not thread-safe.
+ *
+ * {@link org.apache.commons.text.io.StringSubstitutorReader} is a {@link java.io.Reader} that performs string
+ * substitution on a source {@code Reader} using a {@link org.apache.commons.text.StringSubstitutor}.
+ *
+ * Using this Reader avoids reading a whole file into memory as a {@code String} to perform string substitution, for
+ * example, when a Servlet filters a file to a client.
+ * A function's second input type
+ *
+ * @since 1.9
+ */
+final class BiFunctionStringLookup implements BiStringLookup {
+
+ /**
+ * Creates a new instance backed by a Function.
+ *
+ * @param
+ * If the function is null, then null is returned. The function result object is converted to a string using
+ * toString().
+ *
+ * This class represents the simplest form of a string to string map. It has a benefit over a map in that it can create
+ * the result on demand based on the key.
+ *
+ * For example, it would be possible to implement a lookup that used the key as a primary key, and looked up the value
+ * on demand from the database.
+ *
+ * Like {@link BiFunction} is a variant of {@link Function}, this {@code BiStringLookup} is a variant of
+ * {@link StringLookup}.
+ *
+ * The internal implementation may use any mechanism to return the value. The simplest implementation is to use a
+ * Map. However, virtually any implementation is possible.
+ *
+ * For example, it would be possible to implement a lookup that used the key as a primary key, and looked up the
+ * value on demand from the database Or, a numeric based implementation could be created that treats the key as an
+ * integer, increments the value and return the result as a string - converting 1 to 2, 15 to 16 etc.
+ *
+ * This method always returns a String, regardless of the underlying data, by converting it as necessary. For
+ * example:
+ *
+ * Looks up the value of a fully-qualified static final value.
+ *
+ * Sometimes it is necessary in a configuration file to refer to a constant defined in a class. This can be done with
+ * this lookup implementation. Variable names must be in the format {@code apackage.AClass.AFIELD}. The
+ * {@code lookup(String)} method will split the passed in string at the last dot, separating the fully qualified class
+ * name and the name of the constant (i.e. static final) member field. Then the class is loaded and the field's
+ * value is obtained using reflection.
+ *
+ * Once retrieved values are cached for fast access. This class is thread-safe. It can be used as a standard (i.e.
+ * global) lookup object and serve multiple clients concurrently.
+ *
+ * Using a {@link StringLookup} from the {@link StringLookupFactory}:
+ *
+ * Using a {@link StringSubstitutor}:
+ *
+ * The above examples convert {@code java.awt.event.KeyEvent.VK_ESCAPE} to {@code "27"}.
+ *
+ * This class was adapted from Apache Commons Configuration.
+ *
+ * Using a {@link StringLookup} from the {@link StringLookupFactory}:
+ *
+ * Using a {@link StringSubstitutor}:
+ *
+ * The above examples convert {@code "yyyy-MM-dd"} to today's date, for example, {@code "2019-08-04"}.
+ *
+ * This enum was adapted and expanded from Apache Commons Configuration 2.4.
+ *
+ * The lookup keys are:
+ *
+ * Using a {@link StringLookup} from the {@link StringLookupFactory}:
+ *
+ * Using a {@link StringSubstitutor}:
+ *
+ * The above examples convert {@code "address|apache.org"} to {@code "95.216.24.32} (or {@code "40.79.78.1"}).
+ *
+ * Using a {@link StringLookup} from the {@link StringLookupFactory}:
+ *
+ * Using a {@link StringSubstitutor}:
+ *
+ * The above examples convert {@code "UTF-8:SomePath"} to the contents of the file.
+ *
+ * For example: "com/domain/document.xml:/path/to/node".
+ *
+ * If the function is null, then null is returned. The function result object is converted to a string using
+ * toString().
+ *
+ * Uses the {@link StringLookupFactory default lookups}.
+ *
+ * Uses the {@link StringLookupFactory default lookups}.
+ *
+ * Uses the {@link StringLookupFactory default lookups}.
+ *
+ * The lookup keys with examples are:
+ *
+ * Using a {@link StringLookup} from the {@link StringLookupFactory}:
+ *
+ * Using a {@link StringSubstitutor}:
+ *
+ * The above examples convert {@code "version"} to the current VM version, for example,
+ * {@code "Java version 1.8.0_181"}.
+ *
+ * The lookup keys with examples are:
+ *
+ * The lookup keys are:
+ *
+ * Looks up the value for a given key in the format "Document::Key".
+ *
+ * Note the use of "::" instead of ":" to allow for "C:" drive letters in paths.
+ *
+ * For example: "com/domain/document.properties:key".
+ *
+ * For example: "com/domain/document.xml::/path/to/node".
+ *
+ * Note the use of "::" instead of ":" to allow for "C:" drive letters in paths.
+ *
+ * Looks up the value for a given key in the format "BundleName:BundleKey".
+ *
+ * For example: "com.domain.messages:MyKey".
+ *
+ * Execute the script with the engine name in the format "EngineName:Script".
+ *
+ * For example: {@code "javascript:3 + 4"}.
+ *
+ * Using a {@link StringSubstitutor}:
+ *
+ * For example: {@code "javascript:3 + 4"}.
+ *
+ * This class represents the simplest form of a string to string map. It has a benefit over a map in that it can create
+ * the result on demand based on the key.
+ *
+ * For example, it would be possible to implement a lookup that used the key as a primary key, and looked up the value
+ * on demand from the database.
+ *
+ * The internal implementation may use any mechanism to return the value. The simplest implementation is to use a
+ * Map. However, virtually any implementation is possible.
+ *
+ * For example, it would be possible to implement a lookup that used the key as a primary key, and looked up the
+ * value on demand from the database Or, a numeric based implementation could be created that treats the key as an
+ * integer, increments the value and return the result as a string - converting 1 to 2, 15 to 16 etc.
+ *
+ * This method always returns a String, regardless of the underlying data, by converting it as necessary. For
+ * example:
+ *
+ * The "classic" look up is {@link #mapStringLookup(Map)}.
+ *
+ * The methods for variable interpolation (A.K.A. variable substitution) are:
+ *
+ * The default lookups are:
+ *
+ * We also provide functional lookups used as building blocks for other lookups.
+ *
+ * Using a {@link StringLookup} from the {@link StringLookupFactory}:
+ *
+ * Using a {@link StringSubstitutor}:
+ *
+ * The above examples convert {@code "SGVsbG9Xb3JsZCE="} to {@code "HelloWorld!"}.
+ *
+ * Using a {@link StringLookup} from the {@link StringLookupFactory}:
+ *
+ * Using a {@link StringSubstitutor}:
+ *
+ * The above examples convert {@code "HelloWorld!"} to {@code "SGVsbG9Xb3JsZCE="}.
+ *
+ * Using a {@link StringLookup} from the {@link StringLookupFactory}:
+ *
+ * Using a {@link StringSubstitutor}:
+ *
+ * The above examples convert (on Linux) {@code "USER"} to the current user name. On Windows 10, you would use
+ * {@code "USERNAME"} to the same effect.
+ *
+ * Using a {@link StringLookup} from the {@link StringLookupFactory}:
+ *
+ * Using a {@link StringSubstitutor}:
+ *
+ * The above examples convert {@code "SGVsbG9Xb3JsZCE="} to {@code "HelloWorld!"}.
+ *
+ * Using a {@link StringLookup} from the {@link StringLookupFactory}:
+ *
+ * Using a {@link StringSubstitutor}:
+ *
+ * The above examples convert {@code } to {@code "SGVsbG9Xb3JsZCE="}.
+ *
+ * Using a {@link StringLookup} from the {@link StringLookupFactory}:
+ *
+ * Using a {@link StringSubstitutor}:
+ *
+ * The above examples convert {@code "SGVsbG9Xb3JsZCE="} to {@code "HelloWorld!"}.
+ *
+ * Sometimes it is necessary in a configuration file to refer to a constant defined in a class. This can be done
+ * with this lookup implementation. Variable names must be in the format {@code apackage.AClass.AFIELD}. The
+ * {@code lookup(String)} method will split the passed in string at the last dot, separating the fully qualified
+ * class name and the name of the constant (i.e. static final) member field. Then the class is loaded and the
+ * field's value is obtained using reflection.
+ *
+ * Once retrieved values are cached for fast access. This class is thread-safe. It can be used as a standard (i.e.
+ * global) lookup object and serve multiple clients concurrently.
+ *
+ * Using a {@link StringLookup} from the {@link StringLookupFactory}:
+ *
+ * Using a {@link StringSubstitutor}:
+ *
+ * The above examples convert {@code java.awt.event.KeyEvent.VK_ESCAPE} to {@code "27"}.
+ *
+ * Using a {@link StringLookup} from the {@link StringLookupFactory}:
+ *
+ * Using a {@link StringSubstitutor}:
+ *
+ * The above examples convert {@code "yyyy-MM-dd"} to todays's date, for example, {@code "2019-08-04"}.
+ *
+ * Using a {@link StringLookup} from the {@link StringLookupFactory}:
+ *
+ * Using a {@link StringSubstitutor}:
+ *
+ * The above examples convert {@code "address|apache.org"} to {@code "95.216.24.32} (or {@code "40.79.78.1"}).
+ *
+ * Using a {@link StringLookup} from the {@link StringLookupFactory}:
+ *
+ * Using a {@link StringSubstitutor}:
+ *
+ * The above examples convert (on Linux) {@code "USER"} to the current user name. On Windows 10, you would use
+ * {@code "USERNAME"} to the same effect.
+ *
+ * Using a {@link StringLookup} from the {@link StringLookupFactory}:
+ *
+ * Using a {@link StringSubstitutor}:
+ *
+ * The above examples convert {@code "UTF-8:com/domain/document.properties"} to the contents of the file.
+ *
+ * The lookups available to an interpolator are defined in
+ *
+ * Using a {@link StringLookup} from the {@link StringLookupFactory}:
+ *
+ * Using a {@link StringSubstitutor}:
+ *
+ * The above examples convert {@code "${sys:os.name}, ${env:USER}"} to the OS name and Linux user name.
+ *
+ * If {@code addDefaultLookups} is true, the following lookups are used in addition to the ones provided in
+ * {@code stringLookupMap}:
+ *
+ * The lookup keys with examples are:
+ *
+ * Using a {@link StringLookup} from the {@link StringLookupFactory}:
+ *
+ * Using a {@link StringSubstitutor}:
+ *
+ * The above examples convert {@code "version"} to the current VM version, for example,
+ * {@code "Java version 1.8.0_181"}.
+ *
+ * Using a {@link StringLookup} from the {@link StringLookupFactory}:
+ *
+ * Using a {@link StringSubstitutor}:
+ *
+ * The above examples convert {@code "canonical-name"} to the current host name, for example,
+ * {@code "EXAMPLE.apache.org"}.
+ *
+ * Looks up the value for the key in the format "DocumentPath::MyKey".
+ *
+ * Note the use of "::" instead of ":" to allow for "C:" drive letters in paths.
+ *
+ * For example: "com/domain/document.properties::MyKey".
+ *
+ * Using a {@link StringLookup} from the {@link StringLookupFactory}:
+ *
+ * Using a {@link StringSubstitutor}:
+ *
+ * The above examples convert {@code "com/domain/document.properties::MyKey"} to the key value in the properties
+ * file at the path "com/domain/document.properties".
+ *
+ * Looks up the value for a given key in the format "BundleName:BundleKey".
+ *
+ * For example: "com.domain.messages:MyKey".
+ *
+ * Using a {@link StringLookup} from the {@link StringLookupFactory}:
+ *
+ * Using a {@link StringSubstitutor}:
+ *
+ * The above examples convert {@code "com.domain.messages:MyKey"} to the key value in the resource bundle at
+ * {@code "com.domain.messages"}.
+ *
+ * Looks up the value for a given key in the format "MyKey".
+ *
+ * For example: "MyKey".
+ *
+ * Using a {@link StringLookup} from the {@link StringLookupFactory}:
+ *
+ * The above example converts {@code "MyKey"} to the key value in the resource bundle at
+ * {@code "com.domain.messages"}.
+ *
+ * Looks up the value for the key in the format "ScriptEngineName:Script".
+ *
+ * For example: "javascript:3 + 4".
+ *
+ * Using a {@link StringLookup} from the {@link StringLookupFactory}:
+ *
+ * Using a {@link StringSubstitutor}:
+ *
+ * The above examples convert {@code "javascript:3 + 4"} to {@code "7"}.
+ *
+ * Using a {@link StringLookup} from the {@link StringLookupFactory}:
+ *
+ * Using a {@link StringSubstitutor}:
+ *
+ * The above examples convert {@code "os.name"} to the operating system name.
+ *
+ * Decodes URL Strings using the UTF-8 encoding.
+ *
+ * For example: "Hello%20World%21" becomes "Hello World!".
+ *
+ * Using a {@link StringLookup} from the {@link StringLookupFactory}:
+ *
+ * Using a {@link StringSubstitutor}:
+ *
+ * The above examples convert {@code "Hello%20World%21"} to {@code "Hello World!"}.
+ *
+ * Decodes URL Strings using the UTF-8 encoding.
+ *
+ * For example: "Hello World!" becomes "Hello+World%21".
+ *
+ * Using a {@link StringLookup} from the {@link StringLookupFactory}:
+ *
+ * Using a {@link StringSubstitutor}:
+ *
+ * The above examples convert {@code "Hello World!"} to {@code "Hello%20World%21"}.
+ *
+ * Looks up the value for the key in the format "CharsetName:URL".
+ *
+ * For example, using the HTTP scheme: "UTF-8:http://www.google.com"
+ *
+ * For example, using the file scheme:
+ * "UTF-8:file:///C:/somehome/commons/commons-text/src/test/resources/document.properties"
+ *
+ * Using a {@link StringLookup} from the {@link StringLookupFactory}:
+ *
+ * Using a {@link StringSubstitutor}:
+ *
+ * The above examples convert {@code "UTF-8:https://www.apache.org"} to the contents of that page.
+ *
+ * Looks up the value for the key in the format "DocumentPath:XPath".
+ *
+ * For example: "com/domain/document.xml:/path/to/node".
+ *
+ * Using a {@link StringLookup} from the {@link StringLookupFactory}:
+ *
+ * Using a {@link StringSubstitutor}:
+ *
+ * The above examples convert {@code "com/domain/document.xml:/path/to/node"} to the value of the XPath in the XML
+ * document.
+ *
+ * Looks up the value for a given key in the format "Charset:URL".
+ *
+ * For example: "UTF-8:com/domain/document.properties".
+ *
+ * For example: "com/domain/document.xml:/path/to/node".
+ *
+ * Looks up the value for a given key in the format "Document:XPath".
+ *
+ * For example: "com/domain/document.xml:/path/to/node".
+ *
+ * For example: "com/domain/document.xml:/path/to/node".
+ *
+ * Provides algorithms for looking up strings for use with a {@link org.apache.commons.text.StringSubstitutor
+ * StringSubstitutor}. The main class in this package is {@link org.apache.commons.text.lookup.StringLookupFactory
+ * StringLookupFactory}.
+ *
+ * Use {@link org.apache.commons.text.lookup.StringLookupFactory StringLookupFactory} to create instances of string
+ * lookups or access singleton string lookups. The main interface is {@link org.apache.commons.text.lookup.StringLookup
+ * StringLookup} which is implemented here in package private classes.
+ *
+ * Like {@link java.util.function.BiFunction BiFunction} is a variant of {@link java.util.function.Function Function},
+ * this {@link org.apache.commons.text.lookup.BiStringLookup BiStringLookup} is a variant of
+ * {@link org.apache.commons.text.lookup.StringLookup StringLookup}.
+ *
+ * The initial implementation was adapted from Apache Commons Log4j 2.11.0.
+ *
+ * Thread=safe.
+ *
+ * Thread=safe.
+ *
+ * Thread=safe.
+ *
+ * Thread=safe.
+ *
+ * Thread=safe.
+ *
+ * Thread=safe.
+ *
+ * This method is called to check for a match. The parameter {@code pos} represents the current position to be
+ * checked in the string {@code buffer} (a character array which must not be changed). The API guarantees that
+ * {@code pos} is a valid index for {@code buffer}.
+ *
+ * The matching code may check one character or many. It may check characters preceding {@code pos} as well as those
+ * after.
+ *
+ * It must return zero for no match, or a positive number if a match was found. The number indicates the number of
+ * characters that matched.
+ *
+ * This method is called to check for a match against a source {@code buffer}. The parameter {@code start}
+ * represents the start position to be checked in the {@code buffer} (a character array which MUST not be changed).
+ * The implementation SHOULD guarantees that {@code start} is a valid index in {@code buffer}.
+ *
+ * The character array may be larger than the active area to be matched. Only values in the buffer between the
+ * specified indices may be accessed, in other words: {@code bufferStart <= start < bufferEnd}.
+ *
+ * The matching code may check one character or many. It may check characters preceding {@code start} as well as
+ * those after, so long as no checks exceed the bounds specified.
+ *
+ * It must return zero for no match, or a positive number if a match was found. The number indicates the number of
+ * characters that matched.
+ *
+ * This method is called to check for a match. The parameter {@code pos} represents the current position to be
+ * checked in the string {@code buffer} (a character array which must not be changed). The API guarantees that
+ * {@code pos} is a valid index for {@code buffer}.
+ *
+ * The matching code may check one character or many. It may check characters preceding {@code pos} as well as those
+ * after.
+ *
+ * It must return zero for no match, or a positive number if a match was found. The number indicates the number of
+ * characters that matched.
+ *
+ * This method is called to check for a match against a source {@code buffer}. The parameter {@code start}
+ * represents the start position to be checked in the {@code buffer} (a character array which MUST not be changed).
+ * The implementation SHOULD guarantees that {@code start} is a valid index in {@code buffer}.
+ *
+ * The character array may be larger than the active area to be matched. Only values in the buffer between the
+ * specified indices may be accessed, in other words: {@code bufferStart <= start < bufferEnd}.
+ *
+ * The matching code may check one character or many. It may check characters preceding {@code start} as well as
+ * those after, so long as no checks exceed the bounds specified.
+ *
+ * It must return zero for no match, or a positive number if a match was found. The number indicates the number of
+ * characters that matched.
+ *
+ * Provides algorithms for matching up strings for use with a {@link org.apache.commons.text.StringSubstitutor
+ * StringSubstitutor}. The main class here is {@link org.apache.commons.text.matcher.StringMatcherFactory
+ * StringMatcherFactory}.
+ *
- * An edit distance is a formal metric on the Kleene closure (
* This is a BiFunction<CharSequence, CharSequence, R>.
- * The Stores the size of set A, set B and the intersection of A and B
+ * ({@code |A ∩ B|}). This class is immutable. It is assumed that the type {@code T} correctly conforms to the requirements for storage
+ * within a {@link Set} or {@link HashMap}. Ideally the type is immutable and implements
+ * {@link Object#equals(Object)} and {@link Object#hashCode()}. For the intended purpose the Bag does not have to be a {@link Collection}. It does not
+ * even have to know its own size.
+ */
+ private class TinyBag {
+ /** The backing map. */
+ private final Map If the converter returns a {@link Set} then the intersection result will
+ * not include duplicates. Any other {@link Collection} is used to produce a result
+ * that will include duplicates in the intersect and union.
+ *
+ * @param converter the converter used to create the elements from the characters
+ * @throws IllegalArgumentException if the converter is null
+ */
+ public IntersectionSimilarity(final Function
- * The Jaro measure is the weighted sum of percentage of matched characters
- * from each file and transposed characters. Winkler increased this measure
- * for matching initial characters.
- *
- * This implementation is based on the Jaro Winkler similarity algorithm
- * from
- * http://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance.
- *
- * This code has been adapted from Apache Commons Lang 3.3.
- *
+ * The Jaro measure is the weighted sum of percentage of matched characters
+ * from each file and transposed characters. Winkler increased this measure
+ * for matching initial characters.
+ *
+ * This implementation is based on the Jaro Winkler similarity algorithm
+ * from
+ * http://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance.
+ *
+ * This code has been adapted from Apache Commons Lang 3.3.
+ *
- * Note, a substring and subsequence are not necessarily the same thing. Indeed,
- * Note, a substring and subsequence are not necessarily the same thing. Indeed,
- * We Define a SimilarityScore to be a function
@@ -39,9 +39,9 @@
*
*
* Further, this intended to be BiFunction<CharSequence, CharSequence, R>.
- * The The {@link org.apache.commons.text.similarity.CosineDistance Cosine Distance}
* utilises a {@link org.apache.commons.text.similarity.RegexTokenizer regular expression tokenizer (\w+)}.
* And the {@link org.apache.commons.text.similarity.LevenshteinDistance Levenshtein Distance}'s
- * behaviour can be changed to take into consideration a maximum throughput. Returns an upper case hexadecimal Returns an upper case hexadecimal {@code String} for the given
* character. This class holds inner classes for escaping/unescaping Comma Separated Values.
- *
- * In general the use a high level API like commons-csv should be preferred over these
- * low level classes.
+ * This class holds inner classes for escaping/unescaping Comma Separated Values.
+ *
+ * In general the use a high level API like Apache Commons
+ * CSV should be preferred over these low level classes.
+ *
- * Constructs a
- * Constructs a
- * Constructs a
- * Constructs a
- * Constructs a Constructs a Constructs a {@code NumericEntityEscaper} for the specified range. This is
+ * the underlying method for the other constructors/builders. The {@code below}
+ * and {@code above} boundaries are inclusive when {@code between} is
+ * {@code true} and exclusive when it is {@code false}. Constructs a Constructs a {@code NumericEntityEscaper} for all characters. Constructs a Constructs a {@code NumericEntityEscaper} below the specified value (exclusive). Constructs a Constructs a {@code NumericEntityEscaper} above the specified value (exclusive). Constructs a Constructs a {@code NumericEntityEscaper} between the specified values (inclusive). Constructs a Constructs a {@code NumericEntityEscaper} outside of the specified values (exclusive).null to this string builder.
- *
- * @return this, to enable chaining
- */
- public StrBuilder appendNull() {
- if (nullText == null) {
- return this;
- }
- return append(nullText);
- }
-
- /**
- * Appends an object to this string builder.
- * Appending null will call {@link #appendNull()}.
- *
- * @param obj the object to append
- * @return this, to enable chaining
- */
- public StrBuilder append(final Object obj) {
- if (obj == null) {
- return appendNull();
- }
- if (obj instanceof CharSequence) {
- return append((CharSequence) obj);
- }
- return append(obj.toString());
- }
-
- /**
- * Appends a CharSequence to this string builder.
- * Appending null will call {@link #appendNull()}.
- *
- * @param seq the CharSequence to append
- * @return this, to enable chaining
- */
- @Override
- public StrBuilder append(final CharSequence seq) {
- if (seq == null) {
- return appendNull();
- }
- if (seq instanceof StrBuilder) {
- return append((StrBuilder) seq);
- }
- if (seq instanceof StringBuilder) {
- return append((StringBuilder) seq);
- }
- if (seq instanceof StringBuffer) {
- return append((StringBuffer) seq);
- }
- if (seq instanceof CharBuffer) {
- return append((CharBuffer) seq);
- }
- return append(seq.toString());
- }
-
- /**
- * Appends part of a CharSequence to this string builder.
- * Appending null will call {@link #appendNull()}.
- *
- * @param seq the CharSequence to append
- * @param startIndex the start index, inclusive, must be valid
- * @param length the length to append, must be valid
- * @return this, to enable chaining
- */
- @Override
- public StrBuilder append(final CharSequence seq, final int startIndex, final int length) {
- if (seq == null) {
- return appendNull();
- }
- return append(seq.toString(), startIndex, length);
- }
-
- /**
- * Appends a string to this string builder.
- * Appending null will call {@link #appendNull()}.
- *
- * @param str the string to append
- * @return this, to enable chaining
+ * @param str the string to append
+ * @return this, to enable chaining
*/
public StrBuilder append(final String str) {
if (str == null) {
@@ -572,7 +619,6 @@ public StrBuilder append(final String str) {
return this;
}
-
/**
* Appends part of a string to this string builder.
* Appending null will call {@link #appendNull()}.
@@ -589,7 +635,7 @@ public StrBuilder append(final String str, final int startIndex, final int lengt
if (startIndex < 0 || startIndex > str.length()) {
throw new StringIndexOutOfBoundsException("startIndex must be valid");
}
- if (length < 0 || (startIndex + length) > str.length()) {
+ if (length < 0 || startIndex + length > str.length()) {
throw new StringIndexOutOfBoundsException("length must be valid");
}
if (length > 0) {
@@ -613,60 +659,6 @@ public StrBuilder append(final String format, final Object... objs) {
return append(String.format(format, objs));
}
- /**
- * Appends the contents of a char buffer to this string builder.
- * Appending null will call {@link #appendNull()}.
- *
- * @param buf the char buffer to append
- * @return this, to enable chaining
- */
- public StrBuilder append(final CharBuffer buf) {
- if (buf == null) {
- return appendNull();
- }
- if (buf.hasArray()) {
- final int length = buf.remaining();
- final int len = length();
- ensureCapacity(len + length);
- System.arraycopy(buf.array(), buf.arrayOffset() + buf.position(), buffer, len, length);
- size += length;
- } else {
- append(buf.toString());
- }
- return this;
- }
-
- /**
- * Appends the contents of a char buffer to this string builder.
- * Appending null will call {@link #appendNull()}.
- *
- * @param buf the char buffer to append
- * @param startIndex the start index, inclusive, must be valid
- * @param length the length to append, must be valid
- * @return this, to enable chaining
- */
- public StrBuilder append(final CharBuffer buf, final int startIndex, final int length) {
- if (buf == null) {
- return appendNull();
- }
- if (buf.hasArray()) {
- final int totalLength = buf.remaining();
- if (startIndex < 0 || startIndex > totalLength) {
- throw new StringIndexOutOfBoundsException("startIndex must be valid");
- }
- if (length < 0 || (startIndex + length) > totalLength) {
- throw new StringIndexOutOfBoundsException("length must be valid");
- }
- final int len = length();
- ensureCapacity(len + length);
- System.arraycopy(buf.array(), buf.arrayOffset() + buf.position() + startIndex, buffer, len, length);
- size += length;
- } else {
- append(buf.toString(), startIndex, length);
- }
- return this;
- }
-
/**
* Appends a string buffer to this string builder.
* Appending null will call {@link #appendNull()}.
@@ -704,7 +696,7 @@ public StrBuilder append(final StringBuffer str, final int startIndex, final int
if (startIndex < 0 || startIndex > str.length()) {
throw new StringIndexOutOfBoundsException("startIndex must be valid");
}
- if (length < 0 || (startIndex + length) > str.length()) {
+ if (length < 0 || startIndex + length > str.length()) {
throw new StringIndexOutOfBoundsException("length must be valid");
}
if (length > 0) {
@@ -753,7 +745,7 @@ public StrBuilder append(final StringBuilder str, final int startIndex, final in
if (startIndex < 0 || startIndex > str.length()) {
throw new StringIndexOutOfBoundsException("startIndex must be valid");
}
- if (length < 0 || (startIndex + length) > str.length()) {
+ if (length < 0 || startIndex + length > str.length()) {
throw new StringIndexOutOfBoundsException("length must be valid");
}
if (length > 0) {
@@ -766,179 +758,242 @@ public StrBuilder append(final StringBuilder str, final int startIndex, final in
}
/**
- * Appends another string builder to this string builder.
- * Appending null will call {@link #appendNull()}.
+ * 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 str the string builder to append
+ * @param iterable the iterable to append
* @return this, to enable chaining
*/
- public StrBuilder append(final StrBuilder str) {
- if (str == null) {
- return appendNull();
- }
- final int strLen = str.length();
- if (strLen > 0) {
- final int len = length();
- ensureCapacity(len + strLen);
- System.arraycopy(str.buffer, 0, buffer, len, strLen);
- size += strLen;
+ public StrBuilder appendAll(final Iterable> iterable) {
+ if (iterable != null) {
+ for (final Object o : iterable) {
+ append(o);
+ }
}
return this;
}
/**
- * Appends part of a string builder to this string builder.
- * Appending null will call {@link #appendNull()}.
+ * 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 str the string to append
- * @param startIndex the start index, inclusive, must be valid
- * @param length the length to append, must be valid
+ * @param it the iterator to append
* @return this, to enable chaining
*/
- public StrBuilder append(final StrBuilder str, final int startIndex, final int length) {
- if (str == null) {
- return appendNull();
- }
- if (startIndex < 0 || startIndex > str.length()) {
- throw new StringIndexOutOfBoundsException("startIndex must be valid");
- }
- if (length < 0 || (startIndex + length) > str.length()) {
- throw new StringIndexOutOfBoundsException("length must be valid");
- }
- if (length > 0) {
- final int len = length();
- ensureCapacity(len + length);
- str.getChars(startIndex, startIndex + length, buffer, len);
- size += length;
+ public StrBuilder appendAll(final Iterator> it) {
+ if (it != null) {
+ while (it.hasNext()) {
+ append(it.next());
+ }
}
return this;
}
+
+ //-----------------------------------------------------------------------
/**
- * Appends a char array to the string builder.
- * Appending null will call {@link #appendNull()}.
+ * 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 chars the char array to append
+ * @param String.valueOf.
+ * Appends a double value followed by a new line to the string builder using {@code String.valueOf}.
*
* @param value the value to append
* @return this, to enable chaining
*/
- public StrBuilder append(final int value) {
- return append(String.valueOf(value));
+ public StrBuilder appendln(final double value) {
+ return append(value).appendNewLine();
}
/**
- * Appends a long value to the string builder using String.valueOf.
+ * Appends a float value followed by a new line to the string builder using {@code String.valueOf}.
*
* @param value the value to append
* @return this, to enable chaining
*/
- public StrBuilder append(final long value) {
- return append(String.valueOf(value));
+ public StrBuilder appendln(final float value) {
+ return append(value).appendNewLine();
}
/**
- * Appends a float value to the string builder using String.valueOf.
+ * Appends an int value followed by a new line to the string builder using {@code String.valueOf}.
*
* @param value the value to append
* @return this, to enable chaining
*/
- public StrBuilder append(final float value) {
- return append(String.valueOf(value));
+ public StrBuilder appendln(final int value) {
+ return append(value).appendNewLine();
}
/**
- * Appends a double value to the string builder using String.valueOf.
+ * Appends a long value followed by a new line to the string builder using {@code String.valueOf}.
*
* @param value the value to append
* @return this, to enable chaining
*/
- public StrBuilder append(final double value) {
- return append(String.valueOf(value));
+ public StrBuilder appendln(final long value) {
+ return append(value).appendNewLine();
}
//-----------------------------------------------------------------------
@@ -953,6 +1008,30 @@ public StrBuilder appendln(final Object obj) {
return append(obj).appendNewLine();
}
+ /**
+ * Appends another string builder followed by a new line to this string builder.
+ * Appending null will call {@link #appendNull()}.
+ *
+ * @param str the string builder to append
+ * @return this, to enable chaining
+ */
+ public StrBuilder appendln(final StrBuilder str) {
+ return append(str).appendNewLine();
+ }
+
+ /**
+ * Appends part of a string builder followed by a new line to this string builder.
+ * Appending null will call {@link #appendNull()}.
+ *
+ * @param str the string to append
+ * @param startIndex the start index, inclusive, must be valid
+ * @param length the length to append, must be valid
+ * @return this, to enable chaining
+ */
+ public StrBuilder appendln(final StrBuilder str, final int startIndex, final int length) {
+ return append(str, startIndex, length).appendNewLine();
+ }
+
/**
* Appends a string followed by a new line to this string builder.
* Appending null will call {@link #appendNull()}.
@@ -1001,204 +1080,314 @@ public StrBuilder appendln(final StringBuffer str) {
}
/**
- * Appends a string builder followed by a new line to this string builder.
+ * 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 builder to append
+ * @param str the string to append
+ * @param startIndex the start index, inclusive, must be valid
+ * @param length the length to append, must be valid
* @return this, to enable chaining
*/
- public StrBuilder appendln(final StringBuilder str) {
- return append(str).appendNewLine();
+ public StrBuilder appendln(final StringBuffer str, final int startIndex, final int length) {
+ return append(str, startIndex, length).appendNewLine();
}
/**
- * Appends part of a string builder followed by a new line to this string builder.
+ * 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
- * @param startIndex the start index, inclusive, must be valid
- * @param length the length to append, must be valid
* @return this, to enable chaining
*/
- public StrBuilder appendln(final StringBuilder str, final int startIndex, final int length) {
- return append(str, startIndex, length).appendNewLine();
+ public StrBuilder appendln(final StringBuilder str) {
+ return append(str).appendNewLine();
}
/**
- * Appends part of a string buffer followed by a new line to this string builder.
+ * 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 str the string builder to append
* @param startIndex the start index, inclusive, must be valid
* @param length the length to append, must be valid
* @return this, to enable chaining
*/
- public StrBuilder appendln(final StringBuffer str, final int startIndex, final int length) {
+ public StrBuilder appendln(final StringBuilder 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()}.
+ * Appends the new line string to this string builder.
+ *
+ * for (Iterator it = list.iterator(); it.hasNext();){
+ * appendSeparator(',');
+ * append(it.next());
+ * }
+ *
+ * Note that for this simple example, you should use
+ * {@link #appendWithSeparators(Iterable, String)}.
*
- * @param chars the char array to append
- * @param startIndex the start index, inclusive, must be valid
- * @param length the length to append, must be valid
+ * @param separator the separator to use
* @return this, to enable chaining
*/
- public StrBuilder appendln(final char[] chars, final int startIndex, final int length) {
- return append(chars, startIndex, length).appendNewLine();
+ public StrBuilder appendSeparator(final char separator) {
+ if (isNotEmpty()) {
+ append(separator);
+ }
+ return this;
}
/**
- * Appends a boolean value followed by a new line to the string builder.
+ * 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
*
- * @param value the value to append
+ * The separator is appended using {@link #append(char)}.
+ * @param standard the separator if builder is not empty
+ * @param defaultIfEmpty the separator if builder is empty
* @return this, to enable chaining
*/
- public StrBuilder appendln(final boolean value) {
- return append(value).appendNewLine();
+ public StrBuilder appendSeparator(final char standard, final char defaultIfEmpty) {
+ if (isNotEmpty()) {
+ append(standard);
+ } else {
+ append(defaultIfEmpty);
+ }
+ return this;
}
/**
- * Appends a char value followed by a new line to the string builder.
+ * Appends a separator to the builder if the loop index is greater than zero.
+ * The separator is appended using {@link #append(char)}.
+ *
+ * for (int i = 0; i < list.size(); i++) {
+ * appendSeparator(",", i);
+ * append(list.get(i));
+ * }
+ *
+ * Note that for this simple example, you should use
+ * {@link #appendWithSeparators(Iterable, String)}.
*
- * @param ch the value to append
+ * @param separator the separator to use
+ * @param loopIndex the loop index
* @return this, to enable chaining
*/
- public StrBuilder appendln(final char ch) {
- return append(ch).appendNewLine();
+ public StrBuilder appendSeparator(final char separator, final int loopIndex) {
+ if (loopIndex > 0) {
+ append(separator);
+ }
+ return this;
}
+ //-----------------------------------------------------------------------
/**
- * Appends an int value followed by a new line to the string builder using String.valueOf.
+ * 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)}.
+ *
+ * for (Iterator it = list.iterator(); it.hasNext();){
+ * appendSeparator(",");
+ * append(it.next());
+ * }
+ *
+ * Note that for this simple example, you should use
+ * {@link #appendWithSeparators(Iterable, String)}.
*
- * @param value the value to append
+ * @param separator the separator to use, null means no separator
* @return this, to enable chaining
*/
- public StrBuilder appendln(final int value) {
- return append(value).appendNewLine();
+ public StrBuilder appendSeparator(final String separator) {
+ return appendSeparator(separator, null);
}
/**
- * Appends a long value followed by a new line to the string builder using String.valueOf.
+ * 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)}.
+ *
+ * for (int i = 0; i < list.size(); i++) {
+ * appendSeparator(",", i);
+ * append(list.get(i));
+ * }
+ *
+ * Note that for this simple example, you should use
+ * {@link #appendWithSeparators(Iterable, String)}.
*
- * @param value the value to append
+ * @param separator the separator to use, null means no separator
+ * @param loopIndex the loop index
* @return this, to enable chaining
*/
- public StrBuilder appendln(final long value) {
- return append(value).appendNewLine();
+ public StrBuilder appendSeparator(final String separator, final int loopIndex) {
+ if (separator != null && loopIndex > 0) {
+ append(separator);
+ }
+ return this;
}
/**
- * Appends a float value followed by a new line to the string builder using String.valueOf.
+ * 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
*
- * @param value the value to append
- * @return this, to enable chaining
- */
- public StrBuilder appendln(final float value) {
- return append(value).appendNewLine();
- }
-
- /**
- * Appends a double value followed by a new line to the string builder using String.valueOf.
+ * Appending a null separator will have no effect.
+ * The separator is appended using {@link #append(String)}.
+ *
+ * StrBuilder whereClause = new StrBuilder();
+ * if(searchCommand.getPriority() != null) {
+ * whereClause.appendSeparator(" and", " where");
+ * whereClause.append(" priority = ?")
+ * }
+ * if(searchCommand.getComponent() != null) {
+ * whereClause.appendSeparator(" and", " where");
+ * whereClause.append(" component = ?")
+ * }
+ * selectClause.append(whereClause)
+ *
*
- * @param value the value to append
+ * @param standard the separator if builder is not empty, null means no separator
+ * @param defaultIfEmpty the separator if builder is empty, null means no separator
* @return this, to enable chaining
*/
- public StrBuilder appendln(final double value) {
- return append(value).appendNewLine();
+ public StrBuilder appendSeparator(final String standard, final String defaultIfEmpty) {
+ final String str = isEmpty() ? defaultIfEmpty : standard;
+ if (str != null) {
+ append(str);
+ }
+ return this;
}
- //-----------------------------------------------------------------------
/**
- * 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)}.
+ * Appends current contents of this {@code StrBuilder} to the
+ * provided {@link Appendable}.
+ *
+ * StrBuilder b = new StrBuilder();
+ * b.append("a b ");
+ * StrTokenizer t = b.asTokenizer();
+ * String[] tokens1 = t.getTokenArray(); // returns a,b
+ * b.append("c d ");
+ * String[] tokens2 = t.getTokenArray(); // returns a,b (c and d ignored)
+ * t.reset(); // reset causes builder changes to be picked up
+ * String[] tokens3 = t.getTokenArray(); // returns a,b,c,d
+ *
+ * In addition to simply intermixing appends and tokenization, you can also
+ * call the set methods on the tokenizer to alter how it tokenizes. Just
+ * remember to call reset when you want to pickup builder changes.
+ *
- * for (Iterator it = list.iterator(); it.hasNext();){
- * appendSeparator(",");
- * append(it.next());
- * }
- *
- * Note that for this simple example, you should use
- * {@link #appendWithSeparators(Iterable, String)}.
+ * This method allows you to populate the contents of the builder
+ * using any standard method that takes a Writer.
+ *
- * StrBuilder whereClause = new StrBuilder();
- * if(searchCommand.getPriority() != null) {
- * whereClause.appendSeparator(" and", " where");
- * whereClause.append(" priority = ?")
- * }
- * if(searchCommand.getComponent() != null) {
- * whereClause.appendSeparator(" and", " where");
- * whereClause.append(" component = ?")
- * }
- * selectClause.append(whereClause)
- *
- *
- * @param standard the separator if builder is not empty, null means no separator
- * @param defaultIfEmpty the separator if builder is empty, null means no separator
- * @return this, to enable chaining
+ * Implement the {@link Builder} interface.
+ * @return The builder as a String
+ * @see #toString()
*/
- public StrBuilder appendSeparator(final String standard, final String defaultIfEmpty) {
- final String str = isEmpty() ? defaultIfEmpty : standard;
- if (str != null) {
- append(str);
- }
- return this;
+ @Override
+ public String build() {
+ return toString();
}
+ //-----------------------------------------------------------------------
/**
- * Appends a separator if the builder is currently non-empty.
- * The separator is appended using {@link #append(char)}.
- *
- * for (Iterator it = list.iterator(); it.hasNext();){
- * appendSeparator(',');
- * append(it.next());
- * }
- *
- * Note that for this simple example, you should use
- * {@link #appendWithSeparators(Iterable, String)}.
+ * Gets the current size of the internal character array buffer.
*
- * @param separator the separator to use
- * @return this, to enable chaining
+ * @return The capacity
*/
- public StrBuilder appendSeparator(final char separator) {
- if (size() > 0) {
- append(separator);
- }
- return this;
+ public int capacity() {
+ return buffer.length;
}
+ //-----------------------------------------------------------------------
/**
- * 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
+ * Gets the character at the specified index.
*
- * 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
+ * @see #setCharAt(int, char)
+ * @see #deleteCharAt(int)
+ * @param index the index to retrieve, must be valid
+ * @return The character at the index
+ * @throws IndexOutOfBoundsException if the index is invalid
*/
- public StrBuilder appendSeparator(final char standard, final char defaultIfEmpty) {
- if (size() > 0) {
- append(standard);
- } else {
- append(defaultIfEmpty);
+ @Override
+ public char charAt(final int index) {
+ if (index < 0 || index >= length()) {
+ throw new StringIndexOutOfBoundsException(index);
}
- return this;
+ return buffer[index];
}
+
/**
- * 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)}.
+ * Clears the string builder (convenience Collections API style method).
*
- * for (int i = 0; i < list.size(); i++) {
- * appendSeparator(",", i);
- * append(list.get(i));
- * }
- *
- * Note that for this simple example, you should use
- * {@link #appendWithSeparators(Iterable, String)}.
+ * This method does not reduce the size of the internal character buffer.
+ * To do that, call {@code clear()} followed by {@link #minimizeCapacity()}.
+ *
- * for (int i = 0; i < list.size(); i++) {
- * appendSeparator(",", i);
- * append(list.get(i));
- * }
- *
- * Note that for this simple example, you should use
- * {@link #appendWithSeparators(Iterable, String)}.
+ * Checks if the string builder contains the specified char.
*
- * @param separator the separator to use
- * @param loopIndex the loop index
- * @return this, to enable chaining
+ * @param ch the character to find
+ * @return true if the builder contains the character
*/
- public StrBuilder appendSeparator(final char separator, final int loopIndex) {
- if (loopIndex > 0) {
- append(separator);
+ 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 this;
+ return false;
}
- //-----------------------------------------------------------------------
/**
- * Appends the pad character to the builder the specified number of times.
+ * Checks if the string builder contains the specified string.
*
- * @param length the length to append, negative means no append
- * @param padChar the character to append
- * @return this, to enable chaining
+ * @param str the string to find
+ * @return true if the builder contains the string
*/
- public StrBuilder appendPadding(final int length, final char padChar) {
- if (length >= 0) {
- ensureCapacity(size + length);
- for (int i = 0; i < length; i++) {
- buffer[size++] = padChar;
- }
- }
- return this;
+ public boolean contains(final String str) {
+ return indexOf(str, 0) >= 0;
}
- //-----------------------------------------------------------------------
/**
- * Appends an object to the builder padding on the left to a fixed width.
- * The toString of the object is used.
- * If the object is larger than the length, the left hand side is lost.
- * If the object is null, the null text value is used.
+ * Checks if the string builder contains a string matched using the
+ * specified matcher.
+ * String.valueOf of the int value is used.
- * If the formatted value is larger than the length, the left hand side is lost.
+ * Deletes the character wherever it occurs in the builder.
*
- * @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
+ * @param ch the character to delete
* @return this, to enable chaining
*/
- public StrBuilder appendFixedWidthPadLeft(final int value, final int width, final char padChar) {
- return appendFixedWidthPadLeft(String.valueOf(value), width, padChar);
+ public StrBuilder deleteAll(final char ch) {
+ for (int i = 0; i < size; i++) {
+ if (buffer[i] == ch) {
+ final int start = i;
+ while (++i < size) {
+ if (buffer[i] != ch) {
+ break;
+ }
+ }
+ final int len = i - start;
+ deleteImpl(start, i, len);
+ i -= len;
+ }
+ }
+ return this;
}
+ //-----------------------------------------------------------------------
/**
- * Appends an object to the builder padding on the right to a fixed length.
- * The toString of the object is used.
- * If the object is larger than the length, the right hand side is lost.
- * If the object is null, null text value is used.
+ * Deletes the string wherever it occurs in the builder.
*
- * @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
+ * @param str the string to delete, null causes no action
* @return this, to enable chaining
*/
- public StrBuilder appendFixedWidthPadRight(final Object obj, final int width, final char padChar) {
- if (width > 0) {
- ensureCapacity(size + width);
- String str = (obj == null ? getNullText() : obj.toString());
- if (str == null) {
- str = "";
- }
- final int strLen = str.length();
- if (strLen >= width) {
- str.getChars(0, width, buffer, size);
- } else {
- final int padLen = width - strLen;
- str.getChars(0, strLen, buffer, size);
- for (int i = 0; i < padLen; i++) {
- buffer[size + strLen + i] = padChar;
- }
+ public StrBuilder deleteAll(final String str) {
+ final int len = str == null ? 0 : str.length();
+ if (len > 0) {
+ int index = indexOf(str, 0);
+ while (index >= 0) {
+ deleteImpl(index, index + len, len);
+ index = indexOf(str, index);
}
- size += width;
}
return this;
}
+ //-----------------------------------------------------------------------
/**
- * Appends an object to the builder padding on the right to a fixed length.
- * The String.valueOf of the int value is used.
- * If the object is larger than the length, the right hand side is lost.
+ * Deletes all parts of the builder that the matcher matches.
+ * length characters from
- * the builder. If this many characters are not available, the whole
- * builder is returned. Thus the returned string may be shorter than the
- * length requested.
+ * Note that a null input string will return -1, whereas the JDK throws an exception.
*
- * @param length the number of characters to extract, negative returns empty string
- * @return the new string
+ * @param str the string to find, null returns -1
+ * @param startIndex the index to start at, invalid index rounded to edge
+ * @return The last index of the string, or -1 if not found
*/
- public 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);
+ public int lastIndexOf(final String str, int startIndex) {
+ startIndex = startIndex >= size ? size - 1 : startIndex;
+ if (str == null || startIndex < 0) {
+ return -1;
+ }
+ final int strLen = str.length();
+ if (strLen > 0 && strLen <= size) {
+ if (strLen == 1) {
+ return lastIndexOf(str.charAt(0), startIndex);
+ }
+
+ outer:
+ for (int i = startIndex - strLen + 1; i >= 0; i--) {
+ for (int j = 0; j < strLen; j++) {
+ if (str.charAt(j) != buffer[i + j]) {
+ continue outer;
+ }
+ }
+ return i;
+ }
+
+ } else if (strLen == 0) {
+ return startIndex;
}
+ return -1;
}
/**
- * Extracts the rightmost characters from the string builder without
+ * Searches the string builder using the matcher to find the last match.
+ * length characters from
+ * This method extracts the left {@code length} characters from
* the builder. If this many characters are not available, the whole
* builder is returned. Thus the returned string may be shorter than the
* length requested.
*
* @param length the number of characters to extract, negative returns empty string
- * @return the new string
+ * @return The new string
*/
- public String rightString(final int length) {
+ public String leftString(final int length) {
if (length <= 0) {
- return "";
+ return StringUtils.EMPTY;
} else if (length >= size) {
return new String(buffer, 0, size);
} else {
- return new String(buffer, size - length, length);
+ return new String(buffer, 0, length);
}
}
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the length of the string builder.
+ *
+ * @return The length
+ */
+ @Override
+ public int length() {
+ return size;
+ }
+
/**
* Extracts some characters from the middle of the string builder without
* throwing an exception.
* length characters from the builder
+ * This method extracts {@code length} characters from the builder
* at the specified index.
* If the index is negative it is treated as zero.
* If the index is greater than the builder size, it is treated as the builder size.
@@ -2303,14 +2448,14 @@ public String rightString(final int length) {
*
* @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
+ * @return The new string
*/
public String midString(int index, final int length) {
if (index < 0) {
index = 0;
}
if (length <= 0 || index >= size) {
- return "";
+ return StringUtils.EMPTY;
}
if (size <= index + length) {
return new String(buffer, index, size - index);
@@ -2318,483 +2463,512 @@ public String midString(int index, final int length) {
return new String(buffer, index, length);
}
+ /**
+ * Minimizes the capacity to the actual length of the string.
+ *
+ * @return this, to enable chaining
+ */
+ public StrBuilder minimizeCapacity() {
+ if (buffer.length > length()) {
+ final char[] old = buffer;
+ buffer = new char[length()];
+ System.arraycopy(old, 0, buffer, 0, size);
+ }
+ return this;
+ }
+
//-----------------------------------------------------------------------
/**
- * Checks if the string builder contains the specified char.
+ * If possible, reads chars from the provided {@link Readable} directly into underlying
+ * character buffer without making extra copies.
*
- * @param ch the character to find
- * @return true if the builder contains the character
+ * @param readable object to read from
+ * @return The number of characters read
+ * @throws IOException if an I/O error occurs
+ *
+ * @see #appendTo(Appendable)
*/
- public boolean contains(final char ch) {
- final char[] thisBuf = buffer;
- for (int i = 0; i < this.size; i++) {
- if (thisBuf[i] == ch) {
- return true;
+ 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 false;
+ return size - oldSize;
}
/**
- * Checks if the string builder contains the specified string.
+ * 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 str the string to find
- * @return true if the builder contains the 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
+ * @param replaceStr the string to replace with, null means delete range
+ * @return this, to enable chaining
+ * @throws IndexOutOfBoundsException if the index is invalid
*/
- public boolean contains(final String str) {
- return indexOf(str, 0) >= 0;
+ public StrBuilder replace(final int startIndex, int endIndex, final String replaceStr) {
+ endIndex = validateRange(startIndex, endIndex);
+ final int insertLen = replaceStr == null ? 0 : replaceStr.length();
+ replaceImpl(startIndex, endIndex, endIndex - startIndex, replaceStr, insertLen);
+ return this;
}
+ // -----------------------------------------------------------------------
/**
- * Checks if the string builder contains a string matched using the
- * specified matcher.
+ * Advanced search and replaces within the builder using a matcher.
*
- * StrBuilder b = new StrBuilder();
- * b.append("a b ");
- * StrTokenizer t = b.asTokenizer();
- * String[] tokens1 = t.getTokenArray(); // returns a,b
- * b.append("c d ");
- * String[] tokens2 = t.getTokenArray(); // returns a,b (c and d ignored)
- * t.reset(); // reset causes builder changes to be picked up
- * String[] tokens3 = t.getTokenArray(); // returns a,b,c,d
- *
- * In addition to simply intermixing appends and tokenization, you can also
- * call the set methods on the tokenizer to alter how it tokenizes. Just
- * remember to call reset when you want to pickup builder changes.
- * StrBuilder, populate it with
- * data, call asReader, and then read away.
- * StrBuilder,
- * call asWriter, and populate away. The data is available
- * at any time using the methods of the StrBuilder.
- * StrBuilder to the
- * provided {@link Appendable}.
+ * Checks whether this builder starts with the specified string.
*
- * Map<String, Object> map = new HashMap<String, Object>();
- * map.put("number", Integer.valueOf(2));
- * assertEquals("2", StrLookup.mapLookup(map).lookup("number"));
- *
- *
- * @param key the key to be looked up, may be null
- * @return the matching value, null if no match
- */
- public abstract String lookup(String key);
-
// -----------------------------------------------------------------------
/**
* Lookup implementation that uses a Map.
@@ -154,7 +135,7 @@ static class MapStrLookuppos represents the current position to be
- * checked in the string buffer (a character array which must
+ * The parameter {@code pos} represents the current position to be
+ * checked in the string {@code buffer} (a character array which must
* not be changed).
- * The API guarantees that pos is a valid index for buffer.
+ * The API guarantees that {@code pos} is a valid index for {@code buffer}.
* pos as well as those
+ * It may check characters preceding {@code pos} as well as those
* after, so long as no checks exceed the bounds specified.
* pos represents the current position to be
- * checked in the string buffer (a character array which must
+ * The parameter {@code pos} represents the current position to be
+ * checked in the string {@code buffer} (a character array which must
* not be changed).
- * The API guarantees that pos is a valid index for buffer.
+ * The API guarantees that {@code pos} is a valid index for {@code buffer}.
* pos as well as those after.
+ * It may check characters preceding {@code pos} as well as those after.
* NoMatcher.
+ * Constructs a new instance of {@code NoMatcher}.
*/
NoMatcher() {
- super();
}
/**
- * Always returns false.
+ * Always returns {@code 0}.
*
* @param buffer the text content to match against, do not change
* @param pos the starting position for the match, valid for buffer
* @param bufferStart the first active index in the buffer, valid for buffer
* @param bufferEnd the end index of the active buffer, valid for buffer
- * @return the number of matching characters, zero for no match
+ * @return The number of matching characters, or zero if there is no match
*/
@Override
public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) {
@@ -414,10 +422,9 @@ public int isMatch(final char[] buffer, final int pos, final int bufferStart, fi
static final class TrimMatcher extends StrMatcher {
/**
- * Constructs a new instance of TrimMatcher.
+ * Constructs a new instance of {@code TrimMatcher}.
*/
TrimMatcher() {
- super();
}
/**
@@ -427,7 +434,7 @@ static final class TrimMatcher extends StrMatcher {
* @param pos the starting position for the match, valid for buffer
* @param bufferStart the first active index in the buffer, valid for buffer
* @param bufferEnd the end index of the active buffer, valid for buffer
- * @return the number of matching characters, zero for no match
+ * @return The number of matching characters, or zero if there is no match
*/
@Override
public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) {
diff --git a/src/main/java/org/apache/commons/text/StrSubstitutor.java b/src/main/java/org/apache/commons/text/StrSubstitutor.java
index 7bff411d00..42fee5e587 100644
--- a/src/main/java/org/apache/commons/text/StrSubstitutor.java
+++ b/src/main/java/org/apache/commons/text/StrSubstitutor.java
@@ -29,7 +29,7 @@
* Substitutes variables within a string by values.
* ${variableName}.
+ * The default definition of a variable is {@code ${variableName}}.
* The prefix and suffix can be changed via constructors and set methods.
* replace()
+ * the appropriate settings can be performed. After that the {@code replace()}
* method can be called passing in the source text for interpolation. In the returned
* text all variable references (as long as their values are known) will be resolved.
* The following example demonstrates this:
*
- * Map valuesMap = HashMap();
+ * Map<String, String> valuesMap = new HashMap<>();
* valuesMap.put("animal", "quick brown fox");
* valuesMap.put("target", "lazy dog");
* String templateString = "The ${animal} jumped over the ${target}.";
@@ -69,7 +69,7 @@
* {@link #setValueDelimiter(char)} or {@link #setValueDelimiter(String)}.
* The following shows an example with variable default value settings:
*
- *
- * Map valuesMap = HashMap();
+ * Map<String, String> valuesMap = new HashMap<>();
* valuesMap.put("animal", "quick brown fox");
* valuesMap.put("target", "lazy dog");
* String templateString = "The ${animal} jumped over the ${target}. ${undefined.number:-1234567890}.";
@@ -96,7 +96,7 @@
* The variable ${${name}} must be used.
*
* Here only the variable's name referred to in the text should be replaced resulting
- * in the text (assuming that the value of the name variable is x):
+ * in the text (assuming that the value of the {@code name} variable is {@code x}):
*
* The variable ${x} must be used.
*
@@ -114,28 +114,33 @@
*
* ${jre-${java.specification.version}}
*
- * StrSubstitutor supports this recursive substitution in variable
+ * {@code StrSubstitutor} supports this recursive substitution in variable
* names, but it has to be enabled explicitly by setting the
* {@link #setEnableSubstitutionInVariables(boolean) enableSubstitutionInVariables}
* property to true.
* toString and is not altered.
+ * converted to a string using {@code toString} and is not altered.
*
* @param source the source to replace in, null returns null
- * @return the result of the replace operation
+ * @return The result of the replace operation
*/
public String replace(final Object source) {
if (source == null) {
@@ -747,7 +759,7 @@ protected boolean substitute(final StrBuilder buf, final int offset, final int l
* @param offset the start offset within the builder, must be valid
* @param length the length within the builder to be processed, must be valid
* @param priorVariables the stack keeping track of the replaced variables, may be null
- * @return the length change that occurs, unless priorVariables is null when the int
+ * @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, ListvalueDelimiterMatcher is null, then the variable default value resolution
+ * If the {@code valueDelimiterMatcher} is null, then the variable default value resolution
* becomes disabled.
*
* @param valueDelimiterMatcher variable default value delimiter matcher to use, may be null
@@ -1124,7 +1136,7 @@ public StrSubstitutor setValueDelimiterMatcher(final StrMatcher valueDelimiterMa
/**
* Sets the variable default value delimiter to use.
* valueDelimiter is null or empty string, then the variable default
+ * If the {@code valueDelimiter} is null or empty string, then the variable default
* value resolution becomes disabled.
*
* @param valueDelimiter the variable default value delimiter string to use, may be null or empty
* @return this, to enable chaining
*/
public StrSubstitutor setValueDelimiter(final String valueDelimiter) {
- if (valueDelimiter == null || valueDelimiter.length() == 0) {
+ if (valueDelimiter == null || valueDelimiter.isEmpty()) {
setValueDelimiterMatcher(null);
return this;
}
@@ -1161,7 +1173,7 @@ public StrSubstitutor setValueDelimiter(final String valueDelimiter) {
/**
* Gets the VariableResolver that is used to lookup variables.
*
- * @return the VariableResolver
+ * @return The VariableResolver
*/
public StrLookup> getVariableResolver() {
return this.variableResolver;
@@ -1181,7 +1193,7 @@ public void setVariableResolver(final StrLookup> variableResolver) {
/**
* Returns a flag whether substitution is done in variable names.
*
- * @return the substitution in variable names flag
+ * @return The substitution in variable names flag
*/
public boolean isEnableSubstitutionInVariables() {
return enableSubstitutionInVariables;
@@ -1191,7 +1203,7 @@ public boolean isEnableSubstitutionInVariables() {
* Sets a flag whether substitution is done in variable names. If set to
* true, the names of variables can contain other variables which are
* processed first before the original variable is evaluated, e.g.
- * ${jre-${java.version}}. The default value is false.
+ * {@code ${jre-${java.version}}}. The default value is false.
*
* @param enableSubstitutionInVariables the new value of the flag
*/
@@ -1205,7 +1217,7 @@ public void setEnableSubstitutionInVariables(
* true, the values of variables can contain other variables will not be
* processed and substituted original variable is evaluated, e.g.
*
- * Map valuesMap = HashMap();
+ * Map<String, String> valuesMap = new HashMap<>();
* valuesMap.put("name", "Douglas ${surname}");
* valuesMap.put("surname", "Crockford");
* String templateString = "Hi ${name}";
@@ -1217,7 +1229,7 @@ public void setEnableSubstitutionInVariables(
* Hi Douglas ${surname}
*
*
- * @return the substitution in variable values flag
+ * @return The substitution in variable values flag
*
* @since 1.2
*/
@@ -1232,7 +1244,7 @@ public boolean isDisableSubstitutionInValues() {
*
* @since 1.2
*/
- public void setDisableSubstitutionInValues(boolean disableSubstitutionInValues) {
+ public void setDisableSubstitutionInValues(final boolean disableSubstitutionInValues) {
this.disableSubstitutionInValues = disableSubstitutionInValues;
}
@@ -1240,7 +1252,7 @@ public void setDisableSubstitutionInValues(boolean disableSubstitutionInValues)
* Returns the flag controlling whether escapes are preserved during
* substitution.
*
- * @return the preserve escape flag
+ * @return The preserve escape flag
*/
public boolean isPreserveEscapes() {
return preserveEscapes;
@@ -1249,11 +1261,11 @@ public boolean isPreserveEscapes() {
/**
* Sets a flag controlling whether escapes are preserved during
* substitution. If set to true, the escape character is retained
- * during substitution (e.g. $${this-is-escaped} remains
- * $${this-is-escaped}). If set to false, the escape
+ * during substitution (e.g. {@code $${this-is-escaped}} remains
+ * {@code $${this-is-escaped}}). If set to false, the escape
* character is removed during substitution (e.g.
- * $${this-is-escaped} becomes
- * ${this-is-escaped}). The default value is false
+ * {@code $${this-is-escaped}} becomes
+ * {@code ${this-is-escaped}}). The default value is false
*
* @param preserveEscapes true if escapes are to be preserved
*/
diff --git a/src/main/java/org/apache/commons/text/StrTokenizer.java b/src/main/java/org/apache/commons/text/StrTokenizer.java
index d4a2c6c7d3..f1e1b7474a 100644
--- a/src/main/java/org/apache/commons/text/StrTokenizer.java
+++ b/src/main/java/org/apache/commons/text/StrTokenizer.java
@@ -22,6 +22,9 @@
import java.util.ListIterator;
import java.util.NoSuchElementException;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+
/**
* Tokenizes a string based on delimiters (separators)
* and supporting quoting and ignored character concepts.
@@ -29,8 +32,8 @@
* This class can split a String into many smaller strings. It aims
* to do a similar job to {@link java.util.StringTokenizer StringTokenizer},
* however it offers much more control and flexibility including implementing
- * the ListIterator interface. By default, it is set up
- * like StringTokenizer.
+ * the {@code ListIterator} interface. By default, it is set up
+ * like {@code StringTokenizer}.
*
+ *
+ *
*
* @since 1.0
+ * @deprecated Deprecated as of 1.3, use {@link StringTokenizer} instead. This class will be removed in 2.0.
*/
+@Deprecated
public class StrTokenizer implements ListIterator
*
@@ -80,7 +81,9 @@
* Property Type Default
* CSV_TOKENIZER_PROTOTYPE.
+ * Returns a clone of {@code CSV_TOKENIZER_PROTOTYPE}.
*
- * @return a clone of CSV_TOKENIZER_PROTOTYPE.
+ * @return a clone of {@code CSV_TOKENIZER_PROTOTYPE}.
*/
private static StrTokenizer getCSVClone() {
return (StrTokenizer) CSV_TOKENIZER_PROTOTYPE.clone();
}
-
/**
* Gets a new tokenizer instance which parses Comma Separated Value strings
* initializing it with the given input. The default for CSV processing
@@ -149,7 +128,6 @@ private static StrTokenizer getCSVClone() {
public static StrTokenizer getCSVInstance() {
return getCSVClone();
}
-
/**
* Gets a new tokenizer instance which parses Comma Separated Value strings
* initializing it with the given input. The default for CSV processing
@@ -159,7 +137,7 @@ public static StrTokenizer getCSVInstance() {
* @param input the text to parse
* @return a new tokenizer instance which parses Comma Separated Value strings
*/
- public static StrTokenizer getCSVInstance(final String input) {
+ public static StrTokenizer getCSVInstance(final char[] input) {
final StrTokenizer tok = getCSVClone();
tok.reset(input);
return tok;
@@ -174,22 +152,19 @@ public static StrTokenizer getCSVInstance(final String input) {
* @param input the text to parse
* @return a new tokenizer instance which parses Comma Separated Value strings
*/
- public static StrTokenizer getCSVInstance(final char[] input) {
+ public static StrTokenizer getCSVInstance(final String input) {
final StrTokenizer tok = getCSVClone();
tok.reset(input);
return tok;
}
-
/**
- * Returns a clone of TSV_TOKENIZER_PROTOTYPE.
+ * Returns a clone of {@code TSV_TOKENIZER_PROTOTYPE}.
*
- * @return a clone of TSV_TOKENIZER_PROTOTYPE.
+ * @return a clone of {@code TSV_TOKENIZER_PROTOTYPE}.
*/
private static StrTokenizer getTSVClone() {
return (StrTokenizer) TSV_TOKENIZER_PROTOTYPE.clone();
}
-
-
/**
* Gets a new tokenizer instance which parses Tab Separated Value strings.
* The default for CSV processing will be trim whitespace from both ends
@@ -201,7 +176,6 @@ private static StrTokenizer getTSVClone() {
public static StrTokenizer getTSVInstance() {
return getTSVClone();
}
-
/**
* Gets a new tokenizer instance which parses Tab Separated Value strings.
* The default for CSV processing will be trim whitespace from both ends
@@ -209,7 +183,7 @@ public static StrTokenizer getTSVInstance() {
* @param input the string to parse
* @return a new tokenizer instance which parses Tab Separated Value strings.
*/
- public static StrTokenizer getTSVInstance(final String input) {
+ public static StrTokenizer getTSVInstance(final char[] input) {
final StrTokenizer tok = getTSVClone();
tok.reset(input);
return tok;
@@ -222,318 +196,381 @@ public static StrTokenizer getTSVInstance(final String input) {
* @param input the string to parse
* @return a new tokenizer instance which parses Tab Separated Value strings.
*/
- public static StrTokenizer getTSVInstance(final char[] input) {
+ public static StrTokenizer getTSVInstance(final String input) {
final StrTokenizer tok = getTSVClone();
tok.reset(input);
return tok;
}
+ /** The text to work on. */
+ private char[] chars;
+
+ //-----------------------------------------------------------------------
+
+ /** The parsed tokens. */
+ private String[] tokens;
+
+ /** The current iteration position. */
+ private int tokenPos;
+
+ /** The delimiter matcher. */
+ private StrMatcher delimMatcher = StrMatcher.splitMatcher();
+
+ /** The quote matcher. */
+ private StrMatcher quoteMatcher = StrMatcher.noneMatcher();
+
+ /** The ignored matcher. */
+ private StrMatcher ignoredMatcher = StrMatcher.noneMatcher();
+
+
+ /** The trimmer matcher. */
+ private StrMatcher trimmerMatcher = StrMatcher.noneMatcher();
+
+ /** Whether to return empty tokens as null. */
+ private boolean emptyAsNull;
+
+ /** Whether to ignore empty tokens. */
+ private boolean ignoreEmptyTokens = true;
//-----------------------------------------------------------------------
/**
- * Constructs a tokenizer splitting on space, tab, newline and formfeed
+ * Constructs a tokenizer splitting on space, tab, newline and form feed
* as per StringTokenizer, but with no text to tokenize.
* StrTokenizer will always pass a zero offset and a count
- * equal to the length of the array to this method, however a subclass
- * may pass other values, or even an entirely different array.
+ * Gets the token previous to the last returned token.
*
- * @param srcChars the character array being tokenized, may be null
- * @param offset the start position within the character array, must be valid
- * @param count the number of characters to tokenize, must be valid
- * @return the modifiable list of String tokens, unmodifiable if null array or zero count
+ * @return The previous token
*/
- protected Listnull.
- *
- * @return a new instance of this Tokenizer which has been reset.
- */
- @Override
- public Object clone() {
- try {
- return cloneReset();
- } catch (final CloneNotSupportedException ex) {
- return null;
- }
- }
+ // loop around the entire buffer
+ while (pos >= 0 && pos < count) {
+ // find next token
+ pos = readNextToken(srcChars, pos, count, buf, tokenList);
- /**
- * Creates a new instance of this Tokenizer. The new instance is reset so that
- * it will be at the start of the token list.
- *
- * @return a new instance of this Tokenizer which has been reset.
- * @throws CloneNotSupportedException if there is a problem cloning
- */
- Object cloneReset() throws CloneNotSupportedException {
- // this method exists to enable 100% test coverage
- final StrTokenizer cloned = (StrTokenizer) super.clone();
- if (cloned.chars != null) {
- cloned.chars = cloned.chars.clone();
+ // handle case where end of string is a delimiter
+ if (pos >= count) {
+ addToken(tokenList, StringUtils.EMPTY);
+ }
}
- cloned.reset();
- return cloned;
+ return tokenList;
}
//-----------------------------------------------------------------------
/**
* Gets the String content that the tokenizer is parsing.
*
- * @return the string content being parsed
+ * @return The string content being parsed
*/
@Override
public String toString() {
diff --git a/src/main/java/org/apache/commons/text/StringEscapeUtils.java b/src/main/java/org/apache/commons/text/StringEscapeUtils.java
index cd15a8ac99..7daaace325 100644
--- a/src/main/java/org/apache/commons/text/StringEscapeUtils.java
+++ b/src/main/java/org/apache/commons/text/StringEscapeUtils.java
@@ -16,6 +16,12 @@
*/
package org.apache.commons.text;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.translate.AggregateTranslator;
import org.apache.commons.text.translate.CharSequenceTranslator;
@@ -29,18 +35,14 @@
import org.apache.commons.text.translate.UnicodeUnescaper;
import org.apache.commons.text.translate.UnicodeUnpairedSurrogateRemover;
-import java.io.IOException;
-import java.io.Writer;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
/**
- *
@@ -577,7 +571,7 @@ public static final String escapeEcmaScript(final String input) {
*
@@ -643,15 +637,15 @@ public static final String unescapeJson(final String input) {
* "bread" & "butter""bread" & "butter".
+ * {@code "bread" & "butter"}.
* Using System Properties
+ *
+ * StringSubstitutor
+ * .replaceSystemProperties("You are running with java.version = ${java.version} and os.name = ${os.name}.");
+ *
+ *
+ * Using a Custom Map
+ *
+ *
+ *
+ * // Build map
+ * Map<String, String> valuesMap = new HashMap<>();
+ * valuesMap.put("animal", "quick brown fox");
+ * valuesMap.put("target", "lazy dog");
+ * String templateString = "The ${animal} jumped over the ${target}.";
+ *
+ * // Build StringSubstitutor
+ * StringSubstitutor sub = new StringSubstitutor(valuesMap);
+ *
+ * // Replace
+ * String resolvedString = sub.replace(templateString);
+ *
+ *
+ *
+ * "The quick brown fox jumped over the lazy dog."
+ *
+ *
+ * Providing Default Values
+ *
+ * // Build map
+ * Map<String, String> valuesMap = new HashMap<>();
+ * valuesMap.put("animal", "quick brown fox");
+ * valuesMap.put("target", "lazy dog");
+ * String templateString = "The ${animal} jumped over the ${target} ${undefined.number:-1234567890} times.";
+ *
+ * // Build StringSubstitutor
+ * StringSubstitutor sub = new StringSubstitutor(valuesMap);
+ *
+ * // Replace
+ * String resolvedString = sub.replace(templateString);
+ *
+ *
+ *
+ * "The quick brown fox jumped over the lazy dog 1234567890 times."
+ *
+ *
+ * Reusing Instances
+ * Using Interpolation
+ *
+ * final StringSubstitutor interpolator = StringSubstitutor.createInterpolator();
+ * interpolator.setEnableSubstitutionInVariables(true); // Allows for nested $'s.
+ * final String text = interpolator.replace("Base64 Decoder: ${base64Decoder:SGVsbG9Xb3JsZCE=}\n"
+ * + "Base64 Encoder: ${base64Encoder:HelloWorld!}\n"
+ * + "Java Constant: ${const:java.awt.event.KeyEvent.VK_ESCAPE}\n"
+ * + "Date: ${date:yyyy-MM-dd}\n" + "DNS: ${dns:address|apache.org}\n"
+ * + "Environment Variable: ${env:USERNAME}\n"
+ * + "File Content: ${file:UTF-8:src/test/resources/document.properties}\n"
+ * + "Java: ${java:version}\n" + "Localhost: ${localhost:canonical-name}\n"
+ * + "Properties File: ${properties:src/test/resources/document.properties::mykey}\n"
+ * + "Resource Bundle: ${resourceBundle:org.example.testResourceBundleLookup:mykey}\n"
+ * + "Script: ${script:javascript:3 + 4}\n" + "System Property: ${sys:user.dir}\n"
+ * + "URL Decoder: ${urlDecoder:Hello%20World%21}\n"
+ * + "URL Encoder: ${urlEncoder:Hello World!}\n"
+ * + "URL Content (HTTP): ${url:UTF-8:http://www.apache.org}\n"
+ * + "URL Content (HTTPS): ${url:UTF-8:https://www.apache.org}\n"
+ * + "URL Content (File): ${url:UTF-8:file:///${sys:user.dir}/src/test/resources/document.properties}\n"
+ * + "XML XPath: ${xml:src/test/resources/document.xml:/root/path/to/node}\n");
+ *
+ * Using Recursive Variable Replacement
+ *
+ * "The variable ${${name}} must be used."
+ *
+ *
+ *
+ * "The variable ${x} must be used."
+ *
+ *
+ *
+ * "The variable $${${name}} must be used."
+ *
+ *
+ * ${jre-${java.specification.version}}
+ *
+ *
+ * Thread Safety
+ *
+ * StringSubstitutor.createInterpolator().replace(
+ * "OS name: ${sys:os.name}, " + "3 + 4 = ${script:javascript:3 + 4}");
+ *
+ *
+ * @return a new instance using the interpolator string lookup.
+ * @see StringLookupFactory#interpolatorStringLookup()
+ * @since 1.8
+ */
+ public static StringSubstitutor createInterpolator() {
+ return new StringSubstitutor(StringLookupFactory.INSTANCE.interpolatorStringLookup());
+ }
+
+ /**
+ * Replaces all the occurrences of variables in the given source object with their matching values from the map.
+ *
+ * @param
+ * Map<String, String> valuesMap = new HashMap<>();
+ * valuesMap.put("name", "Douglas ${surname}");
+ * valuesMap.put("surname", "Crockford");
+ * String templateString = "Hi ${name}";
+ * StrSubstitutor sub = new StrSubstitutor(valuesMap);
+ * String resolvedString = sub.replace(templateString);
+ *
+ *
+ * yielding:
+ *
+ *
+ * Hi Douglas ${surname}
+ *
+ *
+ * @return The substitution in variable values flag
+ */
+ public boolean isDisableSubstitutionInValues() {
+ return disableSubstitutionInValues;
+ }
+
+ /**
+ * Returns a flag whether substitution is done in variable names.
+ *
+ * @return The substitution in variable names flag
+ */
+ public boolean isEnableSubstitutionInVariables() {
+ return enableSubstitutionInVariables;
+ }
+
+ /**
+ * Returns a flag whether exception can be thrown upon undefined variable.
+ *
+ * @return The fail on undefined variable flag
+ */
+ public boolean isEnableUndefinedVariableException() {
+ return enableUndefinedVariableException;
+ }
+
+ /**
+ * Returns the flag controlling whether escapes are preserved during substitution.
+ *
+ * @return The preserve escape flag
+ */
+ public boolean isPreserveEscapes() {
+ return preserveEscapes;
+ }
+
+ /**
+ * Replaces all the occurrences of variables with their matching values from the resolver using the given source
+ * array as a template. The array is not altered by this method.
+ *
+ * @param source the character array to replace in, not altered, null returns null
+ * @return The result of the replace operation
+ * @throws IllegalArgumentException if variable is not found when its allowed to throw exception
+ */
+ public String replace(final char[] source) {
+ if (source == null) {
+ return null;
+ }
+ final TextStringBuilder buf = new TextStringBuilder(source.length).append(source);
+ substitute(buf, 0, source.length);
+ return buf.toString();
+ }
+
+ /**
+ * Replaces all the occurrences of variables with their matching values from the resolver using the given source
+ * array as a template. The array is not altered by this method.
+ *
+ * "a,b,c" - Three tokens "a","b","c" (comma delimiter)
+ * " a, b , c " - Three tokens "a","b","c" (default CSV processing trims whitespace)
+ * "a, ", b ,", c" - Three tokens "a, " , " b ", ", c" (quoted text untouched)
+ *
+ *
+ *
+ *
+ *
+ * @since 1.3
+ */
+public class StringTokenizer implements ListIterator
+ *
+ * Property
+ * Type
+ * Default
+ *
+ *
+ * delim
+ * CharSetMatcher
+ * { \t\n\r\f}
+ *
+ *
+ * quote
+ * NoneMatcher
+ * {}
+ *
+ *
+ * ignore
+ * NoneMatcher
+ * {}
+ *
+ *
+ * emptyTokenAsNull
+ * boolean
+ * false
+ *
+ *
+ * ignoreEmptyTokens
+ * boolean
+ * true
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * for (Iterator it = list.iterator(); it.hasNext();) {
+ * appendSeparator(',');
+ * append(it.next());
+ * }
+ *
+ *
+ *
+ * for (int i = 0; i < list.size(); i++) {
+ * appendSeparator(",", i);
+ * append(list.get(i));
+ * }
+ *
+ *
+ *
+ * for (Iterator it = list.iterator(); it.hasNext();) {
+ * appendSeparator(",");
+ * append(it.next());
+ * }
+ *
+ *
+ *
+ * for (int i = 0; i < list.size(); i++) {
+ * appendSeparator(",", i);
+ * append(list.get(i));
+ * }
+ *
+ *
+ *
+ * StrBuilder whereClause = new StrBuilder();
+ * if(searchCommand.getPriority() != null) {
+ * whereClause.appendSeparator(" and", " where");
+ * whereClause.append(" priority = ?")
+ * }
+ * if(searchCommand.getComponent() != null) {
+ * whereClause.appendSeparator(" and", " where");
+ * whereClause.append(" component = ?")
+ * }
+ * selectClause.append(whereClause)
+ *
+ *
+ * @param standard the separator if builder is not empty, null means no separator
+ * @param defaultIfEmpty the separator if builder is empty, null means no separator
+ * @return this, to enable chaining
+ */
+ public TextStringBuilder appendSeparator(final String standard, final String defaultIfEmpty) {
+ final String str = isEmpty() ? defaultIfEmpty : standard;
+ if (str != null) {
+ append(str);
+ }
+ return this;
+ }
+
+ /**
+ * Appends current contents of this {@code StrBuilder} to the provided {@link Appendable}.
+ *
+ * StrBuilder b = new StrBuilder();
+ * b.append("a b ");
+ * StrTokenizer t = b.asTokenizer();
+ * String[] tokens1 = t.getTokenArray(); // returns a,b
+ * b.append("c d ");
+ * String[] tokens2 = t.getTokenArray(); // returns a,b (c and d ignored)
+ * t.reset(); // reset causes builder changes to be picked up
+ * String[] tokens3 = t.getTokenArray(); // returns a,b,c,d
+ *
+ *
+ * null input gracefully. An exception will not be thrown for a
- * null input. Each method documents its behavior in more detail.
+ * This class tries to handle {@code null} input gracefully. An exception will not be thrown for a
+ * {@code null} input. Each method documents its behavior in more detail.
* WordUtils instances should NOT be constructed in
+ * WordUtils.wrap("foo bar", 20);.' '.
+ *
+ *
*
@@ -99,19 +99,20 @@ public WordUtils() {
*
* @param str the String to be word wrapped, may be null
* @param wrapLength the column to wrap the words at, less than 1 is treated as 1
- * @return a line with newlines inserted,
*
* input
* wrapLength
@@ -84,14 +84,14 @@ public WordUtils() {
* "Here is one line of\ntext that is going\nto be wrapped after\n20 columns."
*
- *
* "Click here to jump to the commons website - http://commons.apache.org"
+ * "Click here to jump to the commons website - https://commons.apache.org"
* 20
- * "Click here to jump\nto the commons\nwebsite -\nhttp://commons.apache.org"
+ * "Click here to jump\nto the commons\nwebsite -\nhttps://commons.apache.org"
*
- *
* "Click here, http://commons.apache.org, to jump to the commons website"
+ * "Click here, https://commons.apache.org, to jump to the commons website"
* 20
- * "Click here,\nhttp://commons.apache.org,\nto jump to the\ncommons website"
+ * "Click here,\nhttps://commons.apache.org,\nto jump to the\ncommons website"
* null if null input
+ * @return a line with newlines inserted, {@code null} if null input
*/
public static String wrap(final String str, final int wrapLength) {
return wrap(str, wrapLength, null, false);
}
/**
- * ' '.
+ *
+ *
*
* input
* wrapLength
@@ -157,14 +158,14 @@ public static String wrap(final String str, final int wrapLength) {
* + systemNewLine + "to be wrapped after" + systemNewLine + "20 columns."
*
- *
* "Click here to jump to the commons website - http://commons.apache.org"
+ * "Click here to jump to the commons website - https://commons.apache.org"
* 20
* "\n"
* false
- * "Click here to jump\nto the commons\nwebsite -\nhttp://commons.apache.org"
+ * "Click here to jump\nto the commons\nwebsite -\nhttps://commons.apache.org"
*
- * "Click here to jump to the commons website - http://commons.apache.org"
+ * "Click here to jump to the commons website - https://commons.apache.org"
* 20
* "\n"
* true
@@ -175,9 +176,9 @@ public static String wrap(final String str, final int wrapLength) {
* @param str the String to be word wrapped, may be null
* @param wrapLength the column to wrap the words at, less than 1 is treated as 1
* @param newLineStr the string to insert for a new line,
- * null uses the system property line separator
+ * {@code null} uses the system property line separator
* @param wrapLongWords true if long words (such as URLs) should be wrapped
- * @return a line with newlines inserted, null if null input
+ * @return a line with newlines inserted, {@code null} if null input
*/
public static String wrap(final String str,
final int wrapLength,
@@ -187,12 +188,13 @@ public static String wrap(final String str,
}
/**
- * wrapOn.
+ *
+ *
*
* input
* wrapLength
@@ -244,15 +246,15 @@ public static String wrap(final String str,
* + systemNewLine + "to be wrapped after" + systemNewLine + "20 columns."
*
- *
* "Click here to jump to the commons website - http://commons.apache.org"
+ * "Click here to jump to the commons website - https://commons.apache.org"
* 20
* "\n"
* false
* " "
- * "Click here to jump\nto the commons\nwebsite -\nhttp://commons.apache.org"
+ * "Click here to jump\nto the commons\nwebsite -\nhttps://commons.apache.org"
*
- * "Click here to jump to the commons website - http://commons.apache.org"
+ * "Click here to jump to the commons website - https://commons.apache.org"
* 20
* "\n"
* true
@@ -271,11 +273,11 @@ public static String wrap(final String str,
* @param str the String to be word wrapped, may be null
* @param wrapLength the column to wrap the words at, less than 1 is treated as 1
* @param newLineStr the string to insert for a new line,
- * null uses the system property line separator
+ * {@code null} uses the system property line separator
* @param wrapLongWords true if long words (such as URLs) should be wrapped
* @param wrapOn regex expression to be used as a breakable characters,
* if blank string is provided a space character will be used
- * @return a line with newlines inserted, null if null input
+ * @return a line with newlines inserted, {@code null} if null input
*/
public static String wrap(final String str,
int wrapLength,
@@ -298,18 +300,22 @@ public static String wrap(final String str,
final int inputLineLength = str.length();
int offset = 0;
final StringBuilder wrappedLine = new StringBuilder(inputLineLength + 32);
+ int matcherSize = -1;
while (offset < inputLineLength) {
int spaceToWrapAt = -1;
- Matcher matcher = patternToWrapOn.matcher(str.substring(offset, Math
- .min(offset + wrapLength + 1, inputLineLength)));
+ Matcher matcher = patternToWrapOn.matcher(str.substring(offset,
+ Math.min((int) Math.min(Integer.MAX_VALUE, offset + wrapLength + 1L), inputLineLength)));
if (matcher.find()) {
if (matcher.start() == 0) {
- offset += matcher.end();
- continue;
- } else {
- spaceToWrapAt = matcher.start() + offset;
+ matcherSize = matcher.end() - matcher.start();
+ if (matcherSize != 0) {
+ offset += matcher.end();
+ continue;
+ }
+ offset += 1;
}
+ spaceToWrapAt = matcher.start() + offset;
}
// only last line without leading spaces is left
@@ -330,29 +336,45 @@ public static String wrap(final String str,
} else {
// really long word or URL
if (wrapLongWords) {
+ if (matcherSize == 0) {
+ offset--;
+ }
// wrap really long word one line at a time
wrappedLine.append(str, offset, wrapLength + offset);
wrappedLine.append(newLineStr);
offset += wrapLength;
+ matcherSize = -1;
} else {
// do not wrap really long word, just extend beyond limit
matcher = patternToWrapOn.matcher(str.substring(offset + wrapLength));
if (matcher.find()) {
+ matcherSize = matcher.end() - matcher.start();
spaceToWrapAt = matcher.start() + offset + wrapLength;
}
if (spaceToWrapAt >= 0) {
+ if (matcherSize == 0 && offset != 0) {
+ offset--;
+ }
wrappedLine.append(str, offset, spaceToWrapAt);
wrappedLine.append(newLineStr);
offset = spaceToWrapAt + 1;
} else {
+ if (matcherSize == 0 && offset != 0) {
+ offset--;
+ }
wrappedLine.append(str, offset, str.length());
offset = inputLineLength;
+ matcherSize = -1;
}
}
}
}
+ if (matcherSize == 0 && offset < inputLineLength) {
+ offset--;
+ }
+
// Whatever is left in line is short enough to just pass through
wrappedLine.append(str, offset, str.length());
@@ -368,7 +390,7 @@ public static String wrap(final String str,
* use {@link #capitalizeFully(String)}.
*
* null input String returns null.
+ * A {@code null} input String returns {@code null}.
* Capitalization uses the Unicode title case, normally equivalent to
* upper case.null if null String input
+ * @return capitalized String, {@code null} if null String input
* @see #uncapitalize(String)
* @see #capitalizeFully(String)
*/
@@ -395,9 +417,9 @@ public static String capitalize(final String str) {
*
* null input String returns null.
+ * null if null String input
+ * @return capitalized String, {@code null} if null String input
* @see #uncapitalize(String)
* @see #capitalizeFully(String)
*/
@@ -450,10 +472,10 @@ public static String capitalize(final String str, final char... delimiters) {
/**
* null input String returns null.
+ * A {@code null} input String returns {@code null}.
* Capitalization uses the Unicode title case, normally equivalent to
* upper case.null if null String input
+ * @return capitalized String, {@code null} if null String input
*/
public static String capitalizeFully(final String str) {
return capitalizeFully(str, null);
@@ -473,13 +495,13 @@ public static String capitalizeFully(final String str) {
/**
* null input String returns null.
+ * null if null String input
+ * @return capitalized String, {@code null} if null String input
*/
public static String capitalizeFully(String str, final char... delimiters) {
if (StringUtils.isEmpty(str)) {
@@ -509,7 +531,7 @@ public static String capitalizeFully(String str, final char... delimiters) {
* Only the first character of each word is changed.
*
* null input String returns null.
* WordUtils.uncapitalize(null) = null
@@ -518,7 +540,7 @@ public static String capitalizeFully(String str, final char... delimiters) {
*
*
* @param str the String to uncapitalize, may be null
- * @return uncapitalized String, null if null String input
+ * @return uncapitalized String, {@code null} if null String input
* @see #capitalize(String)
*/
public static String uncapitalize(final String str) {
@@ -531,10 +553,10 @@ public static String uncapitalize(final String str) {
*
* null input String returns null.
* WordUtils.uncapitalize(null, *) = null
@@ -547,7 +569,7 @@ public static String uncapitalize(final String str) {
*
* @param str the String to uncapitalize, may be null
* @param delimiters set of characters to determine uncapitalization, null means whitespace
- * @return uncapitalized String,
*
- * @param left the first string, must not be null
- * @param right the second string, must not be null
+ * @param left the first CharSequence, must not be null
+ * @param right the second CharSequence, must not be null
* @param threshold the target threshold, must not be negative
* @return result distance, or -1
*/
private static int limitedCompare(CharSequence left, CharSequence right, final int threshold) { // NOPMD
if (left == null || right == null) {
- throw new IllegalArgumentException("Strings must not be null");
+ throw new IllegalArgumentException("CharSequences must not be null");
}
if (threshold < 0) {
throw new IllegalArgumentException("Threshold must not be negative");
@@ -241,6 +241,11 @@ private static int limitedCompare(CharSequence left, CharSequence right, final i
m = right.length();
}
+ // the edit distance cannot be less than the length difference
+ if (m - n > threshold) {
+ return -1;
+ }
+
int[] p = new int[n + 1]; // 'previous' cost array, horizontally
int[] d = new int[n + 1]; // cost array, horizontally
int[] tempD; // placeholder to assist in swapping p and d
@@ -265,17 +270,12 @@ private static int limitedCompare(CharSequence left, CharSequence right, final i
final int max = j > Integer.MAX_VALUE - threshold ? n : Math.min(
n, j + threshold);
- // the stripe may lead off of the table if s and t are of different
- // sizes
- if (min > max) {
- return -1;
- }
-
// ignore entry left of leftmost
if (min > 1) {
d[min - 1] = Integer.MAX_VALUE;
}
+ int lowerBound = Integer.MAX_VALUE;
// iterates through [min, max] in s
for (int i = min; i <= max; i++) {
if (left.charAt(i - 1) == rightJ) {
@@ -286,6 +286,11 @@ private static int limitedCompare(CharSequence left, CharSequence right, final i
// left and up
d[i] = 1 + Math.min(Math.min(d[i - 1], p[i]), p[i - 1]);
}
+ lowerBound = Math.min(lowerBound, d[i]);
+ }
+ // if the lower bound is greater than the threshold, then exit early
+ if (lowerBound > threshold) {
+ return -1;
}
// copy current distance counts to 'previous row' distance counts
@@ -328,14 +333,14 @@ private static int limitedCompare(CharSequence left, CharSequence right, final i
* unlimitedCompare("hello", "hallo") = 1
*
*
- * @param left the first String, must not be null
- * @param right the second String, must not be null
+ * @param left the first CharSequence, must not be null
+ * @param right the second CharSequence, must not be null
* @return result distance, or -1
- * @throws IllegalArgumentException if either String input {@code null}
+ * @throws IllegalArgumentException if either CharSequence input is {@code null}
*/
private static int unlimitedCompare(CharSequence left, CharSequence right) {
if (left == null || right == null) {
- throw new IllegalArgumentException("Strings must not be null");
+ throw new IllegalArgumentException("CharSequences must not be null");
}
/*
diff --git a/src/main/java/org/apache/commons/text/similarity/LongestCommonSubsequence.java b/src/main/java/org/apache/commons/text/similarity/LongestCommonSubsequence.java
index b913619f50..0f51906b11 100644
--- a/src/main/java/org/apache/commons/text/similarity/LongestCommonSubsequence.java
+++ b/src/main/java/org/apache/commons/text/similarity/LongestCommonSubsequence.java
@@ -24,7 +24,7 @@
* common. Two strings that are entirely different, return a value of 0, and two strings that return a value
* of the commonly shared length implies that the strings are completely the same in value and position.
* Note. Generally this algorithm is fairly inefficient, as for length m, n of the input
- * null if null String input
+ * @return uncapitalized String, {@code null} if null String input
* @see #capitalize(String)
*/
public static String uncapitalize(final String str, final char... delimiters) {
@@ -592,7 +614,7 @@ public static String uncapitalize(final String str, final char... delimiters) {
*
*
* null input String returns null.
* StringUtils.swapCase(null) = null
@@ -601,7 +623,7 @@ public static String uncapitalize(final String str, final char... delimiters) {
*
*
* @param str the String to swap case, may be null
- * @return the changed String, null if null String input
+ * @return The changed String, {@code null} if null String input
*/
public static String swapCase(final String str) {
if (StringUtils.isEmpty(str)) {
@@ -642,7 +664,7 @@ public static String swapCase(final String str) {
* Their case is not changed.
*
* null input String returns null.
* WordUtils.initials(null) = null
@@ -652,7 +674,7 @@ public static String swapCase(final String str) {
*
*
* @param str the String to get initials from, may be null
- * @return String of initial letters, null if null String input
+ * @return String of initial letters, {@code null} if null String input
* @see #initials(String,char[])
*/
public static String initials(final String str) {
@@ -667,7 +689,7 @@ public static String initials(final String str) {
*
* null input String returns null.
+ * A {@code null} input String returns {@code null}.
* An empty delimiter array returns an empty String.
@@ -681,7 +703,7 @@ public static String initials(final String str) {
*
* @param str the String to get initials from, may be null
* @param delimiters set of characters to determine words, null means whitespace
- * @return String of initial characters,
*
- * @param left the first String, must not be null
- * @param right the second String, must not be null
+ * @param left the first CharSequence, must not be null
+ * @param right the second CharSequence, must not be null
* @return result distance, or -1
- * @throws IllegalArgumentException if either String input {@code null}
+ * @throws IllegalArgumentException if either CharSequence input is {@code null}
*/
private static LevenshteinResults unlimitedCompare(CharSequence left, CharSequence right) {
if (left == null || right == null) {
- throw new IllegalArgumentException("Strings must not be null");
+ throw new IllegalArgumentException("CharSequences must not be null");
}
/*
diff --git a/src/main/java/org/apache/commons/text/similarity/LevenshteinDistance.java b/src/main/java/org/apache/commons/text/similarity/LevenshteinDistance.java
index a8fab0437e..913ec2454f 100644
--- a/src/main/java/org/apache/commons/text/similarity/LevenshteinDistance.java
+++ b/src/main/java/org/apache/commons/text/similarity/LevenshteinDistance.java
@@ -117,7 +117,7 @@ public Integer apply(final CharSequence left, final CharSequence right) {
/**
* Gets the default instance.
*
- * @return the default instance
+ * @return The default instance
*/
public static LevenshteinDistance getDefaultInstance() {
return DEFAULT_INSTANCE;
@@ -126,7 +126,7 @@ public static LevenshteinDistance getDefaultInstance() {
/**
* Gets the distance threshold.
*
- * @return the distance threshold
+ * @return The distance threshold
*/
public Integer getThreshold() {
return threshold;
@@ -158,14 +158,14 @@ public Integer getThreshold() {
* limitedCompare("hippo", "elephant", 6) = -1
* null if null String input
+ * @return String of initial characters, {@code null} if null String input
* @see #initials(String)
*/
public static String initials(final String str, final char... delimiters) {
@@ -689,7 +711,7 @@ public static String initials(final String str, final char... delimiters) {
return str;
}
if (delimiters != null && delimiters.length == 0) {
- return "";
+ return StringUtils.EMPTY;
}
final Set
* WordUtils.abbreviate("Now is the time for all good men", 0, 40, null)); = "Now"
@@ -836,7 +858,6 @@ public static boolean isDelimiter(final int codePoint, final char[] delimiters)
public static String abbreviate(final String str, int lower, int upper, final String appendToEnd) {
Validate.isTrue(upper >= -1, "upper value cannot be less than -1");
Validate.isTrue(upper >= lower || upper == -1, "upper value is less than lower value");
-
if (StringUtils.isEmpty(str)) {
return str;
}
@@ -857,7 +878,7 @@ public static String abbreviate(final String str, int lower, int upper, final St
final int index = StringUtils.indexOf(str, " ", lower);
if (index == -1) {
result.append(str, 0, upper);
- // only if abbreviation has occured do we append the appendToEnd value
+ // only if abbreviation has occurred do we append the appendToEnd value
if (upper != str.length()) {
result.append(StringUtils.defaultString(appendToEnd));
}
diff --git a/src/main/java/org/apache/commons/text/diff/DeleteCommand.java b/src/main/java/org/apache/commons/text/diff/DeleteCommand.java
index 8173718b6c..69ed48493d 100644
--- a/src/main/java/org/apache/commons/text/diff/DeleteCommand.java
+++ b/src/main/java/org/apache/commons/text/diff/DeleteCommand.java
@@ -44,7 +44,7 @@ public DeleteCommand(final T object) {
}
/**
- * Accept a visitor. When a
*
- * @param left the first string, must not be null
- * @param right the second string, must not be null
+ * @param left the first CharSequence, must not be null
+ * @param right the second CharSequence, must not be null
* @param threshold the target threshold, must not be negative
* @return result distance, or -1
*/
@@ -159,7 +159,7 @@ private static LevenshteinResults limitedCompare(CharSequence left,
CharSequence right,
final int threshold) { //NOPMD
if (left == null || right == null) {
- throw new IllegalArgumentException("Strings must not be null");
+ throw new IllegalArgumentException("CharSequences must not be null");
}
if (threshold < 0) {
throw new IllegalArgumentException("Threshold must not be negative");
@@ -331,14 +331,14 @@ private static LevenshteinResults limitedCompare(CharSequence left,
* unlimitedCompare("hello", "hallo") = 1
* DeleteCommand accepts a visitor, it calls
+ * Accept a visitor. When a {@code DeleteCommand} accepts a visitor, it calls
* its {@link CommandVisitor#visitDeleteCommand visitDeleteCommand} method.
*
* @param visitor the visitor to be accepted
diff --git a/src/main/java/org/apache/commons/text/diff/EditCommand.java b/src/main/java/org/apache/commons/text/diff/EditCommand.java
index 7920206aef..d26215490a 100644
--- a/src/main/java/org/apache/commons/text/diff/EditCommand.java
+++ b/src/main/java/org/apache/commons/text/diff/EditCommand.java
@@ -33,12 +33,12 @@
* inserted into the first sequence, {@link DeleteCommand DeleteCommand} which
* correspond to an object of the first sequence being removed and
* {@link KeepCommand KeepCommand} which correspond to an object of the first
- * sequence which equals an object in the second sequence. It is
+ * sequence which {@code equals} an object in the second sequence. It is
* guaranteed that comparison is always performed this way (i.e. the
- * equals method of the object from the first sequence is used and
+ * {@code equals} method of the object from the first sequence is used and
* the object passed as an argument comes from the second sequence) ; this can
* be important if subclassing is used for some elements in the first sequence
- * and the equals method is specialized.
+ * and the {@code equals} method is specialized.
*
*
* equals
+ * is used for some elements in the first sequence and the {@code equals}
* method is specialized.
*
* @see StringsComparator
diff --git a/src/main/java/org/apache/commons/text/diff/InsertCommand.java b/src/main/java/org/apache/commons/text/diff/InsertCommand.java
index f0337dc458..f8eb0e07c1 100644
--- a/src/main/java/org/apache/commons/text/diff/InsertCommand.java
+++ b/src/main/java/org/apache/commons/text/diff/InsertCommand.java
@@ -44,7 +44,7 @@ public InsertCommand(final T object) {
}
/**
- * Accept a visitor. When an InsertCommand accepts a visitor,
+ * Accept a visitor. When an {@code InsertCommand} accepts a visitor,
* it calls its {@link CommandVisitor#visitInsertCommand visitInsertCommand}
* method.
*
diff --git a/src/main/java/org/apache/commons/text/diff/KeepCommand.java b/src/main/java/org/apache/commons/text/diff/KeepCommand.java
index 34c6fe7f09..efd48249f3 100644
--- a/src/main/java/org/apache/commons/text/diff/KeepCommand.java
+++ b/src/main/java/org/apache/commons/text/diff/KeepCommand.java
@@ -19,7 +19,7 @@
/**
* Command representing the keeping of one object present in both sequences.
* equals another objects in
+ * When one object of the first sequence {@code equals} another objects in
* the second sequence at the right place, the {@link EditScript edit script}
* transforming the first sequence into the second sequence uses an instance of
* this class to represent the keeping of this object. The objects embedded in
@@ -46,7 +46,7 @@ public KeepCommand(final T object) {
}
/**
- * Accept a visitor. When a KeepCommand accepts a visitor, it
+ * Accept a visitor. When a {@code KeepCommand} accepts a visitor, it
* calls its {@link CommandVisitor#visitKeepCommand visitKeepCommand} method.
*
* @param visitor the visitor to be accepted
diff --git a/src/main/java/org/apache/commons/text/diff/ReplacementsFinder.java b/src/main/java/org/apache/commons/text/diff/ReplacementsFinder.java
index 46f1b888c0..6beff3b100 100644
--- a/src/main/java/org/apache/commons/text/diff/ReplacementsFinder.java
+++ b/src/main/java/org/apache/commons/text/diff/ReplacementsFinder.java
@@ -56,10 +56,12 @@ public class ReplacementsFinderfrom
- * sub-sequence into the to sub-sequence.
+ * The replacement is defined as replacing the {@code from}
+ * sub-sequence into the {@code to} sub-sequence.
*
* @param skipped number of tokens skipped since the last call (i.e. number of
* tokens that were in both sequences), this number should be strictly positive
diff --git a/src/main/java/org/apache/commons/text/diff/StringsComparator.java b/src/main/java/org/apache/commons/text/diff/StringsComparator.java
index 66ce4b0cc7..b3963e5d5b 100644
--- a/src/main/java/org/apache/commons/text/diff/StringsComparator.java
+++ b/src/main/java/org/apache/commons/text/diff/StringsComparator.java
@@ -19,16 +19,16 @@
/**
* o1.equals(o2) where o1 belongs to the first
- * sequence and o2 belongs to the second sequence. This can
+ * {@code o1.equals(o2)} where {@code o1} belongs to the first
+ * sequence and {@code o2} belongs to the second sequence. This can
* be important if subclassing is used for some elements in the first
- * sequence and the equals method is specialized.
+ * sequence and the {@code equals} method is specialized.
* equals method is used to compare objects, so any
+ * sequences. The {@code equals} method is used to compare objects, so any
* object can be put into sequences. Modifications include deleting, inserting
* or keeping one object, starting from the beginning of the first sequence.
* o1.equals(o2) where o1 belongs to the first
- * sequence and o2 belongs to the second sequence. This can be
+ * {@code o1.equals(o2)} where {@code o1} belongs to the first
+ * sequence and {@code o2} belongs to the second sequence. This can be
* important if subclassing is used for some elements in the first sequence
- * and the equals method is specialized.
+ * and the {@code equals} method is specialized.
* equals method is specialized.
+ * sequence and the {@code equals} method is specialized.
*
*
- * @return the edit script resulting from the comparison of the two
+ * @return The edit script resulting from the comparison of the two
* sequences
*/
public EditScript
+ * Map<String, Object> map = new HashMap<String, Object>();
+ * map.put("number", new Integer(2));
+ * assertEquals("2", StringLookupFactory.biFunctionStringLookup(map).lookup("number", "A context object"));
+ *
+ *
+ * @param key the key to look up, may be null.
+ * @param object ignored by default.
+ * @return The matching value, null if no match.
+ */
+ default String lookup(final String key, final U object) {
+ return lookup(key);
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/text/lookup/ConstantStringLookup.java b/src/main/java/org/apache/commons/text/lookup/ConstantStringLookup.java
new file mode 100644
index 0000000000..a7a5d6130e
--- /dev/null
+++ b/src/main/java/org/apache/commons/text/lookup/ConstantStringLookup.java
@@ -0,0 +1,152 @@
+/*
+ * 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.lookup;
+
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.lang3.ClassUtils;
+import org.apache.commons.text.StringSubstitutor;
+
+/**
+ *
+ * StringLookupFactory.INSTANCE.constantStringLookup().lookup("java.awt.event.KeyEvent.VK_ESCAPE");
+ *
+ *
+ * StringSubstitutor.createInterpolator().replace("... ${const:java.awt.event.KeyEvent.VK_ESCAPE} ..."));
+ *
+ *
+ * ClassUtils.
+ *
+ * @param className the name of the class to be loaded
+ * @return The corresponding class object
+ * @throws ClassNotFoundException if the class cannot be loaded
+ */
+ protected Class> fetchClass(final String className) throws ClassNotFoundException {
+ return ClassUtils.getClass(className);
+ }
+
+ /**
+ * Tries to resolve the specified variable. The passed in variable name is interpreted as the name of a static
+ * final member field of a class. If the value has already been obtained, it can be retrieved from an internal
+ * cache. Otherwise this method will invoke the {@code resolveField()} method and pass in the name of the class and
+ * the field.
+ *
+ * @param key the name of the variable to be resolved
+ * @return The value of this variable or null if it cannot be resolved
+ */
+ @Override
+ public synchronized String lookup(final String key) {
+ if (key == null) {
+ return null;
+ }
+ String result;
+ result = CONSTANT_CACHE.get(key);
+ if (result != null) {
+ return result;
+ }
+ final int fieldPos = key.lastIndexOf(FIELD_SEPARATOR);
+ if (fieldPos < 0) {
+ return null;
+ }
+ try {
+ final Object value = resolveField(key.substring(0, fieldPos), key.substring(fieldPos + 1));
+ if (value != null) {
+ final String string = Objects.toString(value, null);
+ CONSTANT_CACHE.put(key, string);
+ result = string;
+ }
+ } catch (final Exception ex) {
+ // TODO it would be nice to log
+ return null;
+ }
+ return result;
+ }
+
+ /**
+ * Determines the value of the specified constant member field of a class. This implementation will call
+ * {@code fetchClass()} to obtain the {@code java.lang.Class} object for the target class. Then it will use
+ * reflection to obtain the field's value. For this to work the field must be accessable.
+ *
+ * @param className the name of the class
+ * @param fieldName the name of the member field of that class to read
+ * @return The field's value
+ * @throws Exception if an error occurs
+ */
+ protected Object resolveField(final String className, final String fieldName) throws Exception {
+ final Class> clazz = fetchClass(className);
+ if (clazz == null) {
+ return null;
+ }
+ return clazz.getField(fieldName).get(null);
+ }
+}
diff --git a/src/main/java/org/apache/commons/text/lookup/DateStringLookup.java b/src/main/java/org/apache/commons/text/lookup/DateStringLookup.java
new file mode 100644
index 0000000000..b3bcb9ccdf
--- /dev/null
+++ b/src/main/java/org/apache/commons/text/lookup/DateStringLookup.java
@@ -0,0 +1,94 @@
+/*
+ * 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.lookup;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.apache.commons.lang3.time.FastDateFormat;
+import org.apache.commons.text.StringSubstitutor;
+
+/**
+ * Formats the current date with the format given in the key in a format compatible with
+ * {@link java.text.SimpleDateFormat}.
+ *
+ * StringLookupFactory.INSTANCE.dateStringLookup().lookup("yyyy-MM-dd");
+ *
+ *
+ * StringSubstitutor.createInterpolator().replace("... ${date:yyyy-MM-dd} ..."));
+ *
+ *
+ *
+ *
+ *
+ * StringLookupFactory.INSTANCE.dnsStringLookup().lookup("address|apache.org");
+ *
+ *
+ * StringSubstitutor.createInterpolator().replace("... ${dns:address|apache.org} ..."));
+ *
+ *
+ * StringLookupFactory.INSTANCE.fileStringLookup().lookup(UTF-8:com/domain/document.properties");
+ *
+ *
+ * StringSubstitutor.createInterpolator().replace("... ${file:UTF-8:com/domain/document.properties} ..."));
+ *
+ *
+ *
+ *
+ *
+ * StringLookupFactory.INSTANCE.javaPlatformStringLookup().lookup("version");
+ *
+ *
+ * StringSubstitutor.createInterpolator().replace("... ${java:version} ..."));
+ *
+ *
+ *
+ *
+ * @param key the key to be looked up, may be null
+ * @return The value of the environment variable.
+ */
+ @Override
+ public String lookup(final String key) {
+ if (key == null) {
+ return null;
+ }
+ switch (key) {
+ case KEY_VERSION:
+ return "Java version " + getSystemProperty("java.version");
+ case KEY_RUNTIME:
+ return getRuntime();
+ case KEY_VM:
+ return getVirtualMachine();
+ case KEY_OS:
+ return getOperatingSystem();
+ case KEY_HARDWARE:
+ return getHardware();
+ case KEY_LOCALE:
+ return getLocale();
+ default:
+ throw new IllegalArgumentException(key);
+ }
+ }
+}
diff --git a/src/main/java/org/apache/commons/text/lookup/LocalHostStringLookup.java b/src/main/java/org/apache/commons/text/lookup/LocalHostStringLookup.java
new file mode 100644
index 0000000000..20271adbec
--- /dev/null
+++ b/src/main/java/org/apache/commons/text/lookup/LocalHostStringLookup.java
@@ -0,0 +1,75 @@
+/*
+ * 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.lookup;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Looks up keys related to the local host: host name, canonical host name, host address.
+ *
+ *
+ *
+ * @since 1.3
+ */
+final class LocalHostStringLookup extends AbstractStringLookup {
+
+ /**
+ * Defines the singleton for this class.
+ */
+ static final LocalHostStringLookup INSTANCE = new LocalHostStringLookup();
+
+ /**
+ * No need to build instances for now.
+ */
+ private LocalHostStringLookup() {
+ // empty
+ }
+
+ /**
+ * Looks up the value of a local host key.
+ *
+ * @param key the key to be looked up, may be null.
+ * @return The value of the environment variable.
+ */
+ @Override
+ public String lookup(final String key) {
+ if (key == null) {
+ return null;
+ }
+ try {
+ switch (key) {
+ case InetAddressKeys.KEY_NAME:
+ return InetAddress.getLocalHost().getHostName();
+ case InetAddressKeys.KEY_CANONICAL_NAME:
+ return InetAddress.getLocalHost().getCanonicalHostName();
+ case InetAddressKeys.KEY_ADDRESS:
+ return InetAddress.getLocalHost().getHostAddress();
+ default:
+ throw new IllegalArgumentException(key);
+ }
+ } catch (final UnknownHostException e) {
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/org/apache/commons/text/lookup/PropertiesStringLookup.java b/src/main/java/org/apache/commons/text/lookup/PropertiesStringLookup.java
new file mode 100644
index 0000000000..2f5d9a27b6
--- /dev/null
+++ b/src/main/java/org/apache/commons/text/lookup/PropertiesStringLookup.java
@@ -0,0 +1,102 @@
+/*
+ * 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.lookup;
+
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Properties;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Looks up keys from an XML document.
+ *
+ * StringSubstitutor.createInterpolator().replace("${script:javascript:3 + 4}"));
+ *
+ *
+ * @since 1.5
+ */
+final class ScriptStringLookup extends AbstractStringLookup {
+
+ /**
+ * Defines the singleton for this class.
+ */
+ static final ScriptStringLookup INSTANCE = new ScriptStringLookup();
+
+ /**
+ * No need to build instances for now.
+ */
+ private ScriptStringLookup() {
+ // empty
+ }
+
+ /**
+ * Execute the script with the engine name in the format "EngineName:Script". Extra colons will be ignored.
+ *
+ * Map<String, Object> map = new HashMap<String, Object>();
+ * map.put("number", new Integer(2));
+ * assertEquals("2", StringLookupFactory.mapStringLookup(map).lookup("number"));
+ *
+ *
+ * @param key the key to look up, may be null.
+ * @return The matching value, null if no match.
+ */
+ String lookup(String key);
+}
diff --git a/src/main/java/org/apache/commons/text/lookup/StringLookupFactory.java b/src/main/java/org/apache/commons/text/lookup/StringLookupFactory.java
new file mode 100644
index 0000000000..b38dd414e3
--- /dev/null
+++ b/src/main/java/org/apache/commons/text/lookup/StringLookupFactory.java
@@ -0,0 +1,1138 @@
+/*
+ * 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.lookup;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import org.apache.commons.text.StringSubstitutor;
+
+/**
+ * Create instances of string lookups or access singleton string lookups implemented in this package.
+ *
+ *
+ *
+ *
+ *
+ *
+ * Key
+ * Interface
+ * Factory Method
+ * Since
+ *
+ *
+ * {@value #KEY_BASE64_DECODER}
+ * {@link StringLookup}
+ * {@link #base64DecoderStringLookup()}
+ * 1.6
+ *
+ *
+ * {@value #KEY_BASE64_ENCODER}
+ * {@link StringLookup}
+ * {@link #base64EncoderStringLookup()}
+ * 1.6
+ *
+ *
+ * {@value #KEY_CONST}
+ * {@link StringLookup}
+ * {@link #constantStringLookup()}
+ * 1.5
+ *
+ *
+ * {@value #KEY_DATE}
+ * {@link StringLookup}
+ * {@link #dateStringLookup()}
+ * 1.5
+ *
+ *
+ * {@value #KEY_DNS}
+ * {@link StringLookup}
+ * {@link #dnsStringLookup()}
+ * 1.8
+ *
+ *
+ * {@value #KEY_ENV}
+ * {@link StringLookup}
+ * {@link #environmentVariableStringLookup()}
+ * 1.3
+ *
+ *
+ * {@value #KEY_FILE}
+ * {@link StringLookup}
+ * {@link #fileStringLookup()}
+ * 1.5
+ *
+ *
+ * {@value #KEY_JAVA}
+ * {@link StringLookup}
+ * {@link #javaPlatformStringLookup()}
+ * 1.5
+ *
+ *
+ * {@value #KEY_LOCALHOST}
+ * {@link StringLookup}
+ * {@link #localHostStringLookup()}
+ * 1.3
+ *
+ *
+ * {@value #KEY_PROPERTIES}
+ * {@link StringLookup}
+ * {@link #propertiesStringLookup()}
+ * 1.5
+ *
+ *
+ * {@value #KEY_RESOURCE_BUNDLE}
+ * {@link StringLookup}
+ * {@link #resourceBundleStringLookup()}
+ * 1.6
+ *
+ *
+ * {@value #KEY_SCRIPT}
+ * {@link StringLookup}
+ * {@link #scriptStringLookup()}
+ * 1.5
+ *
+ *
+ * {@value #KEY_SYS}
+ * {@link StringLookup}
+ * {@link #systemPropertyStringLookup()}
+ * 1.3
+ *
+ *
+ * {@value #KEY_URL}
+ * {@link StringLookup}
+ * {@link #urlStringLookup()}
+ * 1.5
+ *
+ *
+ * {@value #KEY_URL_DECODER}
+ * {@link StringLookup}
+ * {@link #urlDecoderStringLookup()}
+ * 1.5
+ *
+ *
+ * {@value #KEY_URL_ENCODER}
+ * {@link StringLookup}
+ * {@link #urlEncoderStringLookup()}
+ * 1.5
+ *
+ *
+ * {@value #KEY_XML}
+ * {@link StringLookup}
+ * {@link #xmlStringLookup()}
+ * 1.5
+ *
+ *
+ *
+ * @since 1.3
+ */
+public final class StringLookupFactory {
+
+ /**
+ * Defines the singleton for this class.
+ */
+ public static final StringLookupFactory INSTANCE = new StringLookupFactory();
+
+ /**
+ * Decodes Base64 Strings.
+ *
+ *
+ * Interface
+ * Factory Method
+ * Since
+ *
+ *
+ * {@link BiStringLookup}
+ * {@link #biFunctionStringLookup(BiFunction)}
+ * 1.9
+ *
+ *
+ * {@link StringLookup}
+ * {@link #functionStringLookup(Function)}
+ * 1.9
+ *
+ * StringLookupFactory.INSTANCE.base64DecoderStringLookup().lookup("SGVsbG9Xb3JsZCE=");
+ *
+ *
+ * StringSubstitutor.createInterpolator().replace("... ${base64Decoder:SGVsbG9Xb3JsZCE=} ..."));
+ *
+ *
+ * StringLookupFactory.INSTANCE.base64EncoderStringLookup().lookup("HelloWorld!");
+ *
+ *
+ * StringSubstitutor.createInterpolator().replace("... ${base64Encoder:HelloWorld!} ..."));
+ *
+ *
+ * StringLookupFactory.INSTANCE.dateStringLookup().lookup("USER");
+ *
+ *
+ * StringSubstitutor.createInterpolator().replace("... ${env:USER} ..."));
+ *
+ *
+ * StringLookupFactory.INSTANCE.base64DecoderStringLookup().lookup("SGVsbG9Xb3JsZCE=");
+ *
+ *
+ * StringSubstitutor.createInterpolator().replace("... ${base64Decoder:SGVsbG9Xb3JsZCE=} ..."));
+ *
+ *
+ * StringLookupFactory.INSTANCE.base64EncoderStringLookup().lookup("HelloWorld!");
+ *
+ *
+ * StringSubstitutor.createInterpolator().replace("... ${base64Encoder:HelloWorld!} ..."));
+ *
+ *
+ * StringLookupFactory.INSTANCE.base64DecoderStringLookup().lookup("SGVsbG9Xb3JsZCE=");
+ *
+ *
+ * StringSubstitutor.createInterpolator().replace("... ${base64Decoder:SGVsbG9Xb3JsZCE=} ..."));
+ *
+ *
+ * StringLookupFactory.INSTANCE.constantStringLookup().lookup("java.awt.event.KeyEvent.VK_ESCAPE");
+ *
+ *
+ * StringSubstitutor.createInterpolator().replace("... ${const:java.awt.event.KeyEvent.VK_ESCAPE} ..."));
+ *
+ *
+ * StringLookupFactory.INSTANCE.dateStringLookup().lookup("yyyy-MM-dd");
+ *
+ *
+ * StringSubstitutor.createInterpolator().replace("... ${date:yyyy-MM-dd} ..."));
+ *
+ *
+ *
+ *
+ *
+ * StringLookupFactory.INSTANCE.dnsStringLookup().lookup("address|apache.org");
+ *
+ *
+ * StringSubstitutor.createInterpolator().replace("... ${dns:address|apache.org} ..."));
+ *
+ *
+ * StringLookupFactory.INSTANCE.dateStringLookup().lookup("USER");
+ *
+ *
+ * StringSubstitutor.createInterpolator().replace("... ${env:USER} ..."));
+ *
+ *
+ * StringLookupFactory.INSTANCE.fileStringLookup().lookup("UTF-8:com/domain/document.properties");
+ *
+ *
+ * StringSubstitutor.createInterpolator().replace("... ${file:UTF-8:com/domain/document.properties} ..."));
+ *
+ *
+ * StringLookupFactory.INSTANCE.interpolatorStringLookup().lookup("${sys:os.name}, ${env:USER}");
+ *
+ *
+ * StringSubstitutor.createInterpolator().replace("... ${sys:os.name}, ${env:USER} ..."));
+ *
+ *
+ *
+ *
+ *
+ * StringLookupFactory.INSTANCE.javaPlatformStringLookup().lookup("version");
+ *
+ *
+ * StringSubstitutor.createInterpolator().replace("... ${java:version} ..."));
+ *
+ *
+ *
+ *
+ *
+ * StringLookupFactory.INSTANCE.localHostStringLookup().lookup("canonical-name");
+ *
+ *
+ * StringSubstitutor.createInterpolator().replace("... ${localhost:canonical-name} ..."));
+ *
+ *
+ * StringLookupFactory.INSTANCE.propertiesStringLookup().lookup("com/domain/document.properties::MyKey");
+ *
+ *
+ * StringSubstitutor.createInterpolator().replace("... ${properties:com/domain/document.properties::MyKey} ..."));
+ *
+ *
+ * StringLookupFactory.INSTANCE.resourceBundleStringLookup().lookup("com.domain.messages:MyKey");
+ *
+ *
+ * StringSubstitutor.createInterpolator().replace("... ${resourceBundle:com.domain.messages:MyKey} ..."));
+ *
+ *
+ * StringLookupFactory.INSTANCE.resourceBundleStringLookup("com.domain.messages").lookup("MyKey");
+ *
+ *
+ * StringLookupFactory.INSTANCE.scriptStringLookup().lookup("javascript:3 + 4");
+ *
+ *
+ * StringSubstitutor.createInterpolator().replace("... ${javascript:3 + 4} ..."));
+ *
+ *
+ * StringLookupFactory.INSTANCE.systemPropertyStringLookup().lookup("os.name");
+ *
+ *
+ * StringSubstitutor.createInterpolator().replace("... ${sys:os.name} ..."));
+ *
+ *
+ * StringLookupFactory.INSTANCE.urlDecoderStringLookup().lookup("Hello%20World%21");
+ *
+ *
+ * StringSubstitutor.createInterpolator().replace("... ${urlDecoder:Hello%20World%21} ..."));
+ *
+ *
+ * StringLookupFactory.INSTANCE.urlEncoderStringLookup().lookup("Hello World!");
+ *
+ *
+ * StringSubstitutor.createInterpolator().replace("... ${urlEncoder:Hello World!} ..."));
+ *
+ *
+ * StringLookupFactory.INSTANCE.urlStringLookup().lookup("UTF-8:https://www.apache.org");
+ *
+ *
+ * StringSubstitutor.createInterpolator().replace("... ${url:UTF-8:https://www.apache.org} ..."));
+ *
+ *
+ * StringLookupFactory.INSTANCE.xmlStringLookup().lookup("com/domain/document.xml:/path/to/node");
+ *
+ *
+ * StringSubstitutor.createInterpolator().replace("... ${xml:com/domain/document.xml:/path/to/node} ..."));
+ *
+ * X*) over an
- * alphabet (X). Note, that a metric
- * on a set S is a function d: [S * S] -> [0, INFINITY) such
- * that the following hold for x,y,z in
- * the set S:
+ * An edit distance is a formal metric on the Kleene closure ({@code X*}) over an
+ * alphabet ({@code X}). Note, that a metric
+ * on a set {@code S} is a function {@code d: [S * S] -> [0, INFINITY)} such
+ * that the following hold for {@code x,y,z} in
+ * the set {@code S}:
*
- *
*
*
* d(x,y) >= 0, non-negativity or separation axiomd(x,y) == 0, if and only if, x == yd(x,y) == d(y,x), symmetry, andd(x,z) <= d(x,y) + d(y,z), the triangle inequalityapply method
+ * The {@code apply} method
* accepts a pair of {@link CharSequence} parameters
- * and returns an R type similarity score.
+ * and returns an {@code R} type similarity score.
*
- * score.fuzzyScore(null, null, null) = IllegalArgumentException
- * score.fuzzyScore("", "", Locale.ENGLISH) = 0
- * score.fuzzyScore("Workshop", "b", Locale.ENGLISH) = 0
- * score.fuzzyScore("Room", "o", Locale.ENGLISH) = 1
- * score.fuzzyScore("Workshop", "w", Locale.ENGLISH) = 1
- * score.fuzzyScore("Workshop", "ws", Locale.ENGLISH) = 2
- * score.fuzzyScore("Workshop", "wo", Locale.ENGLISH) = 4
- * score.fuzzyScore("Apache Software Foundation", "asf", Locale.ENGLISH) = 3
+ * score.fuzzyScore(null, null) = IllegalArgumentException
+ * score.fuzzyScore("not null", null) = IllegalArgumentException
+ * score.fuzzyScore(null, "not null") = IllegalArgumentException
+ * score.fuzzyScore("", "") = 0
+ * score.fuzzyScore("Workshop", "b") = 0
+ * score.fuzzyScore("Room", "o") = 1
+ * score.fuzzyScore("Workshop", "w") = 1
+ * score.fuzzyScore("Workshop", "ws") = 2
+ * score.fuzzyScore("Workshop", "wo") = 4
+ * score.fuzzyScore("Apache Software Foundation", "asf") = 3
*
*
* @param term a full term that should be matched against, must not be null
* @param query the query that will be matched against a term, must not be
* null
* @return result score
- * @throws IllegalArgumentException if either String input {@code null} or
- * Locale input {@code null}
+ * @throws IllegalArgumentException if the term or query is {@code null}
*/
public Integer fuzzyScore(final CharSequence term, final CharSequence query) {
if (term == null || query == null) {
- throw new IllegalArgumentException("Strings must not be null");
+ throw new IllegalArgumentException("CharSequences must not be null");
}
// fuzzy logic is case insensitive. We normalize the Strings to lower
@@ -135,7 +136,7 @@ public Integer fuzzyScore(final CharSequence term, final CharSequence query) {
/**
* Gets the locale.
*
- * @return the locale
+ * @return The locale
*/
public Locale getLocale() {
return locale;
diff --git a/src/main/java/org/apache/commons/text/similarity/HammingDistance.java b/src/main/java/org/apache/commons/text/similarity/HammingDistance.java
index 8d88fe82f9..183fbd9737 100644
--- a/src/main/java/org/apache/commons/text/similarity/HammingDistance.java
+++ b/src/main/java/org/apache/commons/text/similarity/HammingDistance.java
@@ -57,11 +57,11 @@ public class HammingDistance implements EditDistance
* distance.apply(null, null) = IllegalArgumentException
- * distance.apply("","") = 0.0
- * distance.apply("","a") = 0.0
- * distance.apply("aaapppp", "") = 0.0
- * distance.apply("frog", "fog") = 0.93
- * distance.apply("fly", "ant") = 0.0
- * distance.apply("elephant", "hippo") = 0.44
- * distance.apply("hippo", "elephant") = 0.44
- * distance.apply("hippo", "zzzzzzzz") = 0.0
- * distance.apply("hello", "hallo") = 0.88
- * distance.apply("ABC Corporation", "ABC Corp") = 0.93
- * distance.apply("D N H Enterprises Inc", "D & H Enterprises, Inc.") = 0.95
- * distance.apply("My Gym Children's Fitness Center", "My Gym. Childrens Fitness") = 0.92
- * distance.apply("PENNSYLVANIA", "PENNCISYLVNIA") = 0.88
+ * distance.apply("foo", null) = IllegalArgumentException
+ * distance.apply(null, "foo") = IllegalArgumentException
+ * distance.apply("", "") = 0.0
+ * distance.apply("foo", "foo") = 0.0
+ * distance.apply("foo", "foo ") = 0.06
+ * distance.apply("foo", "foo ") = 0.09
+ * distance.apply("foo", " foo ") = 0.13
+ * distance.apply("foo", " foo") = 0.49
+ * distance.apply("", "a") = 1.0
+ * distance.apply("aaapppp", "") = 1.0
+ * distance.apply("frog", "fog") = 0.07
+ * distance.apply("fly", "ant") = 1.0
+ * distance.apply("elephant", "hippo") = 0.56
+ * distance.apply("hippo", "elephant") = 0.56
+ * distance.apply("hippo", "zzzzzzzz") = 1.0
+ * distance.apply("hello", "hallo") = 0.12
+ * distance.apply("ABC Corporation", "ABC Corp") = 0.09
+ * distance.apply("D N H Enterprises Inc", "D & H Enterprises, Inc.") = 0.05
+ * distance.apply("My Gym Children's Fitness Center", "My Gym. Childrens Fitness") = 0.08
+ * distance.apply("PENNSYLVANIA", "PENNCISYLVNIA") = 0.12
*
*
- * @param left the first String, must not be null
- * @param right the second String, must not be null
+ * @param left the first CharSequence, must not be null
+ * @param right the second CharSequence, must not be null
* @return result distance
- * @throws IllegalArgumentException if either String input {@code null}
+ * @throws IllegalArgumentException if either CharSequence input is {@code null}
*/
@Override
public Double apply(final CharSequence left, final CharSequence right) {
- final double defaultScalingFactor = 0.1;
if (left == null || right == null) {
- throw new IllegalArgumentException("Strings must not be null");
+ throw new IllegalArgumentException("CharSequences must not be null");
}
+ // TODO: replace the rest of the code by this in 2.0, see TEXT-104
+ //
+ // JaroWinklerSimilarity similarity = new JaroWinklerSimilarity();
+ // return 1 - similarity.apply(left, right);
+
+ final double defaultScalingFactor = 0.1;
final int[] mtp = matches(left, right);
final double m = mtp[0];
if (m == 0) {
return 0D;
}
- final double j = ((m / left.length() + m / right.length() + (m - mtp[1]) / m)) / 3;
- final double jw = j < 0.7D ? j : j + Math.min(defaultScalingFactor, 1D / mtp[3]) * mtp[2] * (1D - j);
+ final double j = ((m / left.length() + m / right.length() + (m - (double) mtp[1] / 2) / m)) / 3;
+ final double jw = j < 0.7D ? j : j + defaultScalingFactor * mtp[2] * (1D - j);
return jw;
}
+ // TODO: remove this method in 2.0, see TEXT-104
/**
- * This method returns the Jaro-Winkler string matches, transpositions, prefix, max array.
+ * This method returns the Jaro-Winkler string matches, half transpositions, prefix array.
*
* @param first the first string to be matched
* @param second the second string to be matched
- * @return mtp array containing: matches, transpositions, prefix, and max length
+ * @return mtp array containing: matches, half transpositions, and prefix
+ * @deprecated Deprecated as of 1.7. This method will be removed in 2.0, and moved to a Jaro Winkler similarity
+ * class.
*/
+ @Deprecated
protected static int[] matches(final CharSequence first, final CharSequence second) {
- CharSequence max, min;
+ final CharSequence max;
+ final CharSequence min;
if (first.length() > second.length()) {
max = first;
min = second;
@@ -136,21 +138,20 @@ protected static int[] matches(final CharSequence first, final CharSequence seco
si++;
}
}
- int transpositions = 0;
+ int halfTranspositions = 0;
for (int mi = 0; mi < ms1.length; mi++) {
if (ms1[mi] != ms2[mi]) {
- transpositions++;
+ halfTranspositions++;
}
}
int prefix = 0;
- for (int mi = 0; mi < min.length(); mi++) {
+ for (int mi = 0; mi < Math.min(4, min.length()); mi++) {
if (first.charAt(mi) == second.charAt(mi)) {
prefix++;
} else {
break;
}
}
- return new int[] {matches, transpositions / 2, prefix, max.length()};
+ return new int[] {matches, halfTranspositions, prefix};
}
-
}
diff --git a/src/main/java/org/apache/commons/text/similarity/JaroWinklerSimilarity.java b/src/main/java/org/apache/commons/text/similarity/JaroWinklerSimilarity.java
new file mode 100644
index 0000000000..7ff5869271
--- /dev/null
+++ b/src/main/java/org/apache/commons/text/similarity/JaroWinklerSimilarity.java
@@ -0,0 +1,164 @@
+/*
+ * 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.similarity;
+
+import java.util.Arrays;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * A similarity algorithm indicating the percentage of matched characters between two character sequences.
+ *
+ *
+ * sim.apply(null, null) = IllegalArgumentException
+ * sim.apply("foo", null) = IllegalArgumentException
+ * sim.apply(null, "foo") = IllegalArgumentException
+ * sim.apply("", "") = 1.0
+ * sim.apply("foo", "foo") = 1.0
+ * sim.apply("foo", "foo ") = 0.94
+ * sim.apply("foo", "foo ") = 0.91
+ * sim.apply("foo", " foo ") = 0.87
+ * sim.apply("foo", " foo") = 0.51
+ * sim.apply("", "a") = 0.0
+ * sim.apply("aaapppp", "") = 0.0
+ * sim.apply("frog", "fog") = 0.93
+ * sim.apply("fly", "ant") = 0.0
+ * sim.apply("elephant", "hippo") = 0.44
+ * sim.apply("hippo", "elephant") = 0.44
+ * sim.apply("hippo", "zzzzzzzz") = 0.0
+ * sim.apply("hello", "hallo") = 0.88
+ * sim.apply("ABC Corporation", "ABC Corp") = 0.91
+ * sim.apply("D N H Enterprises Inc", "D & H Enterprises, Inc.") = 0.95
+ * sim.apply("My Gym Children's Fitness Center", "My Gym. Childrens Fitness") = 0.92
+ * sim.apply("PENNSYLVANIA", "PENNCISYLVNIA") = 0.88
+ *
+ *
+ * @param left the first CharSequence, must not be null
+ * @param right the second CharSequence, must not be null
+ * @return result similarity
+ * @throws IllegalArgumentException if either CharSequence input is {@code null}
+ */
+ @Override
+ public Double apply(final CharSequence left, final CharSequence right) {
+ final double defaultScalingFactor = 0.1;
+
+ if (left == null || right == null) {
+ throw new IllegalArgumentException("CharSequences must not be null");
+ }
+
+ if (StringUtils.equals(left, right)) {
+ return 1d;
+ }
+
+ final int[] mtp = matches(left, right);
+ final double m = mtp[0];
+ if (m == 0) {
+ return 0d;
+ }
+ final double j = ((m / left.length() + m / right.length() + (m - (double) mtp[1] / 2) / m)) / 3;
+ final double jw = j < 0.7d ? j : j + defaultScalingFactor * mtp[2] * (1d - j);
+ return jw;
+ }
+
+ /**
+ * This method returns the Jaro-Winkler string matches, half transpositions, prefix array.
+ *
+ * @param first the first string to be matched
+ * @param second the second string to be matched
+ * @return mtp array containing: matches, half transpositions, and prefix
+ */
+ protected static int[] matches(final CharSequence first, final CharSequence second) {
+ final CharSequence max;
+ final CharSequence min;
+ if (first.length() > second.length()) {
+ max = first;
+ min = second;
+ } else {
+ max = second;
+ min = first;
+ }
+ final int range = Math.max(max.length() / 2 - 1, 0);
+ final int[] matchIndexes = new int[min.length()];
+ Arrays.fill(matchIndexes, -1);
+ final boolean[] matchFlags = new boolean[max.length()];
+ int matches = 0;
+ for (int mi = 0; mi < min.length(); mi++) {
+ final char c1 = min.charAt(mi);
+ for (int xi = Math.max(mi - range, 0), xn = Math.min(mi + range + 1, max.length()); xi < xn; xi++) {
+ if (!matchFlags[xi] && c1 == max.charAt(xi)) {
+ matchIndexes[mi] = xi;
+ matchFlags[xi] = true;
+ matches++;
+ break;
+ }
+ }
+ }
+ final char[] ms1 = new char[matches];
+ final char[] ms2 = new char[matches];
+ for (int i = 0, si = 0; i < min.length(); i++) {
+ if (matchIndexes[i] != -1) {
+ ms1[si] = min.charAt(i);
+ si++;
+ }
+ }
+ for (int i = 0, si = 0; i < max.length(); i++) {
+ if (matchFlags[i]) {
+ ms2[si] = max.charAt(i);
+ si++;
+ }
+ }
+ int halfTranspositions = 0;
+ for (int mi = 0; mi < ms1.length; mi++) {
+ if (ms1[mi] != ms2[mi]) {
+ halfTranspositions++;
+ }
+ }
+ int prefix = 0;
+ for (int mi = 0; mi < Math.min(4, min.length()); mi++) {
+ if (first.charAt(mi) == second.charAt(mi)) {
+ prefix++;
+ } else {
+ break;
+ }
+ }
+ return new int[] {matches, halfTranspositions, prefix};
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/text/similarity/LevenshteinDetailedDistance.java b/src/main/java/org/apache/commons/text/similarity/LevenshteinDetailedDistance.java
index 00b2689996..2560d22a5a 100644
--- a/src/main/java/org/apache/commons/text/similarity/LevenshteinDetailedDistance.java
+++ b/src/main/java/org/apache/commons/text/similarity/LevenshteinDetailedDistance.java
@@ -109,7 +109,7 @@ public LevenshteinResults apply(final CharSequence left, final CharSequence righ
/**
* Gets the default instance.
*
- * @return the default instace
+ * @return The default instace
*/
public static LevenshteinDetailedDistance getDefaultInstance() {
return DEFAULT_INSTANCE;
@@ -118,7 +118,7 @@ public static LevenshteinDetailedDistance getDefaultInstance() {
/**
* Gets the distance threshold.
*
- * @return the distance threshold
+ * @return The distance threshold
*/
public Integer getThreshold() {
return threshold;
@@ -150,8 +150,8 @@ public Integer getThreshold() {
* limitedCompare("hippo", "elephant", 6) = -1
* CharSequence's left and right respectively, the runtime of the
+ * {@code CharSequence}'s {@code left} and {@code right} respectively, the runtime of the
* algorithm is O(m*n).
*
*
@@ -43,7 +43,7 @@
public class LongestCommonSubsequence implements SimilarityScoreCharSequence's passed as
+ * Calculates longest common subsequence similarity score of two {@code CharSequence}'s passed as
* input.
*
* @param left first character sequence
@@ -58,16 +58,16 @@ public Integer apply(final CharSequence left, final CharSequence right) {
if (left == null || right == null) {
throw new IllegalArgumentException("Inputs must not be null");
}
- return logestCommonSubsequence(left, right).length();
+ return longestCommonSubsequence(left, right).length();
}
/**
- * Computes the longest common subsequence between the two CharSequence's passed as input.
+ * Computes the longest common subsequence between the two {@code CharSequence}'s passed as input.
*
* abcxyzqrs and
- * xyzghfm have both the same common substring and subsequence, namely xyz. However,
- * axbyczqrs and abcxyzqtv have the longest common subsequence xyzq because a
+ * Note, a substring and subsequence are not necessarily the same thing. Indeed, {@code abcxyzqrs} and
+ * {@code xyzghfm} have both the same common substring and subsequence, namely {@code xyz}. However,
+ * {@code axbyczqrs} and {@code abcxyzqtv} have the longest common subsequence {@code xyzq} because a
* subsequence need not have adjacent characters.
* CharSequence's passed as
+ * Computes the longest common subsequence between the two {@code CharSequence}'s passed as
* input.
*
* abcxyzqrs and
- * xyzghfm have both the same common substring and subsequence, namely xyz. However,
- * axbyczqrs and abcxyzqtv have the longest common subsequence xyzq because a
+ * Note, a substring and subsequence are not necessarily the same thing. Indeed, {@code abcxyzqrs} and
+ * {@code xyzghfm} have both the same common substring and subsequence, namely {@code xyz}. However,
+ * {@code axbyczqrs} and {@code abcxyzqtv} have the longest common subsequence {@code xyzq} because a
* subsequence need not have adjacent characters.
* CharSequence's left and
- * right as: left.length() + right.length() - 2 * LCS(left, right), where
- * LCS is given in {@link LongestCommonSubsequence#apply(CharSequence, CharSequence)}.
+ * Calculates an edit distance between two {@code CharSequence}'s {@code left} and
+ * {@code right} as: {@code left.length() + right.length() - 2 * LCS(left, right)}, where
+ * {@code LCS} is given in {@link LongestCommonSubsequence#apply(CharSequence, CharSequence)}.
*
* @param left first character sequence
* @param right second character sequence
diff --git a/src/main/java/org/apache/commons/text/similarity/RegexTokenizer.java b/src/main/java/org/apache/commons/text/similarity/RegexTokenizer.java
index cc009efafa..84a424d0e6 100644
--- a/src/main/java/org/apache/commons/text/similarity/RegexTokenizer.java
+++ b/src/main/java/org/apache/commons/text/similarity/RegexTokenizer.java
@@ -21,17 +21,20 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
/**
* A simple word tokenizer that utilizes regex to find words. It applies a regex
- * {@code}(\w)+{@code} over the input text to extract words from a given character
+ * {@code (\w)+} over the input text to extract words from a given character
* sequence.
*
* @since 1.0
*/
class RegexTokenizer implements Tokenizerd: [X * X] -> [0, INFINITY) with the
+ * We Define a SimilarityScore to be a function {@code d: [X * X] -> [0, INFINITY)} with the
* following properties:
*
- *
*
* d(x,y) >= 0, non-negativity or separation axiomd(x,y) == d(y,x), symmetry.apply method
+ * The {@code apply} method
* accepts a pair of {@link CharSequence} parameters
- * and returns an R type similarity score. We have omitted the explicit
+ * and returns an {@code R} type similarity score. We have omitted the explicit
* statement of extending BiFunction due to it only being implemented in Java 1.8, and we
* wish to maintain Java 1.7 compatibility.
* String for the given
+ * String
+ * @return An upper case hexadecimal {@code String}
*/
public static String hex(final int codepoint) {
return Integer.toHexString(codepoint).toUpperCase(Locale.ENGLISH);
diff --git a/src/main/java/org/apache/commons/text/translate/CodePointTranslator.java b/src/main/java/org/apache/commons/text/translate/CodePointTranslator.java
index 71828be069..d225aacaac 100644
--- a/src/main/java/org/apache/commons/text/translate/CodePointTranslator.java
+++ b/src/main/java/org/apache/commons/text/translate/CodePointTranslator.java
@@ -32,9 +32,9 @@ public abstract class CodePointTranslator extends CharSequenceTranslator {
* {@inheritDoc}
*/
@Override
- public final int translate(final CharSequence input, final int index, final Writer out) throws IOException {
+ public final int translate(final CharSequence input, final int index, final Writer writer) throws IOException {
final int codepoint = Character.codePointAt(input, index);
- final boolean consumed = translate(codepoint, out);
+ final boolean consumed = translate(codepoint, writer);
return consumed ? 1 : 0;
}
@@ -42,10 +42,10 @@ public final int translate(final CharSequence input, final int index, final Writ
* Translate the specified codepoint into another.
*
* @param codepoint int character input to translate
- * @param out Writer to optionally push the translated output to
+ * @param writer Writer to optionally push the translated output to
* @return boolean as to whether translation occurred or not
* @throws IOException if and only if the Writer produces an IOException
*/
- public abstract boolean translate(int codepoint, Writer out) throws IOException;
+ public abstract boolean translate(int codepoint, Writer writer) throws IOException;
}
diff --git a/src/main/java/org/apache/commons/text/translate/CsvTranslators.java b/src/main/java/org/apache/commons/text/translate/CsvTranslators.java
index 2987eadda9..d192b4d9ee 100644
--- a/src/main/java/org/apache/commons/text/translate/CsvTranslators.java
+++ b/src/main/java/org/apache/commons/text/translate/CsvTranslators.java
@@ -21,13 +21,15 @@
import org.apache.commons.lang3.CharUtils;
import org.apache.commons.lang3.StringUtils;
+
/**
- * java.util.Collections.unmodifiableMap().
+ * All Maps are generated using {@code java.util.Collections.unmodifiableMap()}.
*
* @since 1.0
*/
@@ -434,9 +433,7 @@ public class EntityArrays {
*/
public static MapJavaUnicodeEscaper above the specified value (exclusive).
+ * Constructs a {@code JavaUnicodeEscaper} above the specified value (exclusive).
* JavaUnicodeEscaper below the specified value (exclusive).
+ * Constructs a {@code JavaUnicodeEscaper} below the specified value (exclusive).
* JavaUnicodeEscaper between the specified values (inclusive).
+ * Constructs a {@code JavaUnicodeEscaper} between the specified values (inclusive).
* JavaUnicodeEscaper outside of the specified values (exclusive).
+ * Constructs a {@code JavaUnicodeEscaper} outside of the specified values (exclusive).
* JavaUnicodeEscaper for the specified range. This is the underlying method for the
- * other constructors/builders. The below and above boundaries are inclusive when
- * between is true and exclusive when it is false.
+ * Constructs a {@code JavaUnicodeEscaper} for the specified range. This is the underlying method for the
+ * other constructors/builders. The {@code below} and {@code above} boundaries are inclusive when
+ * {@code between} is {@code true} and exclusive when it is {@code false}.
* NumericEntityEscaper for the specified range. This is
- * the underlying method for the other constructors/builders. The below
- * and above boundaries are inclusive when between is
- * true and exclusive when it is false. NumericEntityEscaper for all characters. NumericEntityEscaper below the specified value (exclusive). NumericEntityEscaper above the specified value (exclusive). NumericEntityEscaper between the specified values (inclusive). NumericEntityEscaper outside of the specified values (exclusive).