diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 65e0b3d61..ae6ed76d4 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -62,6 +62,7 @@
Add a method to ArrayUtils for removing all occurrences of a given element
Fix parsing edge cases in FastDateParser
StringUtils#equals fails with Index OOBE on non-Strings with identical leading prefix
+ There are no tests for CharSequenceUtils.regionMatches
diff --git a/src/main/java/org/apache/commons/lang3/CharSequenceUtils.java b/src/main/java/org/apache/commons/lang3/CharSequenceUtils.java
index 4335c9bd9..a5112d9d0 100644
--- a/src/main/java/org/apache/commons/lang3/CharSequenceUtils.java
+++ b/src/main/java/org/apache/commons/lang3/CharSequenceUtils.java
@@ -191,6 +191,20 @@ public class CharSequenceUtils {
int index2 = start;
int tmpLen = length;
+ // Extract these first so we detect NPEs the same as the java.lang.String version
+ final int srcLen = cs.length() - thisStart;
+ final int otherLen = substring.length() - start;
+
+ // Check for invalid parameters
+ if (thisStart < 0 || start < 0 || length < 0) {
+ return false;
+ }
+
+ // Check that the regions are long enough
+ if (srcLen < length || otherLen < length) {
+ return false;
+ }
+
while (tmpLen-- > 0) {
final char c1 = cs.charAt(index1++);
final char c2 = substring.charAt(index2++);
diff --git a/src/test/java/org/apache/commons/lang3/CharSequenceUtilsTest.java b/src/test/java/org/apache/commons/lang3/CharSequenceUtilsTest.java
index 176ad7266..a57cafaed 100644
--- a/src/test/java/org/apache/commons/lang3/CharSequenceUtilsTest.java
+++ b/src/test/java/org/apache/commons/lang3/CharSequenceUtilsTest.java
@@ -72,5 +72,108 @@ public class CharSequenceUtilsTest {
public void testSubSequenceTooLong() {
Assert.assertEquals(null, CharSequenceUtils.subSequence(StringUtils.EMPTY, 1));
}
-
+
+ static class TestData{
+ final String source;
+ final boolean ignoreCase;
+ final int toffset;
+ final String other;
+ final int ooffset;
+ final int len;
+ final boolean expected;
+ final Class> throwable;
+ TestData(String source, boolean ignoreCase, int toffset,
+ String other, int ooffset, int len, boolean expected){
+ this.source = source;
+ this.ignoreCase = ignoreCase;
+ this.toffset = toffset;
+ this.other = other;
+ this.ooffset = ooffset;
+ this.len = len;
+ this.expected = expected;
+ this.throwable = null;
+ }
+ TestData(String source, boolean ignoreCase, int toffset,
+ String other, int ooffset, int len, Class> throwable){
+ this.source = source;
+ this.ignoreCase = ignoreCase;
+ this.toffset = toffset;
+ this.other = other;
+ this.ooffset = ooffset;
+ this.len = len;
+ this.expected = false;
+ this.throwable = throwable;
+ }
+ public String toString(){
+ StringBuilder sb = new StringBuilder();
+ sb.append(source).append("[").append(toffset).append("]");
+ sb.append(ignoreCase? " caseblind ":" samecase ");
+ sb.append(other).append("[").append(ooffset).append("]");
+ sb.append(" ").append(len).append(" => ");
+ if (throwable != null) {
+ sb.append(throwable);
+ } else {
+ sb.append(expected);
+ }
+ return sb.toString();
+ }
+ }
+
+ private static final TestData[] TEST_DATA = {
+ // Source IgnoreCase Offset Other Offset Length Result
+ new TestData("", true, -1, "", -1, -1, false),
+ new TestData("", true, 0, "", 0, 1, false),
+ new TestData("a", true, 0, "abc", 0, 0, true),
+ new TestData("a", true, 0, "abc", 0, 1, true),
+ new TestData("a", true, 0, null, 0, 0, NullPointerException.class),
+ new TestData(null, true, 0, null, 0, 0, NullPointerException.class),
+ new TestData(null, true, 0, "", 0, 0, NullPointerException.class),
+ };
+
+ private static abstract class RunTest {
+
+ abstract boolean invoke();
+
+ void run(TestData data, String id) {
+ if (data.throwable != null) {
+ try {
+ invoke();
+ Assert.fail(id + " Expected " + data.throwable);
+ } catch (Exception e) {
+ if (!e.getClass().equals(data.throwable)) {
+ Assert.fail(id + " Expected " + data.throwable + " got " + e.getClass());
+ }
+ }
+ } else {
+ boolean stringCheck = invoke();
+ Assert.assertEquals(id + " Failed test " + data, data.expected, stringCheck);
+ }
+ }
+
+ }
+
+ @Test
+ public void testRegionMatches() {
+ for (final TestData data : TEST_DATA) {
+ new RunTest() {
+ @Override
+ boolean invoke() {
+ return data.source.regionMatches(data.ignoreCase, data.toffset, data.other, data.ooffset, data.len);
+ }
+ }.run(data, "String");
+ new RunTest() {
+ @Override
+ boolean invoke() {
+ return CharSequenceUtils.regionMatches(data.source, data.ignoreCase, data.toffset, data.other, data.ooffset, data.len);
+ }
+ }.run(data, "CSString");
+ new RunTest() {
+ @Override
+ boolean invoke() {
+ return CharSequenceUtils.regionMatches(new StringBuilder(data.source), data.ignoreCase, data.toffset, data.other, data.ooffset, data.len);
+ }
+ }.run(data, "CSNonString");
+ }
+ }
+
}