Skip to content

Commit 4bf1fbb

Browse files
committed
8303648: Add String.indexOf(String str, int beginIndex, int endIndex)
Reviewed-by: rriggs
1 parent c4df9b5 commit 4bf1fbb

File tree

2 files changed

+247
-15
lines changed

2 files changed

+247
-15
lines changed

src/java.base/share/classes/java/lang/String.java

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2607,6 +2607,19 @@ public int indexOf(String str) {
26072607
* }</pre>
26082608
* If no such value of {@code k} exists, then {@code -1} is returned.
26092609
*
2610+
* @apiNote
2611+
* Unlike {@link #substring(int)}, for example, this method does not throw
2612+
* an exception when {@code fromIndex} is outside the valid range.
2613+
* Rather, it returns -1 when {@code fromIndex} is larger than the length of
2614+
* the string.
2615+
* This result is, by itself, indistinguishable from a genuine absence of
2616+
* {@code str} in the string.
2617+
* If stricter behavior is needed, {@link #indexOf(String, int, int)}
2618+
* should be considered instead.
2619+
* On {@link String} {@code s} and a non-empty {@code str}, for example,
2620+
* {@code s.indexOf(str, fromIndex, s.length())} would throw if
2621+
* {@code fromIndex} were larger than the string length, or were negative.
2622+
*
26102623
* @param str the substring to search for.
26112624
* @param fromIndex the index from which to start the search.
26122625
* @return the index of the first occurrence of the specified substring,
@@ -2617,35 +2630,63 @@ public int indexOf(String str, int fromIndex) {
26172630
return indexOf(value, coder(), length(), str, fromIndex);
26182631
}
26192632

2633+
/**
2634+
* Returns the index of the first occurrence of the specified substring
2635+
* within the specified index range of {@code this} string.
2636+
*
2637+
* <p>This method returns the same result as the one of the invocation
2638+
* <pre>{@code
2639+
* s.substring(beginIndex, endIndex).indexOf(str) + beginIndex
2640+
* }</pre>
2641+
* if the index returned by {@link #indexOf(String)} is non-negative,
2642+
* and returns -1 otherwise.
2643+
* (No substring is instantiated, though.)
2644+
*
2645+
* @param str the substring to search for.
2646+
* @param beginIndex the index to start the search from (included).
2647+
* @param endIndex the index to stop the search at (excluded).
2648+
* @return the index of the first occurrence of the specified substring
2649+
* within the specified index range,
2650+
* or {@code -1} if there is no such occurrence.
2651+
* @throws StringIndexOutOfBoundsException if {@code beginIndex}
2652+
* is negative, or {@code endIndex} is larger than the length of
2653+
* this {@code String} object, or {@code beginIndex} is larger than
2654+
* {@code endIndex}.
2655+
* @since 21
2656+
*/
2657+
public int indexOf(String str, int beginIndex, int endIndex) {
2658+
if (str.length() == 1) {
2659+
/* Simple optimization, can be omitted without behavioral impact */
2660+
return indexOf(str.charAt(0), beginIndex, endIndex);
2661+
}
2662+
checkBoundsBeginEnd(beginIndex, endIndex, length());
2663+
return indexOf(value, coder(), endIndex, str, beginIndex);
2664+
}
2665+
26202666
/**
26212667
* Code shared by String and AbstractStringBuilder to do searches. The
26222668
* source is the character array being searched, and the target
26232669
* is the string being searched for.
26242670
*
26252671
* @param src the characters being searched.
26262672
* @param srcCoder the coder of the source string.
2627-
* @param srcCount length of the source string.
2673+
* @param srcCount last index (exclusive) in the source string.
26282674
* @param tgtStr the characters being searched for.
26292675
* @param fromIndex the index to begin searching from.
26302676
*/
26312677
static int indexOf(byte[] src, byte srcCoder, int srcCount,
26322678
String tgtStr, int fromIndex) {
2633-
byte[] tgt = tgtStr.value;
2634-
byte tgtCoder = tgtStr.coder();
2635-
int tgtCount = tgtStr.length();
2636-
2637-
if (fromIndex >= srcCount) {
2638-
return (tgtCount == 0 ? srcCount : -1);
2639-
}
2640-
if (fromIndex < 0) {
2641-
fromIndex = 0;
2679+
fromIndex = Math.clamp(fromIndex, 0, srcCount);
2680+
int tgtCount = tgtStr.length();
2681+
if (tgtCount > srcCount - fromIndex) {
2682+
return -1;
26422683
}
26432684
if (tgtCount == 0) {
26442685
return fromIndex;
26452686
}
2646-
if (tgtCount > srcCount) {
2647-
return -1;
2648-
}
2687+
2688+
byte[] tgt = tgtStr.value;
2689+
byte tgtCoder = tgtStr.coder();
26492690
if (srcCoder == tgtCoder) {
26502691
return srcCoder == LATIN1
26512692
? StringLatin1.indexOf(src, srcCount, tgt, tgtCount, fromIndex)

test/jdk/java/lang/String/IndexOfBeginEnd.java

Lines changed: 193 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@
2929

3030
/*
3131
* @test
32-
* @bug 8302590
33-
* @summary This one is for String.indexOf(int,int,int).
32+
* @bug 8302590 8303648
33+
* @summary This one is for String.indexOf([int|String],int,int).
3434
* @run testng IndexOfBeginEnd
3535
*/
3636

@@ -195,6 +195,183 @@ public Object[][] exceptions() {
195195
};
196196
}
197197

198+
@DataProvider
199+
public Object[][] resultsStr() {
200+
return new Object[][] {
201+
202+
new Object[] { STRING_EMPTY, "A", 0, 0, -1 },
203+
new Object[] { STRING_EMPTY, "", 0, 0, 0 },
204+
205+
new Object[] { STRING_L1, "A", 0, 1, 0 },
206+
new Object[] { STRING_L1, "A", 1, 1, -1 },
207+
new Object[] { STRING_L1, "AB", 0, 1, -1 },
208+
new Object[] { STRING_L1, "", 0, 0, 0 },
209+
new Object[] { STRING_L1, "", 0, 1, 0 },
210+
new Object[] { STRING_L1, "", 1, 1, 1 },
211+
212+
new Object[] { STRING_L2, "A", 0, 2, 0 },
213+
new Object[] { STRING_L2, "A", 0, 1, 0 },
214+
new Object[] { STRING_L2, "A", 1, 2, -1 },
215+
new Object[] { STRING_L2, "B", 0, 2, 1 },
216+
new Object[] { STRING_L2, "B", 1, 2, 1 },
217+
new Object[] { STRING_L2, "B", 0, 0, -1 },
218+
new Object[] { STRING_L2, "AB", 0, 2, 0 },
219+
new Object[] { STRING_L2, "AB", 1, 2, -1 },
220+
new Object[] { STRING_L2, "AB", 0, 1, -1 },
221+
new Object[] { STRING_L2, "", 0, 2, 0 },
222+
new Object[] { STRING_L2, "", 1, 2, 1 },
223+
new Object[] { STRING_L2, "", 2, 2, 2 },
224+
225+
new Object[] { STRING_L4, "ABCD", 0, 4, 0 },
226+
new Object[] { STRING_L4, "ABCD", 0, 3, -1 },
227+
new Object[] { STRING_L4, "ABCD", 1, 4, -1 },
228+
new Object[] { STRING_L4, "BC", 0, 4, 1 },
229+
new Object[] { STRING_L4, "BC", 0, 3, 1 },
230+
new Object[] { STRING_L4, "BC", 1, 4, 1 },
231+
new Object[] { STRING_L4, "BC", 1, 2, -1 },
232+
new Object[] { STRING_L4, "BC", 2, 4, -1 },
233+
new Object[] { STRING_L4, "A", 0, 4, 0 },
234+
new Object[] { STRING_L4, "A", 1, 4, -1 },
235+
new Object[] { STRING_L4, "CD", 0, 4, 2 },
236+
new Object[] { STRING_L4, "CD", 2, 4, 2 },
237+
new Object[] { STRING_L4, "CD", 1, 4, 2 },
238+
new Object[] { STRING_L4, "CD", 0, 3, -1 },
239+
new Object[] { STRING_L4, "A", 2, 4, -1 },
240+
new Object[] { STRING_L4, "A", 2, 2, -1 },
241+
new Object[] { STRING_L4, "A", 4, 4, -1 },
242+
new Object[] { STRING_L4, "ABCDE", 0, 4, -1 },
243+
244+
new Object[] { STRING_LLONG, "ABCDEFGH", 0, 8, 0 },
245+
new Object[] { STRING_LLONG, "ABCDEFGH", 1, 8, -1 },
246+
new Object[] { STRING_LLONG, "ABCDEFGH", 0, 7, -1 },
247+
new Object[] { STRING_LLONG, "DEFGH", 0, 8, 3 },
248+
new Object[] { STRING_LLONG, "DEFGH", 3, 8, 3 },
249+
new Object[] { STRING_LLONG, "DEFGH", 4, 8, -1 },
250+
new Object[] { STRING_LLONG, "DEFGH", 0, 7, -1 },
251+
new Object[] { STRING_LLONG, "A", 0, 8, 0 },
252+
new Object[] { STRING_LLONG, "A", 1, 8, -1 },
253+
new Object[] { STRING_LLONG, "A", 0, 0, -1 },
254+
new Object[] { STRING_LLONG, "GHI", 0, 8, -1 },
255+
new Object[] { STRING_LLONG, "GHI", 8, 8, -1 },
256+
new Object[] { STRING_LLONG, "", 4, 4, 4 },
257+
new Object[] { STRING_LLONG, "", 4, 8, 4 },
258+
new Object[] { STRING_LLONG, "", 8, 8, 8 },
259+
260+
new Object[] { STRING_U1, "\uFF21", 0, 1, 0 },
261+
new Object[] { STRING_U1, "\uFF21", 0, 0, -1 },
262+
new Object[] { STRING_U1, "\uFF21", 1, 1, -1 },
263+
new Object[] { STRING_U1, "\uFF21A", 0, 1, -1 },
264+
265+
new Object[] { STRING_U2, "\uFF21\uFF22", 0, 2, 0 },
266+
new Object[] { STRING_U2, "\uFF21\uFF22", 1, 2, -1 },
267+
new Object[] { STRING_U2, "\uFF22", 0, 2, 1 },
268+
new Object[] { STRING_U2, "\uFF22", 0, 1, -1 },
269+
new Object[] { STRING_U2, "\uFF22", 1, 2, 1 },
270+
new Object[] { STRING_U2, "\uFF21", 1, 2, -1 },
271+
new Object[] { STRING_U2, "\uFF21", 0, 1, 0 },
272+
new Object[] { STRING_U2, "", 0, 1, 0 },
273+
new Object[] { STRING_U2, "", 1, 1, 1 },
274+
new Object[] { STRING_U2, "", 2, 2, 2 },
275+
276+
new Object[] { STRING_M12, "\uFF21A", 0, 2, 0 },
277+
new Object[] { STRING_M12, "\uFF21A", 0, 1, -1 },
278+
new Object[] { STRING_M12, "\uFF21A", 1, 2, -1 },
279+
new Object[] { STRING_M12, "A", 1, 2, 1 },
280+
new Object[] { STRING_M12, "A", 0, 2, 1 },
281+
new Object[] { STRING_M12, "A", 0, 1, -1 },
282+
new Object[] { STRING_M12, "\uFF21", 0, 2, 0 },
283+
new Object[] { STRING_M12, "\uFF21", 0, 1, 0 },
284+
new Object[] { STRING_M12, "\uFF21", 1, 2, -1 },
285+
286+
new Object[] { STRING_M11, "A\uFF21", 0, 2, 0 },
287+
new Object[] { STRING_M11, "\uFF21", 1, 2, 1 },
288+
new Object[] { STRING_M11, "A\uFF21", 1, 2, -1 },
289+
new Object[] { STRING_M11, "A\uFF21A", 0, 2, -1 },
290+
291+
new Object[] {
292+
STRING_UDUPLICATE,
293+
"\uFF21\uFF22\uFF21\uFF22\uFF21\uFF22\uFF21\uFF22\uFF21\uFF22",
294+
0, 10, 0 },
295+
new Object[] {
296+
STRING_UDUPLICATE,
297+
"\uFF21\uFF22\uFF21\uFF22\uFF21\uFF22\uFF21\uFF22\uFF21\uFF22",
298+
0, 9, -1 },
299+
new Object[] {
300+
STRING_UDUPLICATE,
301+
"\uFF21\uFF22\uFF21\uFF22\uFF21\uFF22\uFF21\uFF22\uFF21\uFF22",
302+
1, 10, -1 },
303+
new Object[] {
304+
STRING_UDUPLICATE,
305+
"\uFF22\uFF21\uFF22\uFF21\uFF22\uFF21\uFF22\uFF21\uFF22",
306+
1, 10, 1 },
307+
new Object[] {
308+
STRING_UDUPLICATE,
309+
"\uFF22\uFF21\uFF22\uFF21\uFF22\uFF21\uFF22\uFF21\uFF22",
310+
0, 10, 1 },
311+
new Object[] {
312+
STRING_UDUPLICATE,
313+
"\uFF22\uFF21\uFF22\uFF21\uFF22\uFF21\uFF22\uFF21\uFF22",
314+
0, 9, -1 },
315+
new Object[] { STRING_UDUPLICATE, "\uFF21\uFF22\uFF21\uFF22",
316+
4, 10, 4 },
317+
new Object[] { STRING_UDUPLICATE, "\uFF21\uFF22\uFF21\uFF22",
318+
3, 8, 4 },
319+
new Object[] { STRING_UDUPLICATE, "\uFF21\uFF22\uFF21\uFF22",
320+
2, 7, 2 },
321+
new Object[] { STRING_UDUPLICATE, "\uFF21\uFF22\uFF21\uFF22",
322+
7, 10, -1 },
323+
new Object[] { STRING_UDUPLICATE, "",
324+
7, 10, 7 },
325+
new Object[] { STRING_UDUPLICATE, "",
326+
10, 10, 10 },
327+
};
328+
}
329+
330+
@DataProvider
331+
public Object[][] exceptionsStr() {
332+
return new Object[][]{
333+
new Object[]{STRING_LDUPLICATE, "", -1, 0},
334+
new Object[]{STRING_LDUPLICATE, "", 0, 100},
335+
new Object[]{STRING_LDUPLICATE, "", -1, 100},
336+
new Object[]{STRING_LDUPLICATE, "", 3, 1},
337+
338+
new Object[]{STRING_UDUPLICATE, "", -1, 0},
339+
new Object[]{STRING_UDUPLICATE, "", 0, 100},
340+
new Object[]{STRING_UDUPLICATE, "", -1, 100},
341+
new Object[]{STRING_UDUPLICATE, "", 3, 1},
342+
343+
new Object[]{STRING_MDUPLICATE1, "", -1, 0},
344+
new Object[]{STRING_MDUPLICATE1, "", 0, 100},
345+
new Object[]{STRING_MDUPLICATE1, "", -1, 100},
346+
new Object[]{STRING_MDUPLICATE1, "", 3, 1},
347+
348+
new Object[]{STRING_MDUPLICATE2, "", -1, 0},
349+
new Object[]{STRING_MDUPLICATE2, "", 0, 100},
350+
new Object[]{STRING_MDUPLICATE2, "", -1, 100},
351+
new Object[]{STRING_MDUPLICATE2, "", 3, 1},
352+
353+
new Object[]{STRING_LDUPLICATE, "A", -1, 0},
354+
new Object[]{STRING_LDUPLICATE, "A", 0, 100},
355+
new Object[]{STRING_LDUPLICATE, "A", -1, 100},
356+
new Object[]{STRING_LDUPLICATE, "A", 3, 1},
357+
358+
new Object[]{STRING_UDUPLICATE, "A", -1, 0},
359+
new Object[]{STRING_UDUPLICATE, "A", 0, 100},
360+
new Object[]{STRING_UDUPLICATE, "A", -1, 100},
361+
new Object[]{STRING_UDUPLICATE, "A", 3, 1},
362+
363+
new Object[]{STRING_MDUPLICATE1, "A", -1, 0},
364+
new Object[]{STRING_MDUPLICATE1, "A", 0, 100},
365+
new Object[]{STRING_MDUPLICATE1, "A", -1, 100},
366+
new Object[]{STRING_MDUPLICATE1, "A", 3, 1},
367+
368+
new Object[]{STRING_MDUPLICATE2, "A", -1, 0},
369+
new Object[]{STRING_MDUPLICATE2, "A", 0, 100},
370+
new Object[]{STRING_MDUPLICATE2, "A", -1, 100},
371+
new Object[]{STRING_MDUPLICATE2, "A", 3, 1},
372+
};
373+
}
374+
198375
@Test(dataProvider = "results")
199376
public void testIndexOf(String str, int ch, int from, int to, int expected) {
200377
assertEquals(str.indexOf(ch, from, to), expected,
@@ -208,6 +385,19 @@ public void testIndexOf(String str, int ch, int from, int to) {
208385
() -> str.indexOf(ch, from, to));
209386
}
210387

388+
@Test(dataProvider = "resultsStr")
389+
public void testIndexOf(String str, String sub, int from, int to, int expected) {
390+
assertEquals(str.indexOf(sub, from, to), expected,
391+
String.format("testing String(%s).indexOf(%s,%d,%d)",
392+
escapeNonASCIIs(str), escapeNonASCIIs(sub), from, to));
393+
}
394+
395+
@Test(dataProvider = "exceptionsStr")
396+
public void testIndexOf(String str, String sub, int from, int to) {
397+
assertThrows(StringIndexOutOfBoundsException.class,
398+
() -> str.indexOf(sub, from, to));
399+
}
400+
211401
private static String escapeNonASCIIs(String s) {
212402
StringBuilder sb = new StringBuilder();
213403
for (int i = 0; i < s.length(); ++i) {
@@ -220,4 +410,5 @@ private static String escapeNonASCIIs(String s) {
220410
}
221411
return sb.toString();
222412
}
413+
223414
}

0 commit comments

Comments
 (0)