Skip to content

Commit 99d39eb

Browse files
committed
Avoid temporary String creation in StringUtils.starts/endsWithIgnoreCase
Issue: SPR-16095
1 parent c32c9ec commit 99d39eb

File tree

2 files changed

+97
-82
lines changed

2 files changed

+97
-82
lines changed

spring-core/src/main/java/org/springframework/util/StringUtils.java

Lines changed: 21 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -135,17 +135,7 @@ public static boolean hasLength(String str) {
135135
* @see Character#isWhitespace
136136
*/
137137
public static boolean hasText(CharSequence str) {
138-
if (!hasLength(str)) {
139-
return false;
140-
}
141-
142-
int strLen = str.length();
143-
for (int i = 0; i < strLen; i++) {
144-
if (!Character.isWhitespace(str.charAt(i))) {
145-
return true;
146-
}
147-
}
148-
return false;
138+
return (hasLength(str) && containsText(str));
149139
}
150140

151141
/**
@@ -159,7 +149,17 @@ public static boolean hasText(CharSequence str) {
159149
* @see #hasText(CharSequence)
160150
*/
161151
public static boolean hasText(String str) {
162-
return (str != null && !str.isEmpty() && hasText((CharSequence) str));
152+
return (hasLength(str) && containsText(str));
153+
}
154+
155+
private static boolean containsText(CharSequence str) {
156+
int strLen = str.length();
157+
for (int i = 0; i < strLen; i++) {
158+
if (!Character.isWhitespace(str.charAt(i))) {
159+
return true;
160+
}
161+
}
162+
return false;
163163
}
164164

165165
/**
@@ -310,7 +310,6 @@ public static String trimTrailingCharacter(String str, char trailingCharacter) {
310310
return sb.toString();
311311
}
312312

313-
314313
/**
315314
* Test if the given {@code String} starts with the specified prefix,
316315
* ignoring upper/lower case.
@@ -319,19 +318,8 @@ public static String trimTrailingCharacter(String str, char trailingCharacter) {
319318
* @see java.lang.String#startsWith
320319
*/
321320
public static boolean startsWithIgnoreCase(String str, String prefix) {
322-
if (str == null || prefix == null) {
323-
return false;
324-
}
325-
if (str.startsWith(prefix)) {
326-
return true;
327-
}
328-
if (str.length() < prefix.length()) {
329-
return false;
330-
}
331-
332-
String lcStr = str.substring(0, prefix.length()).toLowerCase();
333-
String lcPrefix = prefix.toLowerCase();
334-
return lcStr.equals(lcPrefix);
321+
return (str != null && prefix != null && str.length() >= prefix.length() &&
322+
str.regionMatches(true, 0, prefix, 0, prefix.length()));
335323
}
336324

337325
/**
@@ -342,19 +330,8 @@ public static boolean startsWithIgnoreCase(String str, String prefix) {
342330
* @see java.lang.String#endsWith
343331
*/
344332
public static boolean endsWithIgnoreCase(String str, String suffix) {
345-
if (str == null || suffix == null) {
346-
return false;
347-
}
348-
if (str.endsWith(suffix)) {
349-
return true;
350-
}
351-
if (str.length() < suffix.length()) {
352-
return false;
353-
}
354-
355-
String lcStr = str.substring(str.length() - suffix.length()).toLowerCase();
356-
String lcSuffix = suffix.toLowerCase();
357-
return lcStr.equals(lcSuffix);
333+
return (str != null && suffix != null && str.length() >= suffix.length() &&
334+
str.regionMatches(true, str.length() - suffix.length(), suffix, 0, suffix.length()));
358335
}
359336

360337
/**
@@ -365,9 +342,11 @@ public static boolean endsWithIgnoreCase(String str, String suffix) {
365342
* @param substring the substring to match at the given index
366343
*/
367344
public static boolean substringMatch(CharSequence str, int index, CharSequence substring) {
368-
for (int j = 0; j < substring.length(); j++) {
369-
int i = index + j;
370-
if (i >= str.length() || str.charAt(i) != substring.charAt(j)) {
345+
if (index + substring.length() > str.length()) {
346+
return false;
347+
}
348+
for (int i = 0; i < substring.length(); i++) {
349+
if (str.charAt(index + i) != substring.charAt(i)) {
371350
return false;
372351
}
373352
}

spring-core/src/test/java/org/springframework/util/StringUtilsTests.java

Lines changed: 76 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -32,24 +32,24 @@
3232
public class StringUtilsTests {
3333

3434
@Test
35-
public void testHasTextBlank() throws Exception {
35+
public void testHasTextBlank() {
3636
String blank = " ";
3737
assertEquals(false, StringUtils.hasText(blank));
3838
}
3939

4040
@Test
41-
public void testHasTextNullEmpty() throws Exception {
41+
public void testHasTextNullEmpty() {
4242
assertEquals(false, StringUtils.hasText(null));
4343
assertEquals(false, StringUtils.hasText(""));
4444
}
4545

4646
@Test
47-
public void testHasTextValid() throws Exception {
47+
public void testHasTextValid() {
4848
assertEquals(true, StringUtils.hasText("t"));
4949
}
5050

5151
@Test
52-
public void testContainsWhitespace() throws Exception {
52+
public void testContainsWhitespace() {
5353
assertFalse(StringUtils.containsWhitespace(null));
5454
assertFalse(StringUtils.containsWhitespace(""));
5555
assertFalse(StringUtils.containsWhitespace("a"));
@@ -62,7 +62,7 @@ public void testContainsWhitespace() throws Exception {
6262
}
6363

6464
@Test
65-
public void testTrimWhitespace() throws Exception {
65+
public void testTrimWhitespace() {
6666
assertEquals(null, StringUtils.trimWhitespace(null));
6767
assertEquals("", StringUtils.trimWhitespace(""));
6868
assertEquals("", StringUtils.trimWhitespace(" "));
@@ -75,7 +75,7 @@ public void testTrimWhitespace() throws Exception {
7575
}
7676

7777
@Test
78-
public void testTrimAllWhitespace() throws Exception {
78+
public void testTrimAllWhitespace() {
7979
assertEquals("", StringUtils.trimAllWhitespace(""));
8080
assertEquals("", StringUtils.trimAllWhitespace(" "));
8181
assertEquals("", StringUtils.trimAllWhitespace("\t"));
@@ -87,7 +87,7 @@ public void testTrimAllWhitespace() throws Exception {
8787
}
8888

8989
@Test
90-
public void testTrimLeadingWhitespace() throws Exception {
90+
public void testTrimLeadingWhitespace() {
9191
assertEquals(null, StringUtils.trimLeadingWhitespace(null));
9292
assertEquals("", StringUtils.trimLeadingWhitespace(""));
9393
assertEquals("", StringUtils.trimLeadingWhitespace(" "));
@@ -100,7 +100,7 @@ public void testTrimLeadingWhitespace() throws Exception {
100100
}
101101

102102
@Test
103-
public void testTrimTrailingWhitespace() throws Exception {
103+
public void testTrimTrailingWhitespace() {
104104
assertEquals(null, StringUtils.trimTrailingWhitespace(null));
105105
assertEquals("", StringUtils.trimTrailingWhitespace(""));
106106
assertEquals("", StringUtils.trimTrailingWhitespace(" "));
@@ -113,7 +113,7 @@ public void testTrimTrailingWhitespace() throws Exception {
113113
}
114114

115115
@Test
116-
public void testTrimLeadingCharacter() throws Exception {
116+
public void testTrimLeadingCharacter() {
117117
assertEquals(null, StringUtils.trimLeadingCharacter(null, ' '));
118118
assertEquals("", StringUtils.trimLeadingCharacter("", ' '));
119119
assertEquals("", StringUtils.trimLeadingCharacter(" ", ' '));
@@ -126,7 +126,7 @@ public void testTrimLeadingCharacter() throws Exception {
126126
}
127127

128128
@Test
129-
public void testTrimTrailingCharacter() throws Exception {
129+
public void testTrimTrailingCharacter() {
130130
assertEquals(null, StringUtils.trimTrailingCharacter(null, ' '));
131131
assertEquals("", StringUtils.trimTrailingCharacter("", ' '));
132132
assertEquals("", StringUtils.trimTrailingCharacter(" ", ' '));
@@ -138,6 +138,60 @@ public void testTrimTrailingCharacter() throws Exception {
138138
assertEquals(" a b c", StringUtils.trimTrailingCharacter(" a b c ", ' '));
139139
}
140140

141+
@Test
142+
public void testStartsWithIgnoreCase() {
143+
String prefix = "fOo";
144+
assertTrue(StringUtils.startsWithIgnoreCase("foo", prefix));
145+
assertTrue(StringUtils.startsWithIgnoreCase("Foo", prefix));
146+
assertTrue(StringUtils.startsWithIgnoreCase("foobar", prefix));
147+
assertTrue(StringUtils.startsWithIgnoreCase("foobarbar", prefix));
148+
assertTrue(StringUtils.startsWithIgnoreCase("Foobar", prefix));
149+
assertTrue(StringUtils.startsWithIgnoreCase("FoobarBar", prefix));
150+
assertTrue(StringUtils.startsWithIgnoreCase("foObar", prefix));
151+
assertTrue(StringUtils.startsWithIgnoreCase("FOObar", prefix));
152+
assertTrue(StringUtils.startsWithIgnoreCase("fOobar", prefix));
153+
assertFalse(StringUtils.startsWithIgnoreCase(null, prefix));
154+
assertFalse(StringUtils.startsWithIgnoreCase("fOobar", null));
155+
assertFalse(StringUtils.startsWithIgnoreCase("b", prefix));
156+
assertFalse(StringUtils.startsWithIgnoreCase("barfoo", prefix));
157+
assertFalse(StringUtils.startsWithIgnoreCase("barfoobar", prefix));
158+
}
159+
160+
@Test
161+
public void testEndsWithIgnoreCase() {
162+
String suffix = "fOo";
163+
assertTrue(StringUtils.endsWithIgnoreCase("foo", suffix));
164+
assertTrue(StringUtils.endsWithIgnoreCase("Foo", suffix));
165+
assertTrue(StringUtils.endsWithIgnoreCase("barfoo", suffix));
166+
assertTrue(StringUtils.endsWithIgnoreCase("barbarfoo", suffix));
167+
assertTrue(StringUtils.endsWithIgnoreCase("barFoo", suffix));
168+
assertTrue(StringUtils.endsWithIgnoreCase("barBarFoo", suffix));
169+
assertTrue(StringUtils.endsWithIgnoreCase("barfoO", suffix));
170+
assertTrue(StringUtils.endsWithIgnoreCase("barFOO", suffix));
171+
assertTrue(StringUtils.endsWithIgnoreCase("barfOo", suffix));
172+
assertFalse(StringUtils.endsWithIgnoreCase(null, suffix));
173+
assertFalse(StringUtils.endsWithIgnoreCase("barfOo", null));
174+
assertFalse(StringUtils.endsWithIgnoreCase("b", suffix));
175+
assertFalse(StringUtils.endsWithIgnoreCase("foobar", suffix));
176+
assertFalse(StringUtils.endsWithIgnoreCase("barfoobar", suffix));
177+
}
178+
179+
@Test
180+
public void testSubstringMatch() {
181+
assertTrue(StringUtils.substringMatch("foo", 0, "foo"));
182+
assertTrue(StringUtils.substringMatch("foo", 1, "oo"));
183+
assertTrue(StringUtils.substringMatch("foo", 2, "o"));
184+
assertFalse(StringUtils.substringMatch("foo", 0, "fOo"));
185+
assertFalse(StringUtils.substringMatch("foo", 1, "fOo"));
186+
assertFalse(StringUtils.substringMatch("foo", 2, "fOo"));
187+
assertFalse(StringUtils.substringMatch("foo", 3, "fOo"));
188+
assertFalse(StringUtils.substringMatch("foo", 1, "Oo"));
189+
assertFalse(StringUtils.substringMatch("foo", 2, "Oo"));
190+
assertFalse(StringUtils.substringMatch("foo", 3, "Oo"));
191+
assertFalse(StringUtils.substringMatch("foo", 2, "O"));
192+
assertFalse(StringUtils.substringMatch("foo", 3, "O"));
193+
}
194+
141195
@Test
142196
public void testCountOccurrencesOf() {
143197
assertTrue("nullx2 = 0",
@@ -166,7 +220,7 @@ public void testCountOccurrencesOf() {
166220
}
167221

168222
@Test
169-
public void testReplace() throws Exception {
223+
public void testReplace() {
170224
String inString = "a6AazAaa77abaa";
171225
String oldPattern = "aa";
172226
String newPattern = "foo";
@@ -189,7 +243,7 @@ public void testReplace() throws Exception {
189243
}
190244

191245
@Test
192-
public void testDelete() throws Exception {
246+
public void testDelete() {
193247
String inString = "The quick brown fox jumped over the lazy dog";
194248

195249
String noThe = StringUtils.delete(inString, "the");
@@ -216,7 +270,7 @@ public void testDelete() throws Exception {
216270
}
217271

218272
@Test
219-
public void testDeleteAny() throws Exception {
273+
public void testDeleteAny() {
220274
String inString = "Able was I ere I saw Elba";
221275

222276
String res = StringUtils.deleteAny(inString, "I");
@@ -301,7 +355,6 @@ public void testGetFilenameExtension() {
301355

302356
@Test
303357
public void testStripFilenameExtension() {
304-
assertEquals(null, StringUtils.stripFilenameExtension(null));
305358
assertEquals("", StringUtils.stripFilenameExtension(""));
306359
assertEquals("myfile", StringUtils.stripFilenameExtension("myfile"));
307360
assertEquals("myfile", StringUtils.stripFilenameExtension("myfile."));
@@ -580,86 +633,69 @@ private void doTestCommaDelimitedListToStringArrayLegalMatch(String[] components
580633
assertTrue("Output equals input", Arrays.equals(sa, components));
581634
}
582635

583-
@Test
584-
public void testEndsWithIgnoreCase() {
585-
String suffix = "fOo";
586-
assertTrue(StringUtils.endsWithIgnoreCase("foo", suffix));
587-
assertTrue(StringUtils.endsWithIgnoreCase("Foo", suffix));
588-
assertTrue(StringUtils.endsWithIgnoreCase("barfoo", suffix));
589-
assertTrue(StringUtils.endsWithIgnoreCase("barbarfoo", suffix));
590-
assertTrue(StringUtils.endsWithIgnoreCase("barFoo", suffix));
591-
assertTrue(StringUtils.endsWithIgnoreCase("barBarFoo", suffix));
592-
assertTrue(StringUtils.endsWithIgnoreCase("barfoO", suffix));
593-
assertTrue(StringUtils.endsWithIgnoreCase("barFOO", suffix));
594-
assertTrue(StringUtils.endsWithIgnoreCase("barfOo", suffix));
595-
assertFalse(StringUtils.endsWithIgnoreCase(null, suffix));
596-
assertFalse(StringUtils.endsWithIgnoreCase("barfOo", null));
597-
assertFalse(StringUtils.endsWithIgnoreCase("b", suffix));
598-
}
599-
600636

601637
@Test
602-
public void testParseLocaleStringSunnyDay() throws Exception {
638+
public void testParseLocaleStringSunnyDay() {
603639
Locale expectedLocale = Locale.UK;
604640
Locale locale = StringUtils.parseLocaleString(expectedLocale.toString());
605641
assertNotNull("When given a bona-fide Locale string, must not return null.", locale);
606642
assertEquals(expectedLocale, locale);
607643
}
608644

609645
@Test
610-
public void testParseLocaleStringWithMalformedLocaleString() throws Exception {
646+
public void testParseLocaleStringWithMalformedLocaleString() {
611647
Locale locale = StringUtils.parseLocaleString("_banjo_on_my_knee");
612648
assertNotNull("When given a malformed Locale string, must not return null.", locale);
613649
}
614650

615651
@Test
616-
public void testParseLocaleStringWithEmptyLocaleStringYieldsNullLocale() throws Exception {
652+
public void testParseLocaleStringWithEmptyLocaleStringYieldsNullLocale() {
617653
Locale locale = StringUtils.parseLocaleString("");
618654
assertNull("When given an empty Locale string, must return null.", locale);
619655
}
620656

621657
@Test // SPR-8637
622-
public void testParseLocaleWithMultiSpecialCharactersInVariant() throws Exception {
658+
public void testParseLocaleWithMultiSpecialCharactersInVariant() {
623659
String variant = "proper-northern";
624660
String localeString = "en_GB_" + variant;
625661
Locale locale = StringUtils.parseLocaleString(localeString);
626662
assertEquals("Multi-valued variant portion of the Locale not extracted correctly.", variant, locale.getVariant());
627663
}
628664

629665
@Test // SPR-3671
630-
public void testParseLocaleWithMultiValuedVariant() throws Exception {
666+
public void testParseLocaleWithMultiValuedVariant() {
631667
String variant = "proper_northern";
632668
String localeString = "en_GB_" + variant;
633669
Locale locale = StringUtils.parseLocaleString(localeString);
634670
assertEquals("Multi-valued variant portion of the Locale not extracted correctly.", variant, locale.getVariant());
635671
}
636672

637673
@Test // SPR-3671
638-
public void testParseLocaleWithMultiValuedVariantUsingSpacesAsSeparators() throws Exception {
674+
public void testParseLocaleWithMultiValuedVariantUsingSpacesAsSeparators() {
639675
String variant = "proper northern";
640676
String localeString = "en GB " + variant;
641677
Locale locale = StringUtils.parseLocaleString(localeString);
642678
assertEquals("Multi-valued variant portion of the Locale not extracted correctly.", variant, locale.getVariant());
643679
}
644680

645681
@Test // SPR-3671
646-
public void testParseLocaleWithMultiValuedVariantUsingMixtureOfUnderscoresAndSpacesAsSeparators() throws Exception {
682+
public void testParseLocaleWithMultiValuedVariantUsingMixtureOfUnderscoresAndSpacesAsSeparators() {
647683
String variant = "proper northern";
648684
String localeString = "en_GB_" + variant;
649685
Locale locale = StringUtils.parseLocaleString(localeString);
650686
assertEquals("Multi-valued variant portion of the Locale not extracted correctly.", variant, locale.getVariant());
651687
}
652688

653689
@Test // SPR-3671
654-
public void testParseLocaleWithMultiValuedVariantUsingSpacesAsSeparatorsWithLotsOfLeadingWhitespace() throws Exception {
690+
public void testParseLocaleWithMultiValuedVariantUsingSpacesAsSeparatorsWithLotsOfLeadingWhitespace() {
655691
String variant = "proper northern";
656692
String localeString = "en GB " + variant; // lots of whitespace
657693
Locale locale = StringUtils.parseLocaleString(localeString);
658694
assertEquals("Multi-valued variant portion of the Locale not extracted correctly.", variant, locale.getVariant());
659695
}
660696

661697
@Test // SPR-3671
662-
public void testParseLocaleWithMultiValuedVariantUsingUnderscoresAsSeparatorsWithLotsOfLeadingWhitespace() throws Exception {
698+
public void testParseLocaleWithMultiValuedVariantUsingUnderscoresAsSeparatorsWithLotsOfLeadingWhitespace() {
663699
String variant = "proper_northern";
664700
String localeString = "en_GB_____" + variant; // lots of underscores
665701
Locale locale = StringUtils.parseLocaleString(localeString);

0 commit comments

Comments
 (0)