Skip to content

Commit

Permalink
8303648: Add String.indexOf(String str, int beginIndex, int endIndex)
Browse files Browse the repository at this point in the history
Reviewed-by: rriggs
  • Loading branch information
rgiulietti committed Mar 21, 2023
1 parent c4df9b5 commit 4bf1fbb
Show file tree
Hide file tree
Showing 2 changed files with 247 additions and 15 deletions.
67 changes: 54 additions & 13 deletions src/java.base/share/classes/java/lang/String.java
Original file line number Diff line number Diff line change
Expand Up @@ -2607,6 +2607,19 @@ public int indexOf(String str) {
* }</pre>
* If no such value of {@code k} exists, then {@code -1} is returned.
*
* @apiNote
* Unlike {@link #substring(int)}, for example, this method does not throw
* an exception when {@code fromIndex} is outside the valid range.
* Rather, it returns -1 when {@code fromIndex} is larger than the length of
* the string.
* This result is, by itself, indistinguishable from a genuine absence of
* {@code str} in the string.
* If stricter behavior is needed, {@link #indexOf(String, int, int)}
* should be considered instead.
* On {@link String} {@code s} and a non-empty {@code str}, for example,
* {@code s.indexOf(str, fromIndex, s.length())} would throw if
* {@code fromIndex} were larger than the string length, or were negative.
*
* @param str the substring to search for.
* @param fromIndex the index from which to start the search.
* @return the index of the first occurrence of the specified substring,
Expand All @@ -2617,35 +2630,63 @@ public int indexOf(String str, int fromIndex) {
return indexOf(value, coder(), length(), str, fromIndex);
}

/**
* Returns the index of the first occurrence of the specified substring
* within the specified index range of {@code this} string.
*
* <p>This method returns the same result as the one of the invocation
* <pre>{@code
* s.substring(beginIndex, endIndex).indexOf(str) + beginIndex
* }</pre>
* if the index returned by {@link #indexOf(String)} is non-negative,
* and returns -1 otherwise.
* (No substring is instantiated, though.)
*
* @param str the substring to search for.
* @param beginIndex the index to start the search from (included).
* @param endIndex the index to stop the search at (excluded).
* @return the index of the first occurrence of the specified substring
* within the specified index range,
* or {@code -1} if there is no such occurrence.
* @throws StringIndexOutOfBoundsException if {@code beginIndex}
* is negative, or {@code endIndex} is larger than the length of
* this {@code String} object, or {@code beginIndex} is larger than
* {@code endIndex}.
* @since 21
*/
public int indexOf(String str, int beginIndex, int endIndex) {
if (str.length() == 1) {
/* Simple optimization, can be omitted without behavioral impact */
return indexOf(str.charAt(0), beginIndex, endIndex);
}
checkBoundsBeginEnd(beginIndex, endIndex, length());
return indexOf(value, coder(), endIndex, str, beginIndex);
}

/**
* Code shared by String and AbstractStringBuilder to do searches. The
* source is the character array being searched, and the target
* is the string being searched for.
*
* @param src the characters being searched.
* @param srcCoder the coder of the source string.
* @param srcCount length of the source string.
* @param srcCount last index (exclusive) in the source string.
* @param tgtStr the characters being searched for.
* @param fromIndex the index to begin searching from.
*/
static int indexOf(byte[] src, byte srcCoder, int srcCount,
String tgtStr, int fromIndex) {
byte[] tgt = tgtStr.value;
byte tgtCoder = tgtStr.coder();
int tgtCount = tgtStr.length();

if (fromIndex >= srcCount) {
return (tgtCount == 0 ? srcCount : -1);
}
if (fromIndex < 0) {
fromIndex = 0;
fromIndex = Math.clamp(fromIndex, 0, srcCount);
int tgtCount = tgtStr.length();
if (tgtCount > srcCount - fromIndex) {
return -1;
}
if (tgtCount == 0) {
return fromIndex;
}
if (tgtCount > srcCount) {
return -1;
}

byte[] tgt = tgtStr.value;
byte tgtCoder = tgtStr.coder();
if (srcCoder == tgtCoder) {
return srcCoder == LATIN1
? StringLatin1.indexOf(src, srcCount, tgt, tgtCount, fromIndex)
Expand Down
195 changes: 193 additions & 2 deletions test/jdk/java/lang/String/IndexOfBeginEnd.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@

/*
* @test
* @bug 8302590
* @summary This one is for String.indexOf(int,int,int).
* @bug 8302590 8303648
* @summary This one is for String.indexOf([int|String],int,int).
* @run testng IndexOfBeginEnd
*/

Expand Down Expand Up @@ -195,6 +195,183 @@ public Object[][] exceptions() {
};
}

@DataProvider
public Object[][] resultsStr() {
return new Object[][] {

new Object[] { STRING_EMPTY, "A", 0, 0, -1 },
new Object[] { STRING_EMPTY, "", 0, 0, 0 },

new Object[] { STRING_L1, "A", 0, 1, 0 },
new Object[] { STRING_L1, "A", 1, 1, -1 },
new Object[] { STRING_L1, "AB", 0, 1, -1 },
new Object[] { STRING_L1, "", 0, 0, 0 },
new Object[] { STRING_L1, "", 0, 1, 0 },
new Object[] { STRING_L1, "", 1, 1, 1 },

new Object[] { STRING_L2, "A", 0, 2, 0 },
new Object[] { STRING_L2, "A", 0, 1, 0 },
new Object[] { STRING_L2, "A", 1, 2, -1 },
new Object[] { STRING_L2, "B", 0, 2, 1 },
new Object[] { STRING_L2, "B", 1, 2, 1 },
new Object[] { STRING_L2, "B", 0, 0, -1 },
new Object[] { STRING_L2, "AB", 0, 2, 0 },
new Object[] { STRING_L2, "AB", 1, 2, -1 },
new Object[] { STRING_L2, "AB", 0, 1, -1 },
new Object[] { STRING_L2, "", 0, 2, 0 },
new Object[] { STRING_L2, "", 1, 2, 1 },
new Object[] { STRING_L2, "", 2, 2, 2 },

new Object[] { STRING_L4, "ABCD", 0, 4, 0 },
new Object[] { STRING_L4, "ABCD", 0, 3, -1 },
new Object[] { STRING_L4, "ABCD", 1, 4, -1 },
new Object[] { STRING_L4, "BC", 0, 4, 1 },
new Object[] { STRING_L4, "BC", 0, 3, 1 },
new Object[] { STRING_L4, "BC", 1, 4, 1 },
new Object[] { STRING_L4, "BC", 1, 2, -1 },
new Object[] { STRING_L4, "BC", 2, 4, -1 },
new Object[] { STRING_L4, "A", 0, 4, 0 },
new Object[] { STRING_L4, "A", 1, 4, -1 },
new Object[] { STRING_L4, "CD", 0, 4, 2 },
new Object[] { STRING_L4, "CD", 2, 4, 2 },
new Object[] { STRING_L4, "CD", 1, 4, 2 },
new Object[] { STRING_L4, "CD", 0, 3, -1 },
new Object[] { STRING_L4, "A", 2, 4, -1 },
new Object[] { STRING_L4, "A", 2, 2, -1 },
new Object[] { STRING_L4, "A", 4, 4, -1 },
new Object[] { STRING_L4, "ABCDE", 0, 4, -1 },

new Object[] { STRING_LLONG, "ABCDEFGH", 0, 8, 0 },
new Object[] { STRING_LLONG, "ABCDEFGH", 1, 8, -1 },
new Object[] { STRING_LLONG, "ABCDEFGH", 0, 7, -1 },
new Object[] { STRING_LLONG, "DEFGH", 0, 8, 3 },
new Object[] { STRING_LLONG, "DEFGH", 3, 8, 3 },
new Object[] { STRING_LLONG, "DEFGH", 4, 8, -1 },
new Object[] { STRING_LLONG, "DEFGH", 0, 7, -1 },
new Object[] { STRING_LLONG, "A", 0, 8, 0 },
new Object[] { STRING_LLONG, "A", 1, 8, -1 },
new Object[] { STRING_LLONG, "A", 0, 0, -1 },
new Object[] { STRING_LLONG, "GHI", 0, 8, -1 },
new Object[] { STRING_LLONG, "GHI", 8, 8, -1 },
new Object[] { STRING_LLONG, "", 4, 4, 4 },
new Object[] { STRING_LLONG, "", 4, 8, 4 },
new Object[] { STRING_LLONG, "", 8, 8, 8 },

new Object[] { STRING_U1, "\uFF21", 0, 1, 0 },
new Object[] { STRING_U1, "\uFF21", 0, 0, -1 },
new Object[] { STRING_U1, "\uFF21", 1, 1, -1 },
new Object[] { STRING_U1, "\uFF21A", 0, 1, -1 },

new Object[] { STRING_U2, "\uFF21\uFF22", 0, 2, 0 },
new Object[] { STRING_U2, "\uFF21\uFF22", 1, 2, -1 },
new Object[] { STRING_U2, "\uFF22", 0, 2, 1 },
new Object[] { STRING_U2, "\uFF22", 0, 1, -1 },
new Object[] { STRING_U2, "\uFF22", 1, 2, 1 },
new Object[] { STRING_U2, "\uFF21", 1, 2, -1 },
new Object[] { STRING_U2, "\uFF21", 0, 1, 0 },
new Object[] { STRING_U2, "", 0, 1, 0 },
new Object[] { STRING_U2, "", 1, 1, 1 },
new Object[] { STRING_U2, "", 2, 2, 2 },

new Object[] { STRING_M12, "\uFF21A", 0, 2, 0 },
new Object[] { STRING_M12, "\uFF21A", 0, 1, -1 },
new Object[] { STRING_M12, "\uFF21A", 1, 2, -1 },
new Object[] { STRING_M12, "A", 1, 2, 1 },
new Object[] { STRING_M12, "A", 0, 2, 1 },
new Object[] { STRING_M12, "A", 0, 1, -1 },
new Object[] { STRING_M12, "\uFF21", 0, 2, 0 },
new Object[] { STRING_M12, "\uFF21", 0, 1, 0 },
new Object[] { STRING_M12, "\uFF21", 1, 2, -1 },

new Object[] { STRING_M11, "A\uFF21", 0, 2, 0 },
new Object[] { STRING_M11, "\uFF21", 1, 2, 1 },
new Object[] { STRING_M11, "A\uFF21", 1, 2, -1 },
new Object[] { STRING_M11, "A\uFF21A", 0, 2, -1 },

new Object[] {
STRING_UDUPLICATE,
"\uFF21\uFF22\uFF21\uFF22\uFF21\uFF22\uFF21\uFF22\uFF21\uFF22",
0, 10, 0 },
new Object[] {
STRING_UDUPLICATE,
"\uFF21\uFF22\uFF21\uFF22\uFF21\uFF22\uFF21\uFF22\uFF21\uFF22",
0, 9, -1 },
new Object[] {
STRING_UDUPLICATE,
"\uFF21\uFF22\uFF21\uFF22\uFF21\uFF22\uFF21\uFF22\uFF21\uFF22",
1, 10, -1 },
new Object[] {
STRING_UDUPLICATE,
"\uFF22\uFF21\uFF22\uFF21\uFF22\uFF21\uFF22\uFF21\uFF22",
1, 10, 1 },
new Object[] {
STRING_UDUPLICATE,
"\uFF22\uFF21\uFF22\uFF21\uFF22\uFF21\uFF22\uFF21\uFF22",
0, 10, 1 },
new Object[] {
STRING_UDUPLICATE,
"\uFF22\uFF21\uFF22\uFF21\uFF22\uFF21\uFF22\uFF21\uFF22",
0, 9, -1 },
new Object[] { STRING_UDUPLICATE, "\uFF21\uFF22\uFF21\uFF22",
4, 10, 4 },
new Object[] { STRING_UDUPLICATE, "\uFF21\uFF22\uFF21\uFF22",
3, 8, 4 },
new Object[] { STRING_UDUPLICATE, "\uFF21\uFF22\uFF21\uFF22",
2, 7, 2 },
new Object[] { STRING_UDUPLICATE, "\uFF21\uFF22\uFF21\uFF22",
7, 10, -1 },
new Object[] { STRING_UDUPLICATE, "",
7, 10, 7 },
new Object[] { STRING_UDUPLICATE, "",
10, 10, 10 },
};
}

@DataProvider
public Object[][] exceptionsStr() {
return new Object[][]{
new Object[]{STRING_LDUPLICATE, "", -1, 0},
new Object[]{STRING_LDUPLICATE, "", 0, 100},
new Object[]{STRING_LDUPLICATE, "", -1, 100},
new Object[]{STRING_LDUPLICATE, "", 3, 1},

new Object[]{STRING_UDUPLICATE, "", -1, 0},
new Object[]{STRING_UDUPLICATE, "", 0, 100},
new Object[]{STRING_UDUPLICATE, "", -1, 100},
new Object[]{STRING_UDUPLICATE, "", 3, 1},

new Object[]{STRING_MDUPLICATE1, "", -1, 0},
new Object[]{STRING_MDUPLICATE1, "", 0, 100},
new Object[]{STRING_MDUPLICATE1, "", -1, 100},
new Object[]{STRING_MDUPLICATE1, "", 3, 1},

new Object[]{STRING_MDUPLICATE2, "", -1, 0},
new Object[]{STRING_MDUPLICATE2, "", 0, 100},
new Object[]{STRING_MDUPLICATE2, "", -1, 100},
new Object[]{STRING_MDUPLICATE2, "", 3, 1},

new Object[]{STRING_LDUPLICATE, "A", -1, 0},
new Object[]{STRING_LDUPLICATE, "A", 0, 100},
new Object[]{STRING_LDUPLICATE, "A", -1, 100},
new Object[]{STRING_LDUPLICATE, "A", 3, 1},

new Object[]{STRING_UDUPLICATE, "A", -1, 0},
new Object[]{STRING_UDUPLICATE, "A", 0, 100},
new Object[]{STRING_UDUPLICATE, "A", -1, 100},
new Object[]{STRING_UDUPLICATE, "A", 3, 1},

new Object[]{STRING_MDUPLICATE1, "A", -1, 0},
new Object[]{STRING_MDUPLICATE1, "A", 0, 100},
new Object[]{STRING_MDUPLICATE1, "A", -1, 100},
new Object[]{STRING_MDUPLICATE1, "A", 3, 1},

new Object[]{STRING_MDUPLICATE2, "A", -1, 0},
new Object[]{STRING_MDUPLICATE2, "A", 0, 100},
new Object[]{STRING_MDUPLICATE2, "A", -1, 100},
new Object[]{STRING_MDUPLICATE2, "A", 3, 1},
};
}

@Test(dataProvider = "results")
public void testIndexOf(String str, int ch, int from, int to, int expected) {
assertEquals(str.indexOf(ch, from, to), expected,
Expand All @@ -208,6 +385,19 @@ public void testIndexOf(String str, int ch, int from, int to) {
() -> str.indexOf(ch, from, to));
}

@Test(dataProvider = "resultsStr")
public void testIndexOf(String str, String sub, int from, int to, int expected) {
assertEquals(str.indexOf(sub, from, to), expected,
String.format("testing String(%s).indexOf(%s,%d,%d)",
escapeNonASCIIs(str), escapeNonASCIIs(sub), from, to));
}

@Test(dataProvider = "exceptionsStr")
public void testIndexOf(String str, String sub, int from, int to) {
assertThrows(StringIndexOutOfBoundsException.class,
() -> str.indexOf(sub, from, to));
}

private static String escapeNonASCIIs(String s) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); ++i) {
Expand All @@ -220,4 +410,5 @@ private static String escapeNonASCIIs(String s) {
}
return sb.toString();
}

}

1 comment on commit 4bf1fbb

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.