This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-lang.git
The following commit(s) were added to refs/heads/master by this push: new 6941f81cd Add Strings and refactor StringUtils 6941f81cd is described below commit 6941f81cd321a61217a687c0077fcd0d0a3044a2 Author: Gary Gregory <garydgreg...@gmail.com> AuthorDate: Sat Sep 21 10:35:43 2024 -0400 Add Strings and refactor StringUtils --- src/changes/changes.xml | 3 +- src/conf/spotbugs-exclude-filter.xml | 10 + .../apache/commons/lang3/CharSequenceUtils.java | 9 +- .../java/org/apache/commons/lang3/StringUtils.java | 486 +++------- .../java/org/apache/commons/lang3/Strings.java | 976 +++++++++++++++++++++ .../commons/lang3/builder/ToStringStyle.java | 3 +- .../lang3/exception/DefaultExceptionContext.java | 8 +- .../org/apache/commons/lang3/text/StrBuilder.java | 3 +- .../java/org/apache/commons/lang3/StringsTest.java | 57 ++ 9 files changed, 1183 insertions(+), 372 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index e98200ee3..3de1ad13c 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -50,8 +50,9 @@ The <action> type attribute can be add,update,fix,remove. <action type="fix" dev="ggregory" due-to="Gary Gregory">Fix flaky FileUtilsWaitForTest.testWaitForNegativeDuration().</action> <action type="fix" dev="ggregory" due-to="Gary Gregory">Pick up exec-maven-plugin version from parent POM.</action> <action type="fix" dev="ggregory" due-to="Gary Gregory">Speed up and sanitize StopWatchTest.</action> - <action type="fix" dev="ggregory" due-to="Fabrice Benhamouda">Fix handling of non-ASCII letters & numbers in RandomStringUtils #1273.</action> + <action type="fix" dev="ggregory" due-to="Fabrice Benhamouda">Fix handling of non-ASCII letters and numbers in RandomStringUtils #1273.</action> <!-- ADD --> + <action type="add" dev="ggregory" due-to="Gary Gregory">Add Strings and refactor StringUtils.</action> <!-- UPDATE --> <action type="update" dev="ggregory" due-to="Gary Gregory, Dependabot">Bump org.apache.commons:commons-parent from 73 to 75 #1267, #1277.</action> </release> diff --git a/src/conf/spotbugs-exclude-filter.xml b/src/conf/spotbugs-exclude-filter.xml index d935cb51d..3a89b82d5 100644 --- a/src/conf/spotbugs-exclude-filter.xml +++ b/src/conf/spotbugs-exclude-filter.xml @@ -113,6 +113,16 @@ </Or> <Bug pattern="ES_COMPARING_PARAMETER_STRING_WITH_EQ" /> </Match> + <Match> + <Class name="org.apache.commons.lang3.Strings$CiStrings" /> + <Method name="compare" params="java.lang.String,java.lang.String"/> + <Bug pattern="ES_COMPARING_PARAMETER_STRING_WITH_EQ" /> + </Match> + <Match> + <Class name="org.apache.commons.lang3.Strings$CsStrings" /> + <Method name="compare" params="java.lang.String,java.lang.String"/> + <Bug pattern="ES_COMPARING_PARAMETER_STRING_WITH_EQ" /> + </Match> <!-- Reason: Very much intended to do a fall through on the switch --> <Match> diff --git a/src/main/java/org/apache/commons/lang3/CharSequenceUtils.java b/src/main/java/org/apache/commons/lang3/CharSequenceUtils.java index f34eb2a15..ea6431115 100644 --- a/src/main/java/org/apache/commons/lang3/CharSequenceUtils.java +++ b/src/main/java/org/apache/commons/lang3/CharSequenceUtils.java @@ -41,12 +41,15 @@ public class CharSequenceUtils { /** * Used by the indexOf(CharSequence methods) as a green implementation of indexOf. * - * @param cs the {@link CharSequence} to be processed + * @param cs the {@link CharSequence} to be processed * @param searchChar the {@link CharSequence} to be searched for - * @param start the start index - * @return the index where the search sequence was found + * @param start the start index + * @return the index where the search sequence was found, or {@code -1} if there is no such occurrence. */ static int indexOf(final CharSequence cs, final CharSequence searchChar, final int start) { + if (cs == null || searchChar == null) { + return StringUtils.INDEX_NOT_FOUND; + } if (cs instanceof String) { return ((String) cs).indexOf(searchChar.toString(), start); } diff --git a/src/main/java/org/apache/commons/lang3/StringUtils.java b/src/main/java/org/apache/commons/lang3/StringUtils.java index c69d0526d..0f16d63f3 100644 --- a/src/main/java/org/apache/commons/lang3/StringUtils.java +++ b/src/main/java/org/apache/commons/lang3/StringUtils.java @@ -31,7 +31,6 @@ import java.util.function.Supplier; import java.util.regex.Pattern; import org.apache.commons.lang3.function.Suppliers; -import org.apache.commons.lang3.function.ToBooleanBiFunction; import org.apache.commons.lang3.stream.LangCollectors; import org.apache.commons.lang3.stream.Streams; @@ -413,33 +412,7 @@ public class StringUtils { } /** - * Appends the suffix to the end of the string if the string does not - * already end with the suffix. - * - * @param str The string. - * @param suffix The suffix to append to the end of the string. - * @param ignoreCase Indicates whether the compare should ignore case. - * @param suffixes Additional suffixes that are valid terminators (optional). - * - * @return A new String if suffix was appended, the same string otherwise. - */ - private static String appendIfMissing(final String str, final CharSequence suffix, final boolean ignoreCase, final CharSequence... suffixes) { - if (str == null || isEmpty(suffix) || endsWith(str, suffix, ignoreCase)) { - return str; - } - if (ArrayUtils.isNotEmpty(suffixes)) { - for (final CharSequence s : suffixes) { - if (endsWith(str, s, ignoreCase)) { - return str; - } - } - } - return str + suffix; - } - - /** - * Appends the suffix to the end of the string if the string does not - * already end with any of the suffixes. + * Appends the suffix to the end of the string if the string does not already end with any of the suffixes. * * <pre> * StringUtils.appendIfMissing(null, null) = null @@ -449,7 +422,10 @@ public class StringUtils { * StringUtils.appendIfMissing("abcxyz", "xyz") = "abcxyz" * StringUtils.appendIfMissing("abcXYZ", "xyz") = "abcXYZxyz" * </pre> - * <p>With additional suffixes,</p> + * <p> + * With additional suffixes, + * </p> + * * <pre> * StringUtils.appendIfMissing(null, null, null) = null * StringUtils.appendIfMissing("abc", null, null) = "abc" @@ -463,16 +439,16 @@ public class StringUtils { * StringUtils.appendIfMissing("abcMNO", "xyz", "mno") = "abcMNOxyz" * </pre> * - * @param str The string. - * @param suffix The suffix to append to the end of the string. + * @param str The string. + * @param suffix The suffix to append to the end of the string. * @param suffixes Additional suffixes that are valid terminators. - * * @return A new String if suffix was appended, the same string otherwise. - * * @since 3.2 + * @deprecated Use {@link Strings#appendIfMissing(String, CharSequence, CharSequence...) Strings.CS.appendIfMissing(String, CharSequence, CharSequence...)} */ + @Deprecated public static String appendIfMissing(final String str, final CharSequence suffix, final CharSequence... suffixes) { - return appendIfMissing(str, suffix, false, suffixes); + return Strings.CS.appendIfMissing(str, suffix, suffixes); } /** @@ -504,13 +480,13 @@ public class StringUtils { * @param str The string. * @param suffix The suffix to append to the end of the string. * @param suffixes Additional suffixes that are valid terminators. - * * @return A new String if suffix was appended, the same string otherwise. - * * @since 3.2 + * @deprecated Use {@link Strings#appendIfMissing(String, CharSequence, CharSequence...) Strings.CI.appendIfMissing(String, CharSequence, CharSequence...)} */ + @Deprecated public static String appendIfMissingIgnoreCase(final String str, final CharSequence suffix, final CharSequence... suffixes) { - return appendIfMissing(str, suffix, true, suffixes); + return Strings.CI.appendIfMissing(str, suffix, suffixes); } /** @@ -818,9 +794,11 @@ public class StringUtils { * @param str2 the String to compare to * @return < 0, 0, > 0, if {@code str1} is respectively less, equal or greater than {@code str2} * @since 3.5 + * @deprecated Use {@link Strings#compare(String, String) Strings.CS.compare(String, String)} */ + @Deprecated public static int compare(final String str1, final String str2) { - return compare(str1, str2, true); + return Strings.CS.compare(str1, str2); } /** @@ -906,9 +884,11 @@ public class StringUtils { * @return < 0, 0, > 0, if {@code str1} is respectively less, equal ou greater than {@code str2}, * ignoring case differences. * @since 3.5 + * @deprecated Use {@link Strings#compare(String, String) Strings.CI.compare(String, String)} */ + @Deprecated public static int compareIgnoreCase(final String str1, final String str2) { - return compareIgnoreCase(str1, str2, true); + return Strings.CI.compare(str1, str2); } /** @@ -984,12 +964,11 @@ public class StringUtils { * false if not or {@code null} string input * @since 2.0 * @since 3.0 Changed signature from contains(String, String) to contains(CharSequence, CharSequence) + * @deprecated Use {@link Strings#contains(CharSequence, CharSequence) Strings.CS.contains(CharSequence, CharSequence)} */ + @Deprecated public static boolean contains(final CharSequence seq, final CharSequence searchSeq) { - if (seq == null || searchSeq == null) { - return false; - } - return CharSequenceUtils.indexOf(seq, searchSeq, 0) >= 0; + return Strings.CS.contains(seq, searchSeq); } /** @@ -1132,36 +1111,11 @@ public class StringUtils { * null as well. * @return {@code true} if any of the search CharSequences are found, {@code false} otherwise * @since 3.4 + * @deprecated Use {@link Strings#containsAny(CharSequence, CharSequence...) Strings.CS.containsAny(CharSequence, CharSequence...)} */ + @Deprecated public static boolean containsAny(final CharSequence cs, final CharSequence... searchCharSequences) { - return containsAny(StringUtils::contains, cs, searchCharSequences); - } - - /** - * Tests if the CharSequence contains any of the CharSequences in the given array. - * - * <p> - * A {@code null} {@code cs} CharSequence will return {@code false}. A {@code null} or zero length search array will - * return {@code false}. - * </p> - * - * @param cs The CharSequence to check, may be null - * @param searchCharSequences The array of CharSequences to search for, may be null. Individual CharSequences may be - * null as well. - * @return {@code true} if any of the search CharSequences are found, {@code false} otherwise - * @since 3.12.0 - */ - private static boolean containsAny(final ToBooleanBiFunction<CharSequence, CharSequence> test, - final CharSequence cs, final CharSequence... searchCharSequences) { - if (isEmpty(cs) || ArrayUtils.isEmpty(searchCharSequences)) { - return false; - } - for (final CharSequence searchCharSequence : searchCharSequences) { - if (test.applyAsBoolean(cs, searchCharSequence)) { - return true; - } - } - return false; + return Strings.CS.containsAny(cs, searchCharSequences); } /** @@ -1189,9 +1143,11 @@ public class StringUtils { * null as well. * @return {@code true} if any of the search CharSequences are found, {@code false} otherwise * @since 3.12.0 + * @deprecated Use {@link Strings#containsAny(CharSequence, CharSequence...) Strings.CI.containsAny(CharSequence, CharSequence...)} */ + @Deprecated public static boolean containsAnyIgnoreCase(final CharSequence cs, final CharSequence... searchCharSequences) { - return containsAny(StringUtils::containsIgnoreCase, cs, searchCharSequences); + return Strings.CI.containsAny(cs, searchCharSequences); } /** @@ -1217,19 +1173,11 @@ public class StringUtils { * @return true if the CharSequence contains the search CharSequence irrespective of * case or false if not or {@code null} string input * @since 3.0 Changed signature from containsIgnoreCase(String, String) to containsIgnoreCase(CharSequence, CharSequence) + * @deprecated Use {@link Strings#contains(CharSequence, CharSequence) Strings.CI.contains(CharSequence, CharSequence)} */ + @Deprecated public static boolean containsIgnoreCase(final CharSequence str, final CharSequence searchStr) { - if (str == null || searchStr == null) { - return false; - } - final int len = searchStr.length(); - final int max = str.length() - len; - for (int i = 0; i <= max; i++) { - if (CharSequenceUtils.regionMatches(str, true, i, searchStr, 0, len)) { - return true; - } - } - return false; + return Strings.CI.contains(str, searchStr); } /** @@ -1736,31 +1684,11 @@ public class StringUtils { * both {@code null} * @since 2.4 * @since 3.0 Changed signature from endsWith(String, String) to endsWith(CharSequence, CharSequence) + * @deprecated Use {@link Strings#endsWith(CharSequence, CharSequence) Strings.CS.endsWith(CharSequence, CharSequence)} */ + @Deprecated public static boolean endsWith(final CharSequence str, final CharSequence suffix) { - return endsWith(str, suffix, false); - } - - /** - * Tests if a CharSequence ends with a specified suffix (optionally case-insensitive). - * - * @see String#endsWith(String) - * @param str the CharSequence to check, may be null - * @param suffix the suffix to find, may be null - * @param ignoreCase indicates whether the compare should ignore case - * (case-insensitive) or not. - * @return {@code true} if the CharSequence starts with the prefix or - * both {@code null} - */ - private static boolean endsWith(final CharSequence str, final CharSequence suffix, final boolean ignoreCase) { - if (str == null || suffix == null) { - return str == suffix; - } - if (suffix.length() > str.length()) { - return false; - } - final int strOffset = str.length() - suffix.length(); - return CharSequenceUtils.regionMatches(str, ignoreCase, strOffset, suffix, 0, suffix.length()); + return Strings.CS.endsWith(str, suffix); } /** @@ -1818,9 +1746,11 @@ public class StringUtils { * both {@code null} * @since 2.4 * @since 3.0 Changed signature from endsWithIgnoreCase(String, String) to endsWithIgnoreCase(CharSequence, CharSequence) + * @deprecated Use {@link Strings#endsWith(CharSequence, CharSequence) Strings.CS.endsWith(CharSequence, CharSequence)} */ + @Deprecated public static boolean endsWithIgnoreCase(final CharSequence str, final CharSequence suffix) { - return endsWith(str, suffix, true); + return Strings.CI.endsWith(str, suffix); } /** @@ -1844,28 +1774,11 @@ public class StringUtils { * @since 3.0 Changed signature from equals(String, String) to equals(CharSequence, CharSequence) * @see Object#equals(Object) * @see #equalsIgnoreCase(CharSequence, CharSequence) + * @deprecated Use {@link Strings#equals(CharSequence, CharSequence) Strings.CS.equals(CharSequence, CharSequence)} */ + @Deprecated public static boolean equals(final CharSequence cs1, final CharSequence cs2) { - if (cs1 == cs2) { - return true; - } - if (cs1 == null || cs2 == null) { - return false; - } - if (cs1.length() != cs2.length()) { - return false; - } - if (cs1 instanceof String && cs2 instanceof String) { - return cs1.equals(cs2); - } - // Step-wise comparison - final int length = cs1.length(); - for (int i = 0; i < length; i++) { - if (cs1.charAt(i) != cs2.charAt(i)) { - return false; - } - } - return true; + return Strings.CS.equals(cs1, cs2); } /** @@ -1886,16 +1799,11 @@ public class StringUtils { * @return {@code true} if the string is equal (case-sensitive) to any other element of {@code searchStrings}; * {@code false} if {@code searchStrings} is null or contains no matches. * @since 3.5 + * @deprecated Use {@link Strings#equalsAny(CharSequence, CharSequence...) Strings.CS.equalsAny(CharSequence, CharSequence...)} */ + @Deprecated public static boolean equalsAny(final CharSequence string, final CharSequence... searchStrings) { - if (ArrayUtils.isNotEmpty(searchStrings)) { - for (final CharSequence next : searchStrings) { - if (equals(string, next)) { - return true; - } - } - } - return false; + return Strings.CS.equalsAny(string, searchStrings); } /** @@ -1916,16 +1824,11 @@ public class StringUtils { * @return {@code true} if the string is equal (case-insensitive) to any other element of {@code searchStrings}; * {@code false} if {@code searchStrings} is null or contains no matches. * @since 3.5 + * @deprecated Use {@link Strings#equalsAny(CharSequence, CharSequence...) Strings.CI-.equalsAny(CharSequence, CharSequence...)} */ + @Deprecated public static boolean equalsAnyIgnoreCase(final CharSequence string, final CharSequence... searchStrings) { - if (ArrayUtils.isNotEmpty(searchStrings)) { - for (final CharSequence next : searchStrings) { - if (equalsIgnoreCase(string, next)) { - return true; - } - } - } - return false; + return Strings.CI.equalsAny(string, searchStrings); } /** @@ -1948,18 +1851,11 @@ public class StringUtils { * @return {@code true} if the CharSequences are equal (case-insensitive), or both {@code null} * @since 3.0 Changed signature from equalsIgnoreCase(String, String) to equalsIgnoreCase(CharSequence, CharSequence) * @see #equals(CharSequence, CharSequence) + * @deprecated Use {@link Strings#equals(CharSequence, CharSequence) Strings.CI.equals(CharSequence, CharSequence)} */ + @Deprecated public static boolean equalsIgnoreCase(final CharSequence cs1, final CharSequence cs2) { - if (cs1 == cs2) { - return true; - } - if (cs1 == null || cs2 == null) { - return false; - } - if (cs1.length() != cs2.length()) { - return false; - } - return CharSequenceUtils.regionMatches(cs1, true, 0, cs2, 0, cs1.length()); + return Strings.CI.equals(cs1, cs2); } /** @@ -2629,12 +2525,11 @@ public class StringUtils { * -1 if no match or {@code null} string input * @since 2.0 * @since 3.0 Changed signature from indexOf(String, String) to indexOf(CharSequence, CharSequence) + * @deprecated Use {@link Strings#indexOf(CharSequence, CharSequence) Strings.CS.indexOf(CharSequence, CharSequence)} */ + @Deprecated public static int indexOf(final CharSequence seq, final CharSequence searchSeq) { - if (seq == null || searchSeq == null) { - return INDEX_NOT_FOUND; - } - return CharSequenceUtils.indexOf(seq, searchSeq, 0); + return Strings.CS.indexOf(seq, searchSeq, 0); } /** @@ -2669,12 +2564,11 @@ public class StringUtils { * -1 if no match or {@code null} string input * @since 2.0 * @since 3.0 Changed signature from indexOf(String, String, int) to indexOf(CharSequence, CharSequence, int) + * @deprecated Use {@link Strings#indexOf(CharSequence, CharSequence) Strings.CS.indexOf(CharSequence, CharSequence)} */ + @Deprecated public static int indexOf(final CharSequence seq, final CharSequence searchSeq, final int startPos) { - if (seq == null || searchSeq == null) { - return INDEX_NOT_FOUND; - } - return CharSequenceUtils.indexOf(seq, searchSeq, startPos); + return Strings.CS.indexOf(seq, searchSeq, startPos); } /** @@ -3163,9 +3057,11 @@ public class StringUtils { * -1 if no match or {@code null} string input * @since 2.5 * @since 3.0 Changed signature from indexOfIgnoreCase(String, String) to indexOfIgnoreCase(CharSequence, CharSequence) + * @deprecated Use {@link Strings#indexOf(CharSequence, CharSequence) Strings.CI.indexOf(CharSequence, CharSequence)} */ + @Deprecated public static int indexOfIgnoreCase(final CharSequence str, final CharSequence searchStr) { - return indexOfIgnoreCase(str, searchStr, 0); + return Strings.CI.indexOf(str, searchStr, 0); } /** @@ -3199,27 +3095,11 @@ public class StringUtils { * -1 if no match or {@code null} string input * @since 2.5 * @since 3.0 Changed signature from indexOfIgnoreCase(String, String, int) to indexOfIgnoreCase(CharSequence, CharSequence, int) + * @deprecated Use {@link Strings#indexOf(CharSequence, CharSequence, int) Strings.CI.indexOf(CharSequence, CharSequence, int)} */ + @Deprecated public static int indexOfIgnoreCase(final CharSequence str, final CharSequence searchStr, int startPos) { - if (str == null || searchStr == null) { - return INDEX_NOT_FOUND; - } - if (startPos < 0) { - startPos = 0; - } - final int endLimit = str.length() - searchStr.length() + 1; - if (startPos > endLimit) { - return INDEX_NOT_FOUND; - } - if (searchStr.length() == 0) { - return startPos; - } - for (int i = startPos; i < endLimit; i++) { - if (CharSequenceUtils.regionMatches(str, true, i, searchStr, 0, searchStr.length())) { - return i; - } - } - return INDEX_NOT_FOUND; + return Strings.CI.indexOf(str, searchStr, startPos); } /** @@ -4873,12 +4753,11 @@ public class StringUtils { * -1 if no match or {@code null} string input * @since 2.0 * @since 3.0 Changed signature from lastIndexOf(String, String) to lastIndexOf(CharSequence, CharSequence) + * @deprecated Use {@link Strings#lastIndexOf(CharSequence, CharSequence) Strings.CS.lastIndexOf(CharSequence, CharSequence)} */ + @Deprecated public static int lastIndexOf(final CharSequence seq, final CharSequence searchSeq) { - if (seq == null) { - return INDEX_NOT_FOUND; - } - return CharSequenceUtils.lastIndexOf(seq, searchSeq, seq.length()); + return Strings.CS.lastIndexOf(seq, searchSeq); } /** @@ -4915,9 +4794,11 @@ public class StringUtils { * -1 if no match or {@code null} string input * @since 2.0 * @since 3.0 Changed signature from lastIndexOf(String, String, int) to lastIndexOf(CharSequence, CharSequence, int) + * @deprecated Use {@link Strings#lastIndexOf(CharSequence, CharSequence, int) Strings.CS.lastIndexOf(CharSequence, CharSequence, int)} */ + @Deprecated public static int lastIndexOf(final CharSequence seq, final CharSequence searchSeq, final int startPos) { - return CharSequenceUtils.lastIndexOf(seq, searchSeq, startPos); + return Strings.CS.lastIndexOf(seq, searchSeq, startPos); } /** @@ -5078,12 +4959,11 @@ public class StringUtils { * -1 if no match or {@code null} string input * @since 2.5 * @since 3.0 Changed signature from lastIndexOfIgnoreCase(String, String) to lastIndexOfIgnoreCase(CharSequence, CharSequence) + * @deprecated Use {@link Strings#lastIndexOf(CharSequence, CharSequence) Strings.CI.lastIndexOf(CharSequence, CharSequence)} */ + @Deprecated public static int lastIndexOfIgnoreCase(final CharSequence str, final CharSequence searchStr) { - if (str == null || searchStr == null) { - return INDEX_NOT_FOUND; - } - return lastIndexOfIgnoreCase(str, searchStr, str.length()); + return Strings.CI.lastIndexOf(str, searchStr); } /** @@ -5117,29 +4997,11 @@ public class StringUtils { * -1 if no match or {@code null} input * @since 2.5 * @since 3.0 Changed signature from lastIndexOfIgnoreCase(String, String, int) to lastIndexOfIgnoreCase(CharSequence, CharSequence, int) + * @deprecated Use {@link Strings#lastIndexOf(CharSequence, CharSequence, int) Strings.CI.lastIndexOf(CharSequence, CharSequence, int)} */ + @Deprecated public static int lastIndexOfIgnoreCase(final CharSequence str, final CharSequence searchStr, int startPos) { - if (str == null || searchStr == null) { - return INDEX_NOT_FOUND; - } - final int searchStrLength = searchStr.length(); - final int strLength = str.length(); - if (startPos > strLength - searchStrLength) { - startPos = strLength - searchStrLength; - } - if (startPos < 0) { - return INDEX_NOT_FOUND; - } - if (searchStrLength == 0) { - return startPos; - } - - for (int i = startPos; i >= 0; i--) { - if (CharSequenceUtils.regionMatches(str, true, i, searchStr, 0, searchStrLength)) { - return i; - } - } - return INDEX_NOT_FOUND; + return Strings.CI.lastIndexOf(str, searchStr, startPos); } /** @@ -5710,33 +5572,7 @@ public class StringUtils { } /** - * Prepends the prefix to the start of the string if the string does not - * already start with any of the prefixes. - * - * @param str The string. - * @param prefix The prefix to prepend to the start of the string. - * @param ignoreCase Indicates whether the compare should ignore case. - * @param prefixes Additional prefixes that are valid (optional). - * - * @return A new String if prefix was prepended, the same string otherwise. - */ - private static String prependIfMissing(final String str, final CharSequence prefix, final boolean ignoreCase, final CharSequence... prefixes) { - if (str == null || isEmpty(prefix) || startsWith(str, prefix, ignoreCase)) { - return str; - } - if (ArrayUtils.isNotEmpty(prefixes)) { - for (final CharSequence p : prefixes) { - if (startsWith(str, p, ignoreCase)) { - return str; - } - } - } - return prefix + str; - } - - /** - * Prepends the prefix to the start of the string if the string does not - * already start with any of the prefixes. + * Prepends the prefix to the start of the string if the string does not already start with any of the prefixes. * * <pre> * StringUtils.prependIfMissing(null, null) = null @@ -5746,7 +5582,10 @@ public class StringUtils { * StringUtils.prependIfMissing("xyzabc", "xyz") = "xyzabc" * StringUtils.prependIfMissing("XYZabc", "xyz") = "xyzXYZabc" * </pre> - * <p>With additional prefixes,</p> + * <p> + * With additional prefixes, + * </p> + * * <pre> * StringUtils.prependIfMissing(null, null, null) = null * StringUtils.prependIfMissing("abc", null, null) = "abc" @@ -5760,16 +5599,17 @@ public class StringUtils { * StringUtils.prependIfMissing("MNOabc", "xyz", "mno") = "xyzMNOabc" * </pre> * - * @param str The string. - * @param prefix The prefix to prepend to the start of the string. + * @param str The string. + * @param prefix The prefix to prepend to the start of the string. * @param prefixes Additional prefixes that are valid. - * * @return A new String if prefix was prepended, the same string otherwise. - * * @since 3.2 + * @deprecated Use {@link Strings#prependIfMissing(String, CharSequence, CharSequence...) Strings.CS.prependIfMissing(String, CharSequence, + * CharSequence...)} */ + @Deprecated public static String prependIfMissing(final String str, final CharSequence prefix, final CharSequence... prefixes) { - return prependIfMissing(str, prefix, false, prefixes); + return Strings.CS.prependIfMissing(str, prefix, prefixes); } /** @@ -5801,13 +5641,14 @@ public class StringUtils { * @param str The string. * @param prefix The prefix to prepend to the start of the string. * @param prefixes Additional prefixes that are valid (optional). - * * @return A new String if prefix was prepended, the same string otherwise. - * * @since 3.2 + * @deprecated Use {@link Strings#prependIfMissing(String, CharSequence, CharSequence...) Strings.CI.prependIfMissing(String, CharSequence, + * CharSequence...)} */ + @Deprecated public static String prependIfMissingIgnoreCase(final String str, final CharSequence prefix, final CharSequence... prefixes) { - return prependIfMissing(str, prefix, true, prefixes); + return Strings.CI.prependIfMissing(str, prefix, prefixes); } /** @@ -5946,15 +5787,11 @@ public class StringUtils { * @return the substring with the string removed if found, * {@code null} if null String input * @since 2.1 + * @deprecated Use {@link Strings#removeEnd(String, CharSequence) Strings.CS.removeEnd(String, CharSequence)} */ + @Deprecated public static String removeEnd(final String str, final String remove) { - if (isEmpty(str) || isEmpty(remove)) { - return str; - } - if (str.endsWith(remove)) { - return str.substring(0, str.length() - remove.length()); - } - return str; + return Strings.CS.removeEnd(str, remove); } /** @@ -5982,15 +5819,11 @@ public class StringUtils { * @return the substring with the string removed if found, * {@code null} if null String input * @since 2.4 + * @deprecated Use {@link Strings#removeEnd(String, CharSequence) Strings.CI.removeEnd(String, CharSequence)} */ + @Deprecated public static String removeEndIgnoreCase(final String str, final String remove) { - if (isEmpty(str) || isEmpty(remove)) { - return str; - } - if (endsWithIgnoreCase(str, remove)) { - return str.substring(0, str.length() - remove.length()); - } - return str; + return Strings.CI.removeEnd(str, remove); } /** @@ -6166,15 +5999,11 @@ public class StringUtils { * @return the substring with the string removed if found, * {@code null} if null String input * @since 2.1 + * @deprecated Use {@link Strings#removeStart(String, CharSequence) Strings.CS.removeStart(String, CharSequence)} */ + @Deprecated public static String removeStart(final String str, final String remove) { - if (isEmpty(str) || isEmpty(remove)) { - return str; - } - if (str.startsWith(remove)) { - return str.substring(remove.length()); - } - return str; + return Strings.CS.removeStart(str, remove); } /** @@ -6201,12 +6030,11 @@ public class StringUtils { * @return the substring with the string removed if found, * {@code null} if null String input * @since 2.4 + * @deprecated Use {@link Strings#removeStart(String, CharSequence) Strings.CI.removeStart(String, CharSequence)} */ + @Deprecated public static String removeStartIgnoreCase(final String str, final String remove) { - if (str != null && startsWithIgnoreCase(str, remove)) { - return str.substring(length(remove)); - } - return str; + return Strings.CI.removeStart(str, remove); } /** @@ -6345,9 +6173,11 @@ public class StringUtils { * @param replacement the String to replace it with, may be null * @return the text with any replacements processed, * {@code null} if null String input + * @deprecated Use {@link Strings#replace(String, String, String) Strings.CS.replace(String, String, String)} */ + @Deprecated public static String replace(final String text, final String searchString, final String replacement) { - return replace(text, searchString, replacement, -1); + return Strings.CS.replace(text, searchString, replacement); } /** @@ -6377,70 +6207,13 @@ public class StringUtils { * @param max maximum number of values to replace, or {@code -1} if no maximum * @return the text with any replacements processed, * {@code null} if null String input + * @deprecated Use {@link Strings#replace(String, String, String, int) Strings.CS.replace(String, String, String, int)} */ + @Deprecated public static String replace(final String text, final String searchString, final String replacement, final int max) { - return replace(text, searchString, replacement, max, false); + return Strings.CS.replace(text, searchString, replacement, max); } - /** - * Replaces a String with another String inside a larger String, - * for the first {@code max} values of the search String, - * case-sensitively/insensitively based on {@code ignoreCase} value. - * - * <p>A {@code null} reference passed to this method is a no-op.</p> - * - * <pre> - * StringUtils.replace(null, *, *, *, false) = null - * StringUtils.replace("", *, *, *, false) = "" - * StringUtils.replace("any", null, *, *, false) = "any" - * StringUtils.replace("any", *, null, *, false) = "any" - * StringUtils.replace("any", "", *, *, false) = "any" - * StringUtils.replace("any", *, *, 0, false) = "any" - * StringUtils.replace("abaa", "a", null, -1, false) = "abaa" - * StringUtils.replace("abaa", "a", "", -1, false) = "b" - * StringUtils.replace("abaa", "a", "z", 0, false) = "abaa" - * StringUtils.replace("abaa", "A", "z", 1, false) = "abaa" - * StringUtils.replace("abaa", "A", "z", 1, true) = "zbaa" - * StringUtils.replace("abAa", "a", "z", 2, true) = "zbza" - * StringUtils.replace("abAa", "a", "z", -1, true) = "zbzz" - * </pre> - * - * @param text text to search and replace in, may be null - * @param searchString the String to search for (case-insensitive), may be null - * @param replacement the String to replace it with, may be null - * @param max maximum number of values to replace, or {@code -1} if no maximum - * @param ignoreCase if true replace is case-insensitive, otherwise case-sensitive - * @return the text with any replacements processed, - * {@code null} if null String input - */ - private static String replace(final String text, String searchString, final String replacement, int max, final boolean ignoreCase) { - if (isEmpty(text) || isEmpty(searchString) || replacement == null || max == 0) { - return text; - } - if (ignoreCase) { - searchString = searchString.toLowerCase(); - } - int start = 0; - int end = ignoreCase ? indexOfIgnoreCase(text, searchString, start) : indexOf(text, searchString, start); - if (end == INDEX_NOT_FOUND) { - return text; - } - final int replLength = searchString.length(); - int increase = Math.max(replacement.length() - replLength, 0); - increase *= max < 0 ? 16 : Math.min(max, 64); - final StringBuilder buf = new StringBuilder(text.length() + increase); - while (end != INDEX_NOT_FOUND) { - buf.append(text, start, end).append(replacement); - start = end + replLength; - if (--max == 0) { - break; - } - end = ignoreCase ? indexOfIgnoreCase(text, searchString, start) : indexOf(text, searchString, start); - } - buf.append(text, start, text.length()); - return buf.toString(); - } - /** * Replaces each substring of the text String that matches the given regular expression * with the given replacement. @@ -6922,9 +6695,11 @@ public class StringUtils { * @return the text with any replacements processed, * {@code null} if null String input * @since 3.5 + * @deprecated Use {@link Strings#replace(String, String, String) Strings.CI.replace(String, String, String)} */ + @Deprecated public static String replaceIgnoreCase(final String text, final String searchString, final String replacement) { - return replaceIgnoreCase(text, searchString, replacement, -1); + return Strings.CI.replace(text, searchString, replacement); } /** @@ -6955,9 +6730,11 @@ public class StringUtils { * @return the text with any replacements processed, * {@code null} if null String input * @since 3.5 + * @deprecated Use {@link Strings#replace(String, String, String, int) Strings.CI.replace(String, String, String, int)} */ + @Deprecated public static String replaceIgnoreCase(final String text, final String searchString, final String replacement, final int max) { - return replace(text, searchString, replacement, max, true); + return Strings.CI.replace(text, searchString, replacement, max); } /** @@ -6982,9 +6759,11 @@ public class StringUtils { * @param replacement the String to replace with, may be null * @return the text with any replacements processed, * {@code null} if null String input + * @deprecated Use {@link Strings#replaceOnce(String, String, String) Strings.CS.replaceOnce(String, String, String)} */ + @Deprecated public static String replaceOnce(final String text, final String searchString, final String replacement) { - return replace(text, searchString, replacement, 1); + return Strings.CS.replaceOnce(text, searchString, replacement); } /** @@ -7011,9 +6790,11 @@ public class StringUtils { * @return the text with any replacements processed, * {@code null} if null String input * @since 3.5 + * @deprecated Use {@link Strings#replaceOnce(String, String, String) Strings.CI.replaceOnce(String, String, String)} */ + @Deprecated public static String replaceOnceIgnoreCase(final String text, final String searchString, final String replacement) { - return replaceIgnoreCase(text, searchString, replacement, 1); + return Strings.CI.replaceOnce(text, searchString, replacement); } /** @@ -8018,32 +7799,11 @@ public class StringUtils { * both {@code null} * @since 2.4 * @since 3.0 Changed signature from startsWith(String, String) to startsWith(CharSequence, CharSequence) + * @deprecated Use {@link Strings#startsWith(CharSequence, CharSequence) Strings.CS.startsWith(CharSequence, CharSequence)} */ + @Deprecated public static boolean startsWith(final CharSequence str, final CharSequence prefix) { - return startsWith(str, prefix, false); - } - - /** - * Check if a CharSequence starts with a specified prefix (optionally case-insensitive). - * - * @see String#startsWith(String) - * @param str the CharSequence to check, may be null - * @param prefix the prefix to find, may be null - * @param ignoreCase indicates whether the compare should ignore case - * (case-insensitive) or not. - * @return {@code true} if the CharSequence starts with the prefix or - * both {@code null} - */ - private static boolean startsWith(final CharSequence str, final CharSequence prefix, final boolean ignoreCase) { - if (str == null || prefix == null) { - return str == prefix; - } - // Get length once instead of twice in the unlikely case that it changes. - final int preLen = prefix.length(); - if (preLen > str.length()) { - return false; - } - return CharSequenceUtils.regionMatches(str, ignoreCase, 0, prefix, 0, preLen); + return Strings.CS.startsWith(str, prefix); } /** @@ -8101,9 +7861,11 @@ public class StringUtils { * both {@code null} * @since 2.4 * @since 3.0 Changed signature from startsWithIgnoreCase(String, String) to startsWithIgnoreCase(CharSequence, CharSequence) + * @deprecated Use {@link Strings#startsWith(CharSequence, CharSequence) Strings.CI.startsWith(CharSequence, CharSequence)} */ + @Deprecated public static boolean startsWithIgnoreCase(final CharSequence str, final CharSequence prefix) { - return startsWith(str, prefix, true); + return Strings.CI.startsWith(str, prefix); } /** diff --git a/src/main/java/org/apache/commons/lang3/Strings.java b/src/main/java/org/apache/commons/lang3/Strings.java new file mode 100644 index 000000000..c5614ba9d --- /dev/null +++ b/src/main/java/org/apache/commons/lang3/Strings.java @@ -0,0 +1,976 @@ +/* + * 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.lang3; + +import static org.apache.commons.lang3.StringUtils.INDEX_NOT_FOUND; + +import org.apache.commons.lang3.builder.AbstractSupplier; +import org.apache.commons.lang3.function.ToBooleanBiFunction; + +/** + * String operations where you choose case-senstive {@link #CS} vs. case-insensitive {@link #CI} through a singleton instance. + * + * @see CharSequenceUtils + * @see StringUtils + * @since 3.18.0 + */ +public abstract class Strings { + + public static class Builder extends AbstractSupplier<Strings, Builder, RuntimeException> { + + /** + * Ignores case when possible. + */ + private boolean ignoreCase; + + /** + * Compares null as less when possible. + */ + private boolean nullIsLess; + + @Override + public Strings get() { + return ignoreCase ? new CiStrings(ignoreCase) : new CsStrings(ignoreCase); + } + + protected boolean isIgnoreCase() { + return ignoreCase; + } + + protected boolean isNullIsLess() { + return nullIsLess; + } + + public Builder setIgnoreCase(final boolean ignoreCase) { + this.ignoreCase = ignoreCase; + return asThis(); + } + + public Builder setNullIsLess(final boolean nullIsLess) { + this.nullIsLess = nullIsLess; + return asThis(); + } + + } + + /** + * Case-insentive extension. + */ + private static final class CiStrings extends Strings { + + private CiStrings(final boolean nullIsLess) { + super(true, nullIsLess); + } + + @Override + public int compare(final String s1, final String s2) { + if (s1 == s2) { + // Both null or same object + return 0; + } + if (s1 == null) { + return isNullIsLess() ? -1 : 1; + } + if (s2 == null) { + return isNullIsLess() ? 1 : -1; + } + return s1.compareToIgnoreCase(s2); + } + + /** + * Tests if CharSequence contains a search CharSequence irrespective of case, handling {@code null}. Case-insensitivity is defined as by + * {@link String#equalsIgnoreCase(String)}. + * + * <p> + * A {@code null} CharSequence will return {@code false}. + * </p> + * + * <pre> + * StringUtils.containsIgnoreCase(null, *) = false + * StringUtils.containsIgnoreCase(*, null) = false + * StringUtils.containsIgnoreCase("", "") = true + * StringUtils.containsIgnoreCase("abc", "") = true + * StringUtils.containsIgnoreCase("abc", "a") = true + * StringUtils.containsIgnoreCase("abc", "z") = false + * StringUtils.containsIgnoreCase("abc", "A") = true + * StringUtils.containsIgnoreCase("abc", "Z") = false + * </pre> + * + * @param str the CharSequence to check, may be null + * @param searchStr the CharSequence to find, may be null + * @return true if the CharSequence contains the search CharSequence irrespective of case or false if not or {@code null} string input + */ + @Override + public boolean contains(final CharSequence str, final CharSequence searchStr) { + if (str == null || searchStr == null) { + return false; + } + final int len = searchStr.length(); + final int max = str.length() - len; + for (int i = 0; i <= max; i++) { + if (CharSequenceUtils.regionMatches(str, true, i, searchStr, 0, len)) { + return true; + } + } + return false; + } + + @Override + public boolean equals(final CharSequence cs1, final CharSequence cs2) { + if (cs1 == cs2) { + return true; + } + if (cs1 == null || cs2 == null) { + return false; + } + if (cs1.length() != cs2.length()) { + return false; + } + return CharSequenceUtils.regionMatches(cs1, true, 0, cs2, 0, cs1.length()); + } + + @Override + public boolean equals(final String s1, final String s2) { + return s1.equalsIgnoreCase(s2); + } + + @Override + public int indexOf(final CharSequence str, final CharSequence searchStr, int startPos) { + if (str == null || searchStr == null) { + return INDEX_NOT_FOUND; + } + if (startPos < 0) { + startPos = 0; + } + final int endLimit = str.length() - searchStr.length() + 1; + if (startPos > endLimit) { + return INDEX_NOT_FOUND; + } + if (searchStr.length() == 0) { + return startPos; + } + for (int i = startPos; i < endLimit; i++) { + if (CharSequenceUtils.regionMatches(str, true, i, searchStr, 0, searchStr.length())) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + /** + * Case in-sensitive find of the last index within a CharSequence from the specified position. + * + * <p> + * A {@code null} CharSequence will return {@code -1}. A negative start position returns {@code -1}. An empty ("") search CharSequence always matches + * unless the start position is negative. A start position greater than the string length searches the whole string. The search starts at the startPos + * and works backwards; matches starting after the start position are ignored. + * </p> + * + * <pre> + * StringUtils.lastIndexOfIgnoreCase(null, *, *) = -1 + * StringUtils.lastIndexOfIgnoreCase(*, null, *) = -1 + * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "A", 8) = 7 + * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B", 8) = 5 + * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "AB", 8) = 4 + * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B", 9) = 5 + * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B", -1) = -1 + * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "A", 0) = 0 + * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B", 0) = -1 + * </pre> + * + * @param str the CharSequence to check, may be null + * @param searchStr the CharSequence to find, may be null + * @param startPos the start position + * @return the last index of the search CharSequence (always ≤ startPos), -1 if no match or {@code null} input + */ + @Override + public int lastIndexOf(final CharSequence str, final CharSequence searchStr, int startPos) { + if (str == null || searchStr == null) { + return INDEX_NOT_FOUND; + } + final int searchStrLength = searchStr.length(); + final int strLength = str.length(); + if (startPos > strLength - searchStrLength) { + startPos = strLength - searchStrLength; + } + if (startPos < 0) { + return INDEX_NOT_FOUND; + } + if (searchStrLength == 0) { + return startPos; + } + for (int i = startPos; i >= 0; i--) { + if (CharSequenceUtils.regionMatches(str, true, i, searchStr, 0, searchStrLength)) { + return i; + } + } + return INDEX_NOT_FOUND; + } + + } + + /** + * Case-sentive extension. + */ + private static final class CsStrings extends Strings { + + private CsStrings(final boolean nullIsLess) { + super(false, nullIsLess); + } + + @Override + public int compare(final String s1, final String s2) { + if (s1 == s2) { + // Both null or same object + return 0; + } + if (s1 == null) { + return isNullIsLess() ? -1 : 1; + } + if (s2 == null) { + return isNullIsLess() ? 1 : -1; + } + return s1.compareTo(s2); + } + + @Override + public boolean contains(final CharSequence seq, final CharSequence searchSeq) { + return CharSequenceUtils.indexOf(seq, searchSeq, 0) >= 0; + } + + @Override + public boolean equals(final CharSequence cs1, final CharSequence cs2) { + if (cs1 == cs2) { + return true; + } + if (cs1 == null || cs2 == null) { + return false; + } + if (cs1.length() != cs2.length()) { + return false; + } + if (cs1 instanceof String && cs2 instanceof String) { + return cs1.equals(cs2); + } + // Step-wise comparison + final int length = cs1.length(); + for (int i = 0; i < length; i++) { + if (cs1.charAt(i) != cs2.charAt(i)) { + return false; + } + } + return true; + } + + @Override + public boolean equals(final String s1, final String s2) { + return s1.equals(s2); + } + + @Override + public int indexOf(final CharSequence seq, final CharSequence searchSeq, final int startPos) { + return CharSequenceUtils.indexOf(seq, searchSeq, startPos); + } + + /** + * Finds the last index within a CharSequence, handling {@code null}. This method uses {@link String#lastIndexOf(String, int)} if possible. + * + * <p> + * A {@code null} CharSequence will return {@code -1}. A negative start position returns {@code -1}. An empty ("") search CharSequence always matches + * unless the start position is negative. A start position greater than the string length searches the whole string. The search starts at the startPos + * and works backwards; matches starting after the start position are ignored. + * </p> + * + * <pre> + * StringUtils.lastIndexOf(null, *, *) = -1 + * StringUtils.lastIndexOf(*, null, *) = -1 + * StringUtils.lastIndexOf("aabaabaa", "a", 8) = 7 + * StringUtils.lastIndexOf("aabaabaa", "b", 8) = 5 + * StringUtils.lastIndexOf("aabaabaa", "ab", 8) = 4 + * StringUtils.lastIndexOf("aabaabaa", "b", 9) = 5 + * StringUtils.lastIndexOf("aabaabaa", "b", -1) = -1 + * StringUtils.lastIndexOf("aabaabaa", "a", 0) = 0 + * StringUtils.lastIndexOf("aabaabaa", "b", 0) = -1 + * StringUtils.lastIndexOf("aabaabaa", "b", 1) = -1 + * StringUtils.lastIndexOf("aabaabaa", "b", 2) = 2 + * StringUtils.lastIndexOf("aabaabaa", "ba", 2) = 2 + * </pre> + * + * @param seq the CharSequence to check, may be null + * @param searchSeq the CharSequence to find, may be null + * @param startPos the start position, negative treated as zero + * @return the last index of the search CharSequence (always ≤ startPos), -1 if no match or {@code null} string input + */ + @Override + public int lastIndexOf(final CharSequence seq, final CharSequence searchSeq, final int startPos) { + return CharSequenceUtils.lastIndexOf(seq, searchSeq, startPos); + } + + } + + /** + * The <b>C</b>ase-<b>I</b>nsensitive singleton instance. + */ + public static final Strings CI = new CiStrings(true); + + /** + * The <b>C</b>ase-<b>S</b>nsensitive singleton instance. + */ + public static final Strings CS = new CsStrings(true); + + public static final Builder builder() { + return new Builder(); + } + + /** + * Tests if the CharSequence contains any of the CharSequences in the given array. + * + * <p> + * A {@code null} {@code cs} CharSequence will return {@code false}. A {@code null} or zero length search array will return {@code false}. + * </p> + * + * @param cs The CharSequence to check, may be null + * @param searchCharSequences The array of CharSequences to search for, may be null. Individual CharSequences may be null as well. + * @return {@code true} if any of the search CharSequences are found, {@code false} otherwise + */ + private static boolean containsAny(final ToBooleanBiFunction<CharSequence, CharSequence> test, final CharSequence cs, + final CharSequence... searchCharSequences) { + if (StringUtils.isEmpty(cs) || ArrayUtils.isEmpty(searchCharSequences)) { + return false; + } + for (final CharSequence searchCharSequence : searchCharSequences) { + if (test.applyAsBoolean(cs, searchCharSequence)) { + return true; + } + } + return false; + } + + /** + * Ignores case when possible. + */ + private final boolean ignoreCase; + + /** + * Compares null as less when possible. + */ + private final boolean nullIsLess; + + private Strings(final boolean ignoreCase, final boolean nullIsLess) { + this.ignoreCase = ignoreCase; + this.nullIsLess = nullIsLess; + } + + /** + * Appends the suffix to the end of the string if the string does not already end with the suffix. + * + * <p> + * Case-sensitive examples + * </p> + * + * <pre> + * Strings.CS.appendIfMissing(null, null) = null + * Strings.CS.appendIfMissing("abc", null) = "abc" + * Strings.CS.appendIfMissing("", "xyz" = "xyz" + * Strings.CS.appendIfMissing("abc", "xyz") = "abcxyz" + * Strings.CS.appendIfMissing("abcxyz", "xyz") = "abcxyz" + * Strings.CS.appendIfMissing("abcXYZ", "xyz") = "abcXYZxyz" + * </pre> + * <p> + * With additional suffixes: + * </p> + * + * <pre> + * Strings.CS.appendIfMissing(null, null, null) = null + * Strings.CS.appendIfMissing("abc", null, null) = "abc" + * Strings.CS.appendIfMissing("", "xyz", null) = "xyz" + * Strings.CS.appendIfMissing("abc", "xyz", new CharSequence[]{null}) = "abcxyz" + * Strings.CS.appendIfMissing("abc", "xyz", "") = "abc" + * Strings.CS.appendIfMissing("abc", "xyz", "mno") = "abcxyz" + * Strings.CS.appendIfMissing("abcxyz", "xyz", "mno") = "abcxyz" + * Strings.CS.appendIfMissing("abcmno", "xyz", "mno") = "abcmno" + * Strings.CS.appendIfMissing("abcXYZ", "xyz", "mno") = "abcXYZxyz" + * Strings.CS.appendIfMissing("abcMNO", "xyz", "mno") = "abcMNOxyz" + * </pre> + * + * <p> + * Case-insensitive examples + * </p> + * + * <pre> + * Strings.CI.appendIfMissing(null, null) = null + * Strings.CI.appendIfMissing("abc", null) = "abc" + * Strings.CI.appendIfMissing("", "xyz") = "xyz" + * Strings.CI.appendIfMissing("abc", "xyz") = "abcxyz" + * Strings.CI.appendIfMissing("abcxyz", "xyz") = "abcxyz" + * Strings.CI.appendIfMissing("abcXYZ", "xyz") = "abcXYZ" + * </pre> + * <p> + * With additional suffixes: + * </p> + * + * <pre> + * Strings.CI.appendIfMissing(null, null, null) = null + * Strings.CI.appendIfMissing("abc", null, null) = "abc" + * Strings.CI.appendIfMissing("", "xyz", null) = "xyz" + * Strings.CI.appendIfMissing("abc", "xyz", new CharSequence[]{null}) = "abcxyz" + * Strings.CI.appendIfMissing("abc", "xyz", "") = "abc" + * Strings.CI.appendIfMissing("abc", "xyz", "mno") = "abcxyz" + * Strings.CI.appendIfMissing("abcxyz", "xyz", "mno") = "abcxyz" + * Strings.CI.appendIfMissing("abcmno", "xyz", "mno") = "abcmno" + * Strings.CI.appendIfMissing("abcXYZ", "xyz", "mno") = "abcXYZ" + * Strings.CI.appendIfMissing("abcMNO", "xyz", "mno") = "abcMNO" + * </pre> + * + * @param str The string. + * @param suffix The suffix to append to the end of the string. + * @param suffixes Additional suffixes that are valid terminators (optional). + * @return A new String if suffix was appended, the same string otherwise. + */ + public String appendIfMissing(final String str, final CharSequence suffix, final CharSequence... suffixes) { + if (str == null || StringUtils.isEmpty(suffix) || endsWith(str, suffix)) { + return str; + } + if (ArrayUtils.isNotEmpty(suffixes)) { + for (final CharSequence s : suffixes) { + if (endsWith(str, s)) { + return str; + } + } + } + return str + suffix; + } + + public abstract int compare(String s1, String s2); + + /** + * Tests if CharSequence contains a search CharSequence, handling {@code null}. This method uses {@link String#indexOf(String)} if possible. + * + * <p> + * A {@code null} CharSequence will return {@code false}. + * </p> + * + * <pre> + * Strings.CS.contains(null, *) = false + * Strings.CS.contains(*, null) = false + * Strings.CS.contains("", "") = true + * Strings.CS.contains("abc", "") = true + * Strings.CS.contains("abc", "a") = true + * Strings.CS.contains("abc", "z") = false + * </pre> + * + * @param seq the CharSequence to check, may be null + * @param searchSeq the CharSequence to find, may be null + * @return true if the CharSequence contains the search CharSequence, false if not or {@code null} string input + */ + public abstract boolean contains(CharSequence seq, CharSequence searchSeq); + + /** + * Tests if the CharSequence contains any of the CharSequences in the given array, ignoring case. + * + * <p> + * A {@code null} {@code cs} CharSequence will return {@code false}. A {@code null} or zero length search array will return {@code false}. + * </p> + * + * <pre> + * StringUtils.containsAny(null, *) = false + * StringUtils.containsAny("", *) = false + * StringUtils.containsAny(*, null) = false + * StringUtils.containsAny(*, []) = false + * StringUtils.containsAny("abcd", "ab", null) = true + * StringUtils.containsAny("abcd", "ab", "cd") = true + * StringUtils.containsAny("abc", "d", "abc") = true + * StringUtils.containsAny("abc", "D", "ABC") = true + * StringUtils.containsAny("ABC", "d", "abc") = true + * </pre> + * + * @param cs The CharSequence to check, may be null + * @param searchCharSequences The array of CharSequences to search for, may be null. Individual CharSequences may be null as well. + * @return {@code true} if any of the search CharSequences are found, {@code false} otherwise + */ + public boolean containsAny(final CharSequence cs, final CharSequence... searchCharSequences) { + return containsAny(this::contains, cs, searchCharSequences); + } + + /** + * Tests if a CharSequence ends with a specified suffix (optionally case-insensitive). + * + * <p> + * Case-sensitive examples + * </p> + * + * <pre> + * Strings.CS.endsWith(null, null) = true + * Strings.CS.endsWith(null, "def") = false + * Strings.CS.endsWith("abcdef", null) = false + * Strings.CS.endsWith("abcdef", "def") = true + * Strings.CS.endsWith("ABCDEF", "def") = false + * Strings.CS.endsWith("ABCDEF", "cde") = false + * Strings.CS.endsWith("ABCDEF", "") = true + * </pre> + * + * <p> + * Case-insensitive examples + * </p> + * + * <pre> + * Strings.CI.endsWith(null, null) = true + * Strings.CI.endsWith(null, "def") = false + * Strings.CI.endsWith("abcdef", null) = false + * Strings.CI.endsWith("abcdef", "def") = true + * Strings.CI.endsWith("ABCDEF", "def") = true + * Strings.CI.endsWith("ABCDEF", "cde") = false + * </pre> + * + * @param str the CharSequence to check, may be null. + * @param suffix the suffix to find, may be null. + * @return {@code true} if the CharSequence starts with the prefix or both {@code null}. + * @see String#endsWith(String) + */ + public boolean endsWith(final CharSequence str, final CharSequence suffix) { + if (str == null || suffix == null) { + return str == suffix; + } + final int sufLen = suffix.length(); + if (sufLen > str.length()) { + return false; + } + return CharSequenceUtils.regionMatches(str, ignoreCase, str.length() - sufLen, suffix, 0, sufLen); + } + + public abstract boolean equals(CharSequence s1, CharSequence s2); + + /** + * Compares this string to the specified object. The result is {@code + * true} if and only if the argument is not {@code null} and is a {@code + * String} object that represents the same sequence of characters as this object. + * + * @param s1 The left string to compare this {@code String} against + * @param s2 The right string to compare this {@code String} against + * + * @return {@code true} if the given object represents a {@code String} equivalent to this string, {@code false} otherwise + * + * @see String#compareTo(String) + * @see String#equalsIgnoreCase(String) + */ + public abstract boolean equals(String s1, String s2); + + /** + * Compares given {@code string} to a CharSequences vararg of {@code searchStrings}, returning {@code true} if the {@code string} is equal to any of the + * {@code searchStrings}. + * + * <pre> + * StringUtils.equalsAny(null, (CharSequence[]) null) = false + * StringUtils.equalsAny(null, null, null) = true + * StringUtils.equalsAny(null, "abc", "def") = false + * StringUtils.equalsAny("abc", null, "def") = false + * StringUtils.equalsAny("abc", "abc", "def") = true + * StringUtils.equalsAny("abc", "ABC", "DEF") = false + * </pre> + * + * @param string to compare, may be {@code null}. + * @param searchStrings a vararg of strings, may be {@code null}. + * @return {@code true} if the string is equal (case-sensitive) to any other element of {@code searchStrings}; {@code false} if {@code searchStrings} is + * null or contains no matches. + */ + public boolean equalsAny(final CharSequence string, final CharSequence... searchStrings) { + if (ArrayUtils.isNotEmpty(searchStrings)) { + for (final CharSequence next : searchStrings) { + if (equals(string, next)) { + return true; + } + } + } + return false; + } + + public int indexOf(final CharSequence str, final CharSequence searchStr) { + return indexOf(str, searchStr, 0); + } + + public abstract int indexOf(CharSequence str, CharSequence searchStr, int startPos); + + public boolean isCaseSensitive() { + return !ignoreCase; + } + + boolean isIgnoreCase() { + return ignoreCase; + } + + boolean isNullIsLess() { + return nullIsLess; + } + + public int lastIndexOf(final CharSequence str, final CharSequence searchStr) { + if (str == null) { + return INDEX_NOT_FOUND; + } + return lastIndexOf(str, searchStr, str.length()); + } + + public abstract int lastIndexOf(CharSequence seq, CharSequence searchSeq, int startPos); + + /** + * Prepends the prefix to the start of the string if the string does not already start with any of the prefixes. + * + * <p> + * Case-sensitive examples + * </p> + * + * <pre> + * StringUtils.prependIfMissing(null, null) = null + * StringUtils.prependIfMissing("abc", null) = "abc" + * StringUtils.prependIfMissing("", "xyz") = "xyz" + * StringUtils.prependIfMissing("abc", "xyz") = "xyzabc" + * StringUtils.prependIfMissing("xyzabc", "xyz") = "xyzabc" + * StringUtils.prependIfMissing("XYZabc", "xyz") = "xyzXYZabc" + * </pre> + * <p> + * With additional prefixes, + * </p> + * + * <pre> + * StringUtils.prependIfMissing(null, null, null) = null + * StringUtils.prependIfMissing("abc", null, null) = "abc" + * StringUtils.prependIfMissing("", "xyz", null) = "xyz" + * StringUtils.prependIfMissing("abc", "xyz", new CharSequence[]{null}) = "xyzabc" + * StringUtils.prependIfMissing("abc", "xyz", "") = "abc" + * StringUtils.prependIfMissing("abc", "xyz", "mno") = "xyzabc" + * StringUtils.prependIfMissing("xyzabc", "xyz", "mno") = "xyzabc" + * StringUtils.prependIfMissing("mnoabc", "xyz", "mno") = "mnoabc" + * StringUtils.prependIfMissing("XYZabc", "xyz", "mno") = "xyzXYZabc" + * StringUtils.prependIfMissing("MNOabc", "xyz", "mno") = "xyzMNOabc" + * </pre> + * + * <p> + * Case-insensitive examples + * </p> + * + * <pre> + * StringUtils.prependIfMissingIgnoreCase(null, null) = null + * StringUtils.prependIfMissingIgnoreCase("abc", null) = "abc" + * StringUtils.prependIfMissingIgnoreCase("", "xyz") = "xyz" + * StringUtils.prependIfMissingIgnoreCase("abc", "xyz") = "xyzabc" + * StringUtils.prependIfMissingIgnoreCase("xyzabc", "xyz") = "xyzabc" + * StringUtils.prependIfMissingIgnoreCase("XYZabc", "xyz") = "XYZabc" + * </pre> + * <p> + * With additional prefixes, + * </p> + * + * <pre> + * StringUtils.prependIfMissingIgnoreCase(null, null, null) = null + * StringUtils.prependIfMissingIgnoreCase("abc", null, null) = "abc" + * StringUtils.prependIfMissingIgnoreCase("", "xyz", null) = "xyz" + * StringUtils.prependIfMissingIgnoreCase("abc", "xyz", new CharSequence[]{null}) = "xyzabc" + * StringUtils.prependIfMissingIgnoreCase("abc", "xyz", "") = "abc" + * StringUtils.prependIfMissingIgnoreCase("abc", "xyz", "mno") = "xyzabc" + * StringUtils.prependIfMissingIgnoreCase("xyzabc", "xyz", "mno") = "xyzabc" + * StringUtils.prependIfMissingIgnoreCase("mnoabc", "xyz", "mno") = "mnoabc" + * StringUtils.prependIfMissingIgnoreCase("XYZabc", "xyz", "mno") = "XYZabc" + * StringUtils.prependIfMissingIgnoreCase("MNOabc", "xyz", "mno") = "MNOabc" + * </pre> + * + * @param str The string. + * @param prefix The prefix to prepend to the start of the string. + * @param prefixes Additional prefixes that are valid. + * @return A new String if prefix was prepended, the same string otherwise. + */ + public String prependIfMissing(final String str, final CharSequence prefix, final CharSequence... prefixes) { + if (str == null || StringUtils.isEmpty(prefix) || startsWith(str, prefix)) { + return str; + } + if (ArrayUtils.isNotEmpty(prefixes)) { + for (final CharSequence p : prefixes) { + if (startsWith(str, p)) { + return str; + } + } + } + return prefix + str; + } + + /** + * Case-insensitive removal of a substring if it is at the end of a source string, otherwise returns the source string. + * + * <p> + * A {@code null} source string will return {@code null}. An empty ("") source string will return the empty string. A {@code null} search string will return + * the source string. + * </p> + * + * <p> + * Case-sensitive examples + * </p> + * + * <pre> + * StringUtils.removeEnd(null, *) = null + * StringUtils.removeEnd("", *) = "" + * StringUtils.removeEnd(*, null) = * + * StringUtils.removeEnd("www.domain.com", ".com.") = "www.domain.com" + * StringUtils.removeEnd("www.domain.com", ".com") = "www.domain" + * StringUtils.removeEnd("www.domain.com", "domain") = "www.domain.com" + * StringUtils.removeEnd("abc", "") = "abc" + * </pre> + * <p> + * Case-insensitive examples + * </p> + * + * <pre> + * StringUtils.removeEndIgnoreCase(null, *) = null + * StringUtils.removeEndIgnoreCase("", *) = "" + * StringUtils.removeEndIgnoreCase(*, null) = * + * StringUtils.removeEndIgnoreCase("www.domain.com", ".com.") = "www.domain.com" + * StringUtils.removeEndIgnoreCase("www.domain.com", ".com") = "www.domain" + * StringUtils.removeEndIgnoreCase("www.domain.com", "domain") = "www.domain.com" + * StringUtils.removeEndIgnoreCase("abc", "") = "abc" + * StringUtils.removeEndIgnoreCase("www.domain.com", ".COM") = "www.domain") + * StringUtils.removeEndIgnoreCase("www.domain.COM", ".com") = "www.domain") + * </pre> + * + * @param str the source String to search, may be null + * @param remove the String to search for (case-insensitive) and remove, may be null + * @return the substring with the string removed if found, {@code null} if null String input + */ + public String removeEnd(final String str, final CharSequence remove) { + if (StringUtils.isEmpty(str) || StringUtils.isEmpty(remove)) { + return str; + } + if (endsWith(str, remove)) { + return str.substring(0, str.length() - remove.length()); + } + return str; + } + + /** + * Case-insensitive removal of a substring if it is at the beginning of a source string, otherwise returns the source string. + * + * <p> + * A {@code null} source string will return {@code null}. An empty ("") source string will return the empty string. A {@code null} search string will return + * the source string. + * </p> + * + * <p> + * Case-insensitive examples + * </p> + * + * <pre> + * StringUtils.removeStartIgnoreCase(null, *) = null + * StringUtils.removeStartIgnoreCase("", *) = "" + * StringUtils.removeStartIgnoreCase(*, null) = * + * StringUtils.removeStartIgnoreCase("www.domain.com", "www.") = "domain.com" + * StringUtils.removeStartIgnoreCase("www.domain.com", "WWW.") = "domain.com" + * StringUtils.removeStartIgnoreCase("domain.com", "www.") = "domain.com" + * StringUtils.removeStartIgnoreCase("www.domain.com", "domain") = "www.domain.com" + * StringUtils.removeStartIgnoreCase("abc", "") = "abc" + * </pre> + * + * @param str the source String to search, may be null + * @param remove the String to search for (case-insensitive) and remove, may be null + * @return the substring with the string removed if found, {@code null} if null String input + */ + public String removeStart(final String str, final CharSequence remove) { + if (str != null && startsWith(str, remove)) { + return str.substring(StringUtils.length(remove)); + } + return str; + } + + /** + * Case insensitively replaces all occurrences of a String within another String. + * + * <p> + * A {@code null} reference passed to this method is a no-op. + * </p> + * + * <p> + * Case-sensitive examples + * </p> + * + * <pre> + * StringUtils.replace(null, *, *) = null + * StringUtils.replace("", *, *) = "" + * StringUtils.replace("any", null, *) = "any" + * StringUtils.replace("any", *, null) = "any" + * StringUtils.replace("any", "", *) = "any" + * StringUtils.replace("aba", "a", null) = "aba" + * StringUtils.replace("aba", "a", "") = "b" + * StringUtils.replace("aba", "a", "z") = "zbz" + * </pre> + * <p> + * Case-insensitive examples + * </p> + * + * <pre> + * StringUtils.replaceIgnoreCase(null, *, *) = null + * StringUtils.replaceIgnoreCase("", *, *) = "" + * StringUtils.replaceIgnoreCase("any", null, *) = "any" + * StringUtils.replaceIgnoreCase("any", *, null) = "any" + * StringUtils.replaceIgnoreCase("any", "", *) = "any" + * StringUtils.replaceIgnoreCase("aba", "a", null) = "aba" + * StringUtils.replaceIgnoreCase("abA", "A", "") = "b" + * StringUtils.replaceIgnoreCase("aba", "A", "z") = "zbz" + * </pre> + * + * @see #replace(String text, String searchString, String replacement, int max) + * @param text text to search and replace in, may be null + * @param searchString the String to search for (case-insensitive), may be null + * @param replacement the String to replace it with, may be null + * @return the text with any replacements processed, {@code null} if null String input + */ + public String replace(final String text, final String searchString, final String replacement) { + return replace(text, searchString, replacement, -1); + } + + /** + * Replaces a String with another String inside a larger String, for the first {@code max} values of the search String, case-sensitively/insensitively based + * on {@code ignoreCase} value. + * + * <p> + * A {@code null} reference passed to this method is a no-op. + * </p> + * + * <pre> + * StringUtils.replace(null, *, *, *, false) = null + * StringUtils.replace("", *, *, *, false) = "" + * StringUtils.replace("any", null, *, *, false) = "any" + * StringUtils.replace("any", *, null, *, false) = "any" + * StringUtils.replace("any", "", *, *, false) = "any" + * StringUtils.replace("any", *, *, 0, false) = "any" + * StringUtils.replace("abaa", "a", null, -1, false) = "abaa" + * StringUtils.replace("abaa", "a", "", -1, false) = "b" + * StringUtils.replace("abaa", "a", "z", 0, false) = "abaa" + * StringUtils.replace("abaa", "A", "z", 1, false) = "abaa" + * StringUtils.replace("abaa", "A", "z", 1, true) = "zbaa" + * StringUtils.replace("abAa", "a", "z", 2, true) = "zbza" + * StringUtils.replace("abAa", "a", "z", -1, true) = "zbzz" + * </pre> + * + * @param text text to search and replace in, may be null + * @param searchString the String to search for (case-insensitive), may be null + * @param replacement the String to replace it with, may be null + * @param max maximum number of values to replace, or {@code -1} if no maximum + * @return the text with any replacements processed, {@code null} if null String input + */ + public String replace(final String text, String searchString, final String replacement, int max) { + if (StringUtils.isEmpty(text) || StringUtils.isEmpty(searchString) || replacement == null || max == 0) { + return text; + } + if (ignoreCase) { + searchString = searchString.toLowerCase(); + } + int start = 0; + int end = indexOf(text, searchString, start); + if (end == INDEX_NOT_FOUND) { + return text; + } + final int replLength = searchString.length(); + int increase = Math.max(replacement.length() - replLength, 0); + increase *= max < 0 ? 16 : Math.min(max, 64); + final StringBuilder buf = new StringBuilder(text.length() + increase); + while (end != INDEX_NOT_FOUND) { + buf.append(text, start, end).append(replacement); + start = end + replLength; + if (--max == 0) { + break; + } + end = indexOf(text, searchString, start); + } + buf.append(text, start, text.length()); + return buf.toString(); + } + + /** + * Replaces a String with another String inside a larger String, once. + * + * <p> + * A {@code null} reference passed to this method is a no-op. + * </p> + * + * <p> + * Case-sensitive examples + * </p> + * + * <pre> + * StringUtils.replaceOnce(null, *, *) = null + * StringUtils.replaceOnce("", *, *) = "" + * StringUtils.replaceOnce("any", null, *) = "any" + * StringUtils.replaceOnce("any", *, null) = "any" + * StringUtils.replaceOnce("any", "", *) = "any" + * StringUtils.replaceOnce("aba", "a", null) = "aba" + * StringUtils.replaceOnce("aba", "a", "") = "ba" + * StringUtils.replaceOnce("aba", "a", "z") = "zba" + * </pre> + * + * @see #replace(String text, String searchString, String replacement, int max) + * @param text text to search and replace in, may be null + * @param searchString the String to search for, may be null + * @param replacement the String to replace with, may be null + * @return the text with any replacements processed, {@code null} if null String input + */ + public String replaceOnce(final String text, final String searchString, final String replacement) { + return replace(text, searchString, replacement, 1); + } + + /** + * Tests if a CharSequence starts with a specified prefix. + * + * <p> + * {@code null}s are handled without exceptions. Two {@code null} references are considered to be equal. + * </p> + * + * <p> + * Case-sensitive examples + * </p> + * + * <pre> + * StringUtils.startsWith(null, null) = true + * StringUtils.startsWith(null, "abc") = false + * StringUtils.startsWith("abcdef", null) = false + * StringUtils.startsWith("abcdef", "abc") = true + * StringUtils.startsWith("ABCDEF", "abc") = false + * </pre> + * + * <p> + * Case-insensitive examples + * </p> + * + * <pre> + * StringUtils.startsWithIgnoreCase(null, null) = true + * StringUtils.startsWithIgnoreCase(null, "abc") = false + * StringUtils.startsWithIgnoreCase("abcdef", null) = false + * StringUtils.startsWithIgnoreCase("abcdef", "abc") = true + * StringUtils.startsWithIgnoreCase("ABCDEF", "abc") = true + * </pre> + * + * @see String#startsWith(String) + * @param str the CharSequence to check, may be null + * @param prefix the prefix to find, may be null + * @return {@code true} if the CharSequence starts with the prefix, case-sensitive, or both {@code null} + */ + public boolean startsWith(final CharSequence str, final CharSequence prefix) { + if (str == null || prefix == null) { + return str == prefix; + } + final int preLen = prefix.length(); + if (preLen > str.length()) { + return false; + } + return CharSequenceUtils.regionMatches(str, ignoreCase, 0, prefix, 0, preLen); + } +} diff --git a/src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java b/src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java index df7fff831..fe2570135 100644 --- a/src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java +++ b/src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java @@ -28,6 +28,7 @@ import org.apache.commons.lang3.ClassUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringEscapeUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Strings; /** * Controls {@link String} formatting for {@link ToStringBuilder}. @@ -2270,7 +2271,7 @@ public abstract class ToStringStyle implements Serializable { * @since 2.0 */ protected void removeLastFieldSeparator(final StringBuffer buffer) { - if (StringUtils.endsWith(buffer, fieldSeparator)) { + if (Strings.CS.endsWith(buffer, fieldSeparator)) { buffer.setLength(buffer.length() - fieldSeparator.length()); } } diff --git a/src/main/java/org/apache/commons/lang3/exception/DefaultExceptionContext.java b/src/main/java/org/apache/commons/lang3/exception/DefaultExceptionContext.java index c98d3d4b2..cb9f182fa 100644 --- a/src/main/java/org/apache/commons/lang3/exception/DefaultExceptionContext.java +++ b/src/main/java/org/apache/commons/lang3/exception/DefaultExceptionContext.java @@ -24,7 +24,7 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Strings; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; @@ -84,7 +84,7 @@ public class DefaultExceptionContext implements ExceptionContext, Serializable { */ @Override public List<Object> getContextValues(final String label) { - return stream().filter(pair -> StringUtils.equals(label, pair.getKey())).map(Pair::getValue).collect(Collectors.toList()); + return stream().filter(pair -> Strings.CS.equals(label, pair.getKey())).map(Pair::getValue).collect(Collectors.toList()); } /** @@ -92,7 +92,7 @@ public class DefaultExceptionContext implements ExceptionContext, Serializable { */ @Override public Object getFirstContextValue(final String label) { - return stream().filter(pair -> StringUtils.equals(label, pair.getKey())).findFirst().map(Pair::getValue).orElse(null); + return stream().filter(pair -> Strings.CS.equals(label, pair.getKey())).findFirst().map(Pair::getValue).orElse(null); } /** @@ -138,7 +138,7 @@ public class DefaultExceptionContext implements ExceptionContext, Serializable { */ @Override public DefaultExceptionContext setContextValue(final String label, final Object value) { - contextValues.removeIf(p -> StringUtils.equals(label, p.getKey())); + contextValues.removeIf(p -> Strings.CS.equals(label, p.getKey())); addContextValue(label, value); return this; } diff --git a/src/main/java/org/apache/commons/lang3/text/StrBuilder.java b/src/main/java/org/apache/commons/lang3/text/StrBuilder.java index d700b4b36..6f907a8f1 100644 --- a/src/main/java/org/apache/commons/lang3/text/StrBuilder.java +++ b/src/main/java/org/apache/commons/lang3/text/StrBuilder.java @@ -29,6 +29,7 @@ import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.CharUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Strings; import org.apache.commons.lang3.builder.Builder; /** @@ -2357,7 +2358,7 @@ public class StrBuilder implements CharSequence, Appendable, Serializable, Build * @return the last index of the string, or -1 if not found */ public int lastIndexOf(final String str, final int startIndex) { - return StringUtils.lastIndexOf(this, str, startIndex); + return Strings.CS.lastIndexOf(this, str, startIndex); } /** diff --git a/src/test/java/org/apache/commons/lang3/StringsTest.java b/src/test/java/org/apache/commons/lang3/StringsTest.java new file mode 100644 index 000000000..1bb29ddc0 --- /dev/null +++ b/src/test/java/org/apache/commons/lang3/StringsTest.java @@ -0,0 +1,57 @@ +/* + * 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.lang3; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** + * Tests {@link Strings}. + */ +public class StringsTest { + + @Test + public void testBuilder() { + assertTrue(Strings.builder().setIgnoreCase(false).get().isCaseSensitive()); + assertFalse(Strings.builder().setIgnoreCase(true).get().isCaseSensitive()); + // + assertTrue(Strings.builder().setNullIsLess(false).get().isCaseSensitive()); + assertTrue(Strings.builder().setNullIsLess(true).get().isCaseSensitive()); + } + + @Test + public void testBuilderDefaults() { + final Strings strings = Strings.builder().get(); + assertTrue(strings.isCaseSensitive()); + } + + @Test + public void testCaseInsensitiveConstant() { + assertNotNull(Strings.CI); + assertFalse(Strings.CI.isCaseSensitive()); + } + + @Test + public void testCaseSensitiveConstant() { + assertNotNull(Strings.CS); + assertTrue(Strings.CS.isCaseSensitive()); + } +}