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