From 99d39eb2ee49ebebf1e856c7c284d35a602db66a Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 23 Oct 2017 19:03:42 +0200 Subject: [PATCH] Avoid temporary String creation in StringUtils.starts/endsWithIgnoreCase Issue: SPR-16095 --- .../org/springframework/util/StringUtils.java | 63 ++++------ .../util/StringUtilsTests.java | 116 ++++++++++++------ 2 files changed, 97 insertions(+), 82 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/util/StringUtils.java b/spring-core/src/main/java/org/springframework/util/StringUtils.java index be81cd84b3d..5e2fbfec154 100644 --- a/spring-core/src/main/java/org/springframework/util/StringUtils.java +++ b/spring-core/src/main/java/org/springframework/util/StringUtils.java @@ -135,17 +135,7 @@ public abstract class StringUtils { * @see Character#isWhitespace */ public static boolean hasText(CharSequence str) { - if (!hasLength(str)) { - return false; - } - - int strLen = str.length(); - for (int i = 0; i < strLen; i++) { - if (!Character.isWhitespace(str.charAt(i))) { - return true; - } - } - return false; + return (hasLength(str) && containsText(str)); } /** @@ -159,7 +149,17 @@ public abstract class StringUtils { * @see #hasText(CharSequence) */ public static boolean hasText(String str) { - return (str != null && !str.isEmpty() && hasText((CharSequence) str)); + return (hasLength(str) && containsText(str)); + } + + private static boolean containsText(CharSequence str) { + int strLen = str.length(); + for (int i = 0; i < strLen; i++) { + if (!Character.isWhitespace(str.charAt(i))) { + return true; + } + } + return false; } /** @@ -310,7 +310,6 @@ public abstract class StringUtils { return sb.toString(); } - /** * Test if the given {@code String} starts with the specified prefix, * ignoring upper/lower case. @@ -319,19 +318,8 @@ public abstract class StringUtils { * @see java.lang.String#startsWith */ public static boolean startsWithIgnoreCase(String str, String prefix) { - if (str == null || prefix == null) { - return false; - } - if (str.startsWith(prefix)) { - return true; - } - if (str.length() < prefix.length()) { - return false; - } - - String lcStr = str.substring(0, prefix.length()).toLowerCase(); - String lcPrefix = prefix.toLowerCase(); - return lcStr.equals(lcPrefix); + return (str != null && prefix != null && str.length() >= prefix.length() && + str.regionMatches(true, 0, prefix, 0, prefix.length())); } /** @@ -342,19 +330,8 @@ public abstract class StringUtils { * @see java.lang.String#endsWith */ public static boolean endsWithIgnoreCase(String str, String suffix) { - if (str == null || suffix == null) { - return false; - } - if (str.endsWith(suffix)) { - return true; - } - if (str.length() < suffix.length()) { - return false; - } - - String lcStr = str.substring(str.length() - suffix.length()).toLowerCase(); - String lcSuffix = suffix.toLowerCase(); - return lcStr.equals(lcSuffix); + return (str != null && suffix != null && str.length() >= suffix.length() && + str.regionMatches(true, str.length() - suffix.length(), suffix, 0, suffix.length())); } /** @@ -365,9 +342,11 @@ public abstract class StringUtils { * @param substring the substring to match at the given index */ public static boolean substringMatch(CharSequence str, int index, CharSequence substring) { - for (int j = 0; j < substring.length(); j++) { - int i = index + j; - if (i >= str.length() || str.charAt(i) != substring.charAt(j)) { + if (index + substring.length() > str.length()) { + return false; + } + for (int i = 0; i < substring.length(); i++) { + if (str.charAt(index + i) != substring.charAt(i)) { return false; } } diff --git a/spring-core/src/test/java/org/springframework/util/StringUtilsTests.java b/spring-core/src/test/java/org/springframework/util/StringUtilsTests.java index 2a4c61cc1ec..8ccffe38a63 100644 --- a/spring-core/src/test/java/org/springframework/util/StringUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/util/StringUtilsTests.java @@ -32,24 +32,24 @@ import static org.junit.Assert.*; public class StringUtilsTests { @Test - public void testHasTextBlank() throws Exception { + public void testHasTextBlank() { String blank = " "; assertEquals(false, StringUtils.hasText(blank)); } @Test - public void testHasTextNullEmpty() throws Exception { + public void testHasTextNullEmpty() { assertEquals(false, StringUtils.hasText(null)); assertEquals(false, StringUtils.hasText("")); } @Test - public void testHasTextValid() throws Exception { + public void testHasTextValid() { assertEquals(true, StringUtils.hasText("t")); } @Test - public void testContainsWhitespace() throws Exception { + public void testContainsWhitespace() { assertFalse(StringUtils.containsWhitespace(null)); assertFalse(StringUtils.containsWhitespace("")); assertFalse(StringUtils.containsWhitespace("a")); @@ -62,7 +62,7 @@ public class StringUtilsTests { } @Test - public void testTrimWhitespace() throws Exception { + public void testTrimWhitespace() { assertEquals(null, StringUtils.trimWhitespace(null)); assertEquals("", StringUtils.trimWhitespace("")); assertEquals("", StringUtils.trimWhitespace(" ")); @@ -75,7 +75,7 @@ public class StringUtilsTests { } @Test - public void testTrimAllWhitespace() throws Exception { + public void testTrimAllWhitespace() { assertEquals("", StringUtils.trimAllWhitespace("")); assertEquals("", StringUtils.trimAllWhitespace(" ")); assertEquals("", StringUtils.trimAllWhitespace("\t")); @@ -87,7 +87,7 @@ public class StringUtilsTests { } @Test - public void testTrimLeadingWhitespace() throws Exception { + public void testTrimLeadingWhitespace() { assertEquals(null, StringUtils.trimLeadingWhitespace(null)); assertEquals("", StringUtils.trimLeadingWhitespace("")); assertEquals("", StringUtils.trimLeadingWhitespace(" ")); @@ -100,7 +100,7 @@ public class StringUtilsTests { } @Test - public void testTrimTrailingWhitespace() throws Exception { + public void testTrimTrailingWhitespace() { assertEquals(null, StringUtils.trimTrailingWhitespace(null)); assertEquals("", StringUtils.trimTrailingWhitespace("")); assertEquals("", StringUtils.trimTrailingWhitespace(" ")); @@ -113,7 +113,7 @@ public class StringUtilsTests { } @Test - public void testTrimLeadingCharacter() throws Exception { + public void testTrimLeadingCharacter() { assertEquals(null, StringUtils.trimLeadingCharacter(null, ' ')); assertEquals("", StringUtils.trimLeadingCharacter("", ' ')); assertEquals("", StringUtils.trimLeadingCharacter(" ", ' ')); @@ -126,7 +126,7 @@ public class StringUtilsTests { } @Test - public void testTrimTrailingCharacter() throws Exception { + public void testTrimTrailingCharacter() { assertEquals(null, StringUtils.trimTrailingCharacter(null, ' ')); assertEquals("", StringUtils.trimTrailingCharacter("", ' ')); assertEquals("", StringUtils.trimTrailingCharacter(" ", ' ')); @@ -138,6 +138,60 @@ public class StringUtilsTests { assertEquals(" a b c", StringUtils.trimTrailingCharacter(" a b c ", ' ')); } + @Test + public void testStartsWithIgnoreCase() { + String prefix = "fOo"; + assertTrue(StringUtils.startsWithIgnoreCase("foo", prefix)); + assertTrue(StringUtils.startsWithIgnoreCase("Foo", prefix)); + assertTrue(StringUtils.startsWithIgnoreCase("foobar", prefix)); + assertTrue(StringUtils.startsWithIgnoreCase("foobarbar", prefix)); + assertTrue(StringUtils.startsWithIgnoreCase("Foobar", prefix)); + assertTrue(StringUtils.startsWithIgnoreCase("FoobarBar", prefix)); + assertTrue(StringUtils.startsWithIgnoreCase("foObar", prefix)); + assertTrue(StringUtils.startsWithIgnoreCase("FOObar", prefix)); + assertTrue(StringUtils.startsWithIgnoreCase("fOobar", prefix)); + assertFalse(StringUtils.startsWithIgnoreCase(null, prefix)); + assertFalse(StringUtils.startsWithIgnoreCase("fOobar", null)); + assertFalse(StringUtils.startsWithIgnoreCase("b", prefix)); + assertFalse(StringUtils.startsWithIgnoreCase("barfoo", prefix)); + assertFalse(StringUtils.startsWithIgnoreCase("barfoobar", prefix)); + } + + @Test + public void testEndsWithIgnoreCase() { + String suffix = "fOo"; + assertTrue(StringUtils.endsWithIgnoreCase("foo", suffix)); + assertTrue(StringUtils.endsWithIgnoreCase("Foo", suffix)); + assertTrue(StringUtils.endsWithIgnoreCase("barfoo", suffix)); + assertTrue(StringUtils.endsWithIgnoreCase("barbarfoo", suffix)); + assertTrue(StringUtils.endsWithIgnoreCase("barFoo", suffix)); + assertTrue(StringUtils.endsWithIgnoreCase("barBarFoo", suffix)); + assertTrue(StringUtils.endsWithIgnoreCase("barfoO", suffix)); + assertTrue(StringUtils.endsWithIgnoreCase("barFOO", suffix)); + assertTrue(StringUtils.endsWithIgnoreCase("barfOo", suffix)); + assertFalse(StringUtils.endsWithIgnoreCase(null, suffix)); + assertFalse(StringUtils.endsWithIgnoreCase("barfOo", null)); + assertFalse(StringUtils.endsWithIgnoreCase("b", suffix)); + assertFalse(StringUtils.endsWithIgnoreCase("foobar", suffix)); + assertFalse(StringUtils.endsWithIgnoreCase("barfoobar", suffix)); + } + + @Test + public void testSubstringMatch() { + assertTrue(StringUtils.substringMatch("foo", 0, "foo")); + assertTrue(StringUtils.substringMatch("foo", 1, "oo")); + assertTrue(StringUtils.substringMatch("foo", 2, "o")); + assertFalse(StringUtils.substringMatch("foo", 0, "fOo")); + assertFalse(StringUtils.substringMatch("foo", 1, "fOo")); + assertFalse(StringUtils.substringMatch("foo", 2, "fOo")); + assertFalse(StringUtils.substringMatch("foo", 3, "fOo")); + assertFalse(StringUtils.substringMatch("foo", 1, "Oo")); + assertFalse(StringUtils.substringMatch("foo", 2, "Oo")); + assertFalse(StringUtils.substringMatch("foo", 3, "Oo")); + assertFalse(StringUtils.substringMatch("foo", 2, "O")); + assertFalse(StringUtils.substringMatch("foo", 3, "O")); + } + @Test public void testCountOccurrencesOf() { assertTrue("nullx2 = 0", @@ -166,7 +220,7 @@ public class StringUtilsTests { } @Test - public void testReplace() throws Exception { + public void testReplace() { String inString = "a6AazAaa77abaa"; String oldPattern = "aa"; String newPattern = "foo"; @@ -189,7 +243,7 @@ public class StringUtilsTests { } @Test - public void testDelete() throws Exception { + public void testDelete() { String inString = "The quick brown fox jumped over the lazy dog"; String noThe = StringUtils.delete(inString, "the"); @@ -216,7 +270,7 @@ public class StringUtilsTests { } @Test - public void testDeleteAny() throws Exception { + public void testDeleteAny() { String inString = "Able was I ere I saw Elba"; String res = StringUtils.deleteAny(inString, "I"); @@ -301,7 +355,6 @@ public class StringUtilsTests { @Test public void testStripFilenameExtension() { - assertEquals(null, StringUtils.stripFilenameExtension(null)); assertEquals("", StringUtils.stripFilenameExtension("")); assertEquals("myfile", StringUtils.stripFilenameExtension("myfile")); assertEquals("myfile", StringUtils.stripFilenameExtension("myfile.")); @@ -580,26 +633,9 @@ public class StringUtilsTests { assertTrue("Output equals input", Arrays.equals(sa, components)); } - @Test - public void testEndsWithIgnoreCase() { - String suffix = "fOo"; - assertTrue(StringUtils.endsWithIgnoreCase("foo", suffix)); - assertTrue(StringUtils.endsWithIgnoreCase("Foo", suffix)); - assertTrue(StringUtils.endsWithIgnoreCase("barfoo", suffix)); - assertTrue(StringUtils.endsWithIgnoreCase("barbarfoo", suffix)); - assertTrue(StringUtils.endsWithIgnoreCase("barFoo", suffix)); - assertTrue(StringUtils.endsWithIgnoreCase("barBarFoo", suffix)); - assertTrue(StringUtils.endsWithIgnoreCase("barfoO", suffix)); - assertTrue(StringUtils.endsWithIgnoreCase("barFOO", suffix)); - assertTrue(StringUtils.endsWithIgnoreCase("barfOo", suffix)); - assertFalse(StringUtils.endsWithIgnoreCase(null, suffix)); - assertFalse(StringUtils.endsWithIgnoreCase("barfOo", null)); - assertFalse(StringUtils.endsWithIgnoreCase("b", suffix)); - } - @Test - public void testParseLocaleStringSunnyDay() throws Exception { + public void testParseLocaleStringSunnyDay() { Locale expectedLocale = Locale.UK; Locale locale = StringUtils.parseLocaleString(expectedLocale.toString()); assertNotNull("When given a bona-fide Locale string, must not return null.", locale); @@ -607,19 +643,19 @@ public class StringUtilsTests { } @Test - public void testParseLocaleStringWithMalformedLocaleString() throws Exception { + public void testParseLocaleStringWithMalformedLocaleString() { Locale locale = StringUtils.parseLocaleString("_banjo_on_my_knee"); assertNotNull("When given a malformed Locale string, must not return null.", locale); } @Test - public void testParseLocaleStringWithEmptyLocaleStringYieldsNullLocale() throws Exception { + public void testParseLocaleStringWithEmptyLocaleStringYieldsNullLocale() { Locale locale = StringUtils.parseLocaleString(""); assertNull("When given an empty Locale string, must return null.", locale); } @Test // SPR-8637 - public void testParseLocaleWithMultiSpecialCharactersInVariant() throws Exception { + public void testParseLocaleWithMultiSpecialCharactersInVariant() { String variant = "proper-northern"; String localeString = "en_GB_" + variant; Locale locale = StringUtils.parseLocaleString(localeString); @@ -627,7 +663,7 @@ public class StringUtilsTests { } @Test // SPR-3671 - public void testParseLocaleWithMultiValuedVariant() throws Exception { + public void testParseLocaleWithMultiValuedVariant() { String variant = "proper_northern"; String localeString = "en_GB_" + variant; Locale locale = StringUtils.parseLocaleString(localeString); @@ -635,7 +671,7 @@ public class StringUtilsTests { } @Test // SPR-3671 - public void testParseLocaleWithMultiValuedVariantUsingSpacesAsSeparators() throws Exception { + public void testParseLocaleWithMultiValuedVariantUsingSpacesAsSeparators() { String variant = "proper northern"; String localeString = "en GB " + variant; Locale locale = StringUtils.parseLocaleString(localeString); @@ -643,7 +679,7 @@ public class StringUtilsTests { } @Test // SPR-3671 - public void testParseLocaleWithMultiValuedVariantUsingMixtureOfUnderscoresAndSpacesAsSeparators() throws Exception { + public void testParseLocaleWithMultiValuedVariantUsingMixtureOfUnderscoresAndSpacesAsSeparators() { String variant = "proper northern"; String localeString = "en_GB_" + variant; Locale locale = StringUtils.parseLocaleString(localeString); @@ -651,7 +687,7 @@ public class StringUtilsTests { } @Test // SPR-3671 - public void testParseLocaleWithMultiValuedVariantUsingSpacesAsSeparatorsWithLotsOfLeadingWhitespace() throws Exception { + public void testParseLocaleWithMultiValuedVariantUsingSpacesAsSeparatorsWithLotsOfLeadingWhitespace() { String variant = "proper northern"; String localeString = "en GB " + variant; // lots of whitespace Locale locale = StringUtils.parseLocaleString(localeString); @@ -659,7 +695,7 @@ public class StringUtilsTests { } @Test // SPR-3671 - public void testParseLocaleWithMultiValuedVariantUsingUnderscoresAsSeparatorsWithLotsOfLeadingWhitespace() throws Exception { + public void testParseLocaleWithMultiValuedVariantUsingUnderscoresAsSeparatorsWithLotsOfLeadingWhitespace() { String variant = "proper_northern"; String localeString = "en_GB_____" + variant; // lots of underscores Locale locale = StringUtils.parseLocaleString(localeString);