diff --git a/src/main/java/org/apache/commons/lang3/StringUtils.java b/src/main/java/org/apache/commons/lang3/StringUtils.java index 5bd18dc1b..a6563749b 100644 --- a/src/main/java/org/apache/commons/lang3/StringUtils.java +++ b/src/main/java/org/apache/commons/lang3/StringUtils.java @@ -1084,10 +1084,10 @@ public class StringUtils { // LastIndexOf //----------------------------------------------------------------------- /** - *

Finds the last index within a String, handling {@code null}. - * This method uses {@link String#lastIndexOf(int)}.

+ *

Finds the last index within a CharSequence, handling {@code null}. + * This method uses {@link String#lastIndexOf(int)} if possible.

* - *

A {@code null} or empty ("") String will return {@code -1}.

+ *

A {@code null} or empty ("") CharSequence will return {@code -1}.

* *
      * StringUtils.lastIndexOf(null, *)         = -1
@@ -1096,25 +1096,25 @@ public class StringUtils {
      * StringUtils.lastIndexOf("aabaabaa", 'b') = 5
      * 
* - * @param str the String to check, may be null + * @param seq the CharSequence to check, may be null * @param searchChar the character to find * @return the last index of the search character, * -1 if no match or {@code null} string input * @since 2.0 */ - public static int lastIndexOf(String str, int searchChar) { - if (isEmpty(str)) { + public static int lastIndexOf(CharSequence seq, int searchChar) { + if (isEmpty(seq)) { return INDEX_NOT_FOUND; } - return str.lastIndexOf(searchChar); + return StringUtils.lastIndexOfSequence(seq, searchChar, seq.length()); } /** - *

Finds the last index within a String from a start position, + *

Finds the last index within a CharSequence from a start position, * handling {@code null}. - * This method uses {@link String#lastIndexOf(int, int)}.

+ * This method uses {@link String#lastIndexOf(int, int)} if possible.

* - *

A {@code null} or empty ("") String will return {@code -1}. + *

A {@code null} or empty ("") CharSequence will return {@code -1}. * A negative start position returns {@code -1}. * A start position greater than the string length searches the whole string.

* @@ -1129,25 +1129,25 @@ public class StringUtils { * StringUtils.lastIndexOf("aabaabaa", 'a', 0) = 0 * * - * @param str the String to check, may be null + * @param seq the CharSequence to check, may be null * @param searchChar the character to find * @param startPos the start position * @return the last index of the search character, * -1 if no match or {@code null} string input * @since 2.0 */ - public static int lastIndexOf(String str, int searchChar, int startPos) { - if (isEmpty(str)) { + public static int lastIndexOf(CharSequence seq, int searchChar, int startPos) { + if (isEmpty(seq)) { return INDEX_NOT_FOUND; } - return str.lastIndexOf(searchChar, startPos); + return StringUtils.lastIndexOfSequence(seq, searchChar, startPos); } /** - *

Finds the last index within a String, handling {@code null}. - * This method uses {@link String#lastIndexOf(String)}.

+ *

Finds the last index within a CharSequence, handling {@code null}. + * This method uses {@link String#lastIndexOf(String)} if possible.

* - *

A {@code null} String will return {@code -1}.

+ *

A {@code null} CharSequence will return {@code -1}.

* *
      * StringUtils.lastIndexOf(null, *)          = -1
@@ -1159,17 +1159,17 @@ public class StringUtils {
      * StringUtils.lastIndexOf("aabaabaa", "")   = 8
      * 
* - * @param str the String to check, may be null - * @param searchStr the String to find, may be null + * @param seq the CharSequence to check, may be null + * @param searchSeq the CharSequence to find, may be null * @return the last index of the search String, * -1 if no match or {@code null} string input * @since 2.0 */ - public static int lastIndexOf(String str, String searchStr) { - if (str == null || searchStr == null) { + public static int lastIndexOf(CharSequence seq, CharSequence searchSeq) { + if (seq == null || searchSeq == null) { return INDEX_NOT_FOUND; } - return str.lastIndexOf(searchStr); + return StringUtils.lastIndexOfSequence(seq, searchSeq, seq.length()); } /** @@ -1210,12 +1210,12 @@ public class StringUtils { } /** - *

Finds the first index within a String, handling {@code null}. - * This method uses {@link String#lastIndexOf(String, int)}.

+ *

Finds the first index within a CharSequence, handling {@code null}. + * This method uses {@link String#lastIndexOf(String, int)} if possible.

* - *

A {@code null} String will return {@code -1}. + *

A {@code null} CharSequence will return {@code -1}. * A negative start position returns {@code -1}. - * An empty ("") search String always matches unless the start position is negative. + * An empty ("") search CharSequence always matches unless the start position is negative. * A start position greater than the string length searches the whole string.

* *
@@ -1230,18 +1230,18 @@ public class StringUtils {
      * StringUtils.lastIndexOf("aabaabaa", "b", 0)  = -1
      * 
* - * @param str the String to check, may be null - * @param searchStr the String to find, may be null + * @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 first index of the search String, + * @return the first index of the search CharSequence, * -1 if no match or {@code null} string input * @since 2.0 */ - public static int lastIndexOf(String str, String searchStr, int startPos) { - if (str == null || searchStr == null) { + public static int lastIndexOf(CharSequence seq, CharSequence searchSeq, int startPos) { + if (seq == null || searchSeq == null) { return INDEX_NOT_FOUND; } - return str.lastIndexOf(searchStr, startPos); + return StringUtils.lastIndexOfSequence(seq, searchSeq, startPos); } /** @@ -1352,10 +1352,10 @@ public class StringUtils { } /** - *

Checks if String contains a search String, handling {@code null}. - * This method uses {@link String#indexOf(String)}.

+ *

Checks if CharSequence contains a search CharSequence, handling {@code null}. + * This method uses {@link String#indexOf(String)} if possible.

* - *

A {@code null} String will return {@code false}.

+ *

A {@code null} CharSequence will return {@code false}.

* *
      * StringUtils.contains(null, *)     = false
@@ -1366,17 +1366,17 @@ public class StringUtils {
      * StringUtils.contains("abc", "z")  = false
      * 
* - * @param str the String to check, may be null - * @param searchStr the String to find, may be null - * @return true if the String contains the search String, + * @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 * @since 2.0 */ - public static boolean contains(String str, String searchStr) { - if (str == null || searchStr == null) { + public static boolean contains(CharSequence seq, CharSequence searchSeq) { + if (seq == null || searchSeq == null) { return false; } - return str.indexOf(searchStr) >= 0; + return indexOfSequence(seq, searchSeq, 0) >= 0; } /** @@ -1417,21 +1417,21 @@ public class StringUtils { } /** - * Check whether the given String contains any whitespace characters. - * @param str the String to check (may be {@code null}) - * @return {@code true} if the String is not empty and + * Check whether the given CharSequence contains any whitespace characters. + * @param seq the CharSequence to check (may be {@code null}) + * @return {@code true} if the CharSequence is not empty and * contains at least 1 whitespace character * @see java.lang.Character#isWhitespace * @since 3.0 */ // From org.springframework.util.StringUtils, under Apache License 2.0 - public static boolean containsWhitespace(String str) { - if (isEmpty(str)) { + public static boolean containsWhitespace(CharSequence seq) { + if (isEmpty(seq)) { return false; } - int strLen = str.length(); + int strLen = seq.length(); for (int i = 0; i < strLen; i++) { - if (Character.isWhitespace(str.charAt(i))) { + if (Character.isWhitespace(seq.charAt(i))) { return true; } } @@ -1510,13 +1510,13 @@ public class StringUtils { * @param searchChars the chars to search for, may be null * @return the index of any of the chars, -1 if no match or null input * @since 2.0 - * @since 3.0 Changed signature from indexOfAny(String, String) to indexOfAny(CharSequence, String) + * @since 3.0 Changed signature from indexOfAny(String, String) to indexOfAny(CharSequence, CharSequence) */ - public static int indexOfAny(CharSequence cs, String searchChars) { + public static int indexOfAny(CharSequence cs, CharSequence searchChars) { if (isEmpty(cs) || isEmpty(searchChars)) { return INDEX_NOT_FOUND; } - return indexOfAny(cs, searchChars.toCharArray()); + return indexOfAny(cs, toCharArraySequence(searchChars)); } // ContainsAny @@ -1544,7 +1544,7 @@ public class StringUtils { * {@code false} if no match or null input * @since 2.4 */ - public static boolean containsAny(String cs, char[] searchChars) { + public static boolean containsAny(CharSequence cs, char[] searchChars) { if (isEmpty(cs) || ArrayUtils.isEmpty(searchChars)) { return false; } @@ -1601,11 +1601,11 @@ public class StringUtils { * @return the {@code true} if any of the chars are found, {@code false} if no match or null input * @since 2.4 */ - public static boolean containsAny(String cs, String searchChars) { + public static boolean containsAny(CharSequence cs, CharSequence searchChars) { if (searchChars == null) { return false; } - return containsAny(cs, searchChars.toCharArray()); + return containsAny(cs, toCharArraySequence(searchChars)); } // IndexOfAnyBut chars @@ -1662,10 +1662,10 @@ public class StringUtils { } /** - *

Search a String to find the first index of any + *

Search a CharSequence to find the first index of any * character not in the given set of characters.

* - *

A {@code null} String will return {@code -1}. + *

A {@code null} CharSequence will return {@code -1}. * A {@code null} or empty search string will return {@code -1}.

* *
@@ -1678,22 +1678,22 @@ public class StringUtils {
      * StringUtils.indexOfAnyBut("aba","ab")         = -1
      * 
* - * @param str the String to check, may be null + * @param seq the CharSequence to check, may be null * @param searchChars the chars to search for, may be null * @return the index of any of the chars, -1 if no match or null input * @since 2.0 */ - public static int indexOfAnyBut(String str, String searchChars) { - if (isEmpty(str) || isEmpty(searchChars)) { + public static int indexOfAnyBut(CharSequence seq, CharSequence searchChars) { + if (isEmpty(seq) || isEmpty(searchChars)) { return INDEX_NOT_FOUND; } - int strLen = str.length(); + int strLen = seq.length(); for (int i = 0; i < strLen; i++) { - char ch = str.charAt(i); - boolean chFound = searchChars.indexOf(ch) >= 0; + char ch = seq.charAt(i); + boolean chFound = indexOfSequence(searchChars, ch, 0) >= 0; if (i + 1 < strLen && Character.isHighSurrogate(ch)) { - char ch2 = str.charAt(i + 1); - if (chFound && searchChars.indexOf(ch2) < 0) { + char ch2 = seq.charAt(i + 1); + if (chFound && indexOfSequence(searchChars, ch2, 0) < 0) { return i; } } else { @@ -1724,7 +1724,7 @@ public class StringUtils { * StringUtils.containsOnly("abz", 'abc') = false * * - * @param cs the String to check, may be null + * @param cs the CharSequence to check, may be null * @param valid an array of valid chars, may be null * @return true if it only contains valid chars and is non-null * @since 3.0 Changed signature from containsOnly(String, char[]) to containsOnly(CharSequence, char[]) @@ -1761,16 +1761,16 @@ public class StringUtils { * * * @param cs the CharSequence to check, may be null - * @param validChars a String of valid chars, may be null + * @param validChars a CharSequence of valid chars, may be null * @return true if it only contains valid chars and is non-null * @since 2.0 - * @since 3.0 Changed signature from containsOnly(String, String) to containsOnly(CharSequence, String) + * @since 3.0 Changed signature from containsOnly(String, String) to containsOnly(CharSequence, CharSequence) */ - public static boolean containsOnly(CharSequence cs, String validChars) { + public static boolean containsOnly(CharSequence cs, CharSequence validChars) { if (cs == null || validChars == null) { return false; } - return containsOnly(cs, validChars.toCharArray()); + return containsOnly(cs, toCharArraySequence(validChars)); } // ContainsNone @@ -1846,16 +1846,16 @@ public class StringUtils { * * * @param cs the CharSequence to check, may be null - * @param invalidChars a String of invalid chars, may be null + * @param invalidChars a CharSequence of invalid chars, may be null * @return true if it contains none of the invalid chars, or is null * @since 2.0 - * @since 3.0 Changed signature from containsNone(String, String) to containsNone(CharSequence, String) + * @since 3.0 Changed signature from containsNone(String, String) to containsNone(CharSequence, CharSequence) */ - public static boolean containsNone(CharSequence cs, String invalidChars) { + public static boolean containsNone(CharSequence cs, CharSequence invalidChars) { if (cs == null || invalidChars == null) { return true; } - return containsNone(cs, invalidChars.toCharArray()); + return containsNone(cs, toCharArraySequence(invalidChars)); } // IndexOfAny strings @@ -1863,11 +1863,11 @@ public class StringUtils { /** *

Find the first index of any of a set of potential substrings.

* - *

A {@code null} String will return {@code -1}. + *

A {@code null} CharSequence will return {@code -1}. * A {@code null} or zero length search array will return {@code -1}. * A {@code null} search array entry will be ignored, but a search * array containing "" will return {@code 0} if {@code str} is not - * null. This method uses {@link String#indexOf(String)}.

+ * null. This method uses {@link String#indexOf(String)} if possible.

* *
      * StringUtils.indexOfAny(null, *)                     = -1
@@ -1882,11 +1882,11 @@ public class StringUtils {
      * StringUtils.indexOfAny("", ["a"])                   = -1
      * 
* - * @param str the String to check, may be null - * @param searchStrs the Strings to search for, may be null + * @param str the CharSequence to check, may be null + * @param searchStrs the CharSequences to search for, may be null * @return the first index of any of the searchStrs in str, -1 if no match */ - public static int indexOfAny(String str, String[] searchStrs) { + public static int indexOfAny(CharSequence str, CharSequence[] searchStrs) { if (str == null || searchStrs == null) { return INDEX_NOT_FOUND; } @@ -1897,11 +1897,11 @@ public class StringUtils { int tmp = 0; for (int i = 0; i < sz; i++) { - String search = searchStrs[i]; + CharSequence search = searchStrs[i]; if (search == null) { continue; } - tmp = str.indexOf(search); + tmp = indexOfSequence(str, search, 0); if (tmp == INDEX_NOT_FOUND) { continue; } @@ -1917,11 +1917,11 @@ public class StringUtils { /** *

Find the latest index of any of a set of potential substrings.

* - *

A {@code null} String will return {@code -1}. + *

A {@code null} CharSequence will return {@code -1}. * A {@code null} search array will return {@code -1}. * A {@code null} or zero length search array entry will be ignored, * but a search array containing "" will return the length of {@code str} - * if {@code str} is not null. This method uses {@link String#indexOf(String)}

+ * if {@code str} is not null. This method uses {@link String#indexOf(String)} if possible

* *
      * StringUtils.lastIndexOfAny(null, *)                   = -1
@@ -1935,11 +1935,11 @@ public class StringUtils {
      * StringUtils.lastIndexOfAny("zzabyycdxx", ["mn",""])   = 10
      * 
* - * @param str the String to check, may be null - * @param searchStrs the Strings to search for, may be null - * @return the last index of any of the Strings, -1 if no match + * @param str the CharSequence to check, may be null + * @param searchStrs the CharSequences to search for, may be null + * @return the last index of any of the CharSequences, -1 if no match */ - public static int lastIndexOfAny(String str, String[] searchStrs) { + public static int lastIndexOfAny(CharSequence str, CharSequence[] searchStrs) { if (str == null || searchStrs == null) { return INDEX_NOT_FOUND; } @@ -1947,11 +1947,11 @@ public class StringUtils { int ret = INDEX_NOT_FOUND; int tmp = 0; for (int i = 0; i < sz; i++) { - String search = searchStrs[i]; + CharSequence search = searchStrs[i]; if (search == null) { continue; } - tmp = str.lastIndexOf(search); + tmp = lastIndexOfSequence(str, search, str.length()); if (tmp > ret) { ret = tmp; } @@ -1962,13 +1962,13 @@ public class StringUtils { // Substring //----------------------------------------------------------------------- /** - *

Gets a substring from the specified String avoiding exceptions.

+ *

Gets a substring from the specified CharSequence avoiding exceptions.

* *

A negative start position can be used to start {@code n} - * characters from the end of the String.

+ * characters from the end of the CharSequence.

* - *

A {@code null} String will return {@code null}. - * An empty ("") String will return "".

+ *

A {@code null} CharSequence will return {@code null}. + * An empty ("") CharSequence will return "".

* *
      * StringUtils.substring(null, *)   = null
@@ -1980,12 +1980,12 @@ public class StringUtils {
      * StringUtils.substring("abc", -4) = "abc"
      * 
* - * @param str the String to get the substring from, may be null + * @param str the CharSequence to get the substring from, may be null * @param start the position to start from, negative means - * count back from the end of the String by this many characters - * @return substring from start position, {@code null} if null String input + * count back from the end of the CharSequence by this many characters + * @return substring from start position, {@code null} if null CharSequence input */ - public static String substring(String str, int start) { + public static String substring(CharSequence str, int start) { if (str == null) { return null; } @@ -2002,20 +2002,20 @@ public class StringUtils { return EMPTY; } - return str.substring(start); + return sequenceToString(str.subSequence(start, str.length())); } /** - *

Gets a substring from the specified String avoiding exceptions.

+ *

Gets a substring from the specified CharSequence avoiding exceptions.

* *

A negative start position can be used to start/end {@code n} - * characters from the end of the String.

+ * characters from the end of the CharSequence.

* *

The returned substring starts with the character in the {@code start} * position and ends before the {@code end} position. All position counting is * zero-based -- i.e., to start at the beginning of the string use * {@code start = 0}. Negative start and end positions can be used to - * specify offsets relative to the end of the String.

+ * specify offsets relative to the end of the CharSequence.

* *

If {@code start} is not strictly to the left of {@code end}, "" * is returned.

@@ -2032,15 +2032,15 @@ public class StringUtils { * StringUtils.substring("abc", -4, 2) = "ab" * * - * @param str the String to get the substring from, may be null + * @param str the CharSequence to get the substring from, may be null * @param start the position to start from, negative means - * count back from the end of the String by this many characters + * count back from the end of the CharSequence by this many characters * @param end the position to end at (exclusive), negative means - * count back from the end of the String by this many characters + * count back from the end of the CharSequence by this many characters * @return substring from start position to end positon, - * {@code null} if null String input + * {@code null} if null CharSequence input */ - public static String substring(String str, int start, int end) { + public static String substring(CharSequence str, int start, int end) { if (str == null) { return null; } @@ -2070,7 +2070,7 @@ public class StringUtils { end = 0; } - return str.substring(start, end); + return sequenceToString(str.subSequence(start, end)); } // Left/Right/Mid @@ -6416,6 +6416,9 @@ public class StringUtils { return cs == null ? null : cs.subSequence(start, cs.length()); } + // The following methods are used to support basic java.lang.String functionality + // for CharSequences + // Used by the indexOf(CharSequence methods) as a green implementation of indexOf static int indexOfSequence(CharSequence cs, int searchChar, int start) { if (cs instanceof String) { @@ -6446,4 +6449,56 @@ public class StringUtils { } } + // Used by the lastIndexOf(CharSequence methods) as a green implementation of lastIndexOf + static int lastIndexOfSequence(CharSequence cs, int searchChar, int start) { + if (cs instanceof String) { + return ((String) cs).lastIndexOf(searchChar, start); + } else { + int sz = cs.length(); + if ( start < 0 ) { + return -1; + } + if ( start >= sz ) { + start = sz - 1; + } + for ( int i=start; i >= 0; --i ) { + if ( cs.charAt(i) == searchChar) { + return i; + } + } + return -1; + } + } + // Used by the lastIndexOf(CharSequence methods) as a green implementation of lastIndexOf + static int lastIndexOfSequence(CharSequence cs, CharSequence searchChar, int start) { + if (cs instanceof String && searchChar instanceof String) { + // TODO: Do we assume searchChar is usually relatively small; + // If so then calling toString() on it is better than reverting to + // the green implementation in the else block + return ((String) cs).lastIndexOf( (String) searchChar, start); + } else { + // TODO: Implement rather than convert to String + return cs.toString().lastIndexOf(searchChar.toString(), start); + } + } + + // Green implementation of toCharArray + static char[] toCharArraySequence(CharSequence cs) { + if (cs instanceof String) { + return ((String) cs).toCharArray(); + } else { + // TODO: Implement rather than convert to String + return cs.toString().toCharArray(); + } + } + + // Convert a CharSequence to a String + static String sequenceToString(CharSequence cs) { + if (cs instanceof String) { + return ((String) cs); + } else { + return cs.toString(); + } + } + } diff --git a/src/test/java/org/apache/commons/lang3/StringUtilsEqualsIndexOfTest.java b/src/test/java/org/apache/commons/lang3/StringUtilsEqualsIndexOfTest.java index abe707a25..2f05e0523 100644 --- a/src/test/java/org/apache/commons/lang3/StringUtilsEqualsIndexOfTest.java +++ b/src/test/java/org/apache/commons/lang3/StringUtilsEqualsIndexOfTest.java @@ -675,6 +675,8 @@ public class StringUtilsEqualsIndexOfTest extends TestCase { assertEquals(-1, StringUtils.lastIndexOf("", ' ')); assertEquals(7, StringUtils.lastIndexOf("aabaabaa", 'a')); assertEquals(5, StringUtils.lastIndexOf("aabaabaa", 'b')); + + assertEquals(5, StringUtils.lastIndexOf(new StringBuilder("aabaabaa"), 'b')); } public void testLastIndexOf_charInt() { @@ -688,6 +690,8 @@ public class StringUtilsEqualsIndexOfTest extends TestCase { assertEquals(5, StringUtils.lastIndexOf("aabaabaa", 'b', 9)); assertEquals(-1, StringUtils.lastIndexOf("aabaabaa", 'b', -1)); assertEquals(0, StringUtils.lastIndexOf("aabaabaa", 'a', 0)); + + assertEquals(2, StringUtils.lastIndexOf(new StringBuilder("aabaabaa"), 'b', 2)); } public void testLastIndexOf_String() { @@ -699,6 +703,8 @@ public class StringUtilsEqualsIndexOfTest extends TestCase { assertEquals(7, StringUtils.lastIndexOf("aabaabaa", "a")); assertEquals(5, StringUtils.lastIndexOf("aabaabaa", "b")); assertEquals(4, StringUtils.lastIndexOf("aabaabaa", "ab")); + + assertEquals(4, StringUtils.lastIndexOf(new StringBuilder("aabaabaa"), "ab")); } public void testLastIndexOf_StringInt() { @@ -722,6 +728,8 @@ public class StringUtilsEqualsIndexOfTest extends TestCase { assertEquals(-1, StringUtils.lastIndexOf("aabaabaa", "b", -1)); assertEquals(-1, StringUtils.lastIndexOf("aabaabaa", "b", 0)); assertEquals(0, StringUtils.lastIndexOf("aabaabaa", "a", 0)); + + assertEquals(2, StringUtils.lastIndexOf(new StringBuilder("aabaabaa"), "b", 3)); } public void testLastIndexOfAny_StringStringArray() {