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(); } + }