diff --git a/src/java.base/share/classes/java/lang/String.java b/src/java.base/share/classes/java/lang/String.java index ecebb77dd59..63883c75ba5 100644 --- a/src/java.base/share/classes/java/lang/String.java +++ b/src/java.base/share/classes/java/lang/String.java @@ -2607,6 +2607,19 @@ public final class String * } * 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, @@ -2617,6 +2630,39 @@ public final class String 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. + * + *
This method returns the same result as the one of the invocation + *
{@code
+ * s.substring(beginIndex, endIndex).indexOf(str) + beginIndex
+ * }
+ * 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
@@ -2624,28 +2670,23 @@ public final class String
*
* @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)
diff --git a/test/jdk/java/lang/String/IndexOfBeginEnd.java b/test/jdk/java/lang/String/IndexOfBeginEnd.java
index c231c035228..b131a6c7285 100644
--- a/test/jdk/java/lang/String/IndexOfBeginEnd.java
+++ b/test/jdk/java/lang/String/IndexOfBeginEnd.java
@@ -29,8 +29,8 @@ import static org.testng.Assert.assertThrows;
/*
* @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
*/
@@ -195,6 +195,183 @@ public class IndexOfBeginEnd {
};
}
+ @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,
@@ -208,6 +385,19 @@ public class IndexOfBeginEnd {
() -> 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) {
@@ -220,4 +410,5 @@ public class IndexOfBeginEnd {
}
return sb.toString();
}
+
}