diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 313bc1d26..ada808fcb 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -82,7 +82,9 @@ The type attribute can be add,update,fix,remove. Minor changes #769. FastDateFormat does not support the 'L'-Pattern from SimpleDateFormat. Increase test coverage of ComparableUtils from 71% to 100% #898. - Increase method test coverage of MultilineRecursiveToStringStyle #899. + Increase method test coverage of MultilineRecursiveToStringStyle #899. + Fix unstable coverage of CharSequenceUtils tests noticed during merge of PRs 898 and 899 #901. + Rewrite Conversion.binaryBeMsb0ToHexDigit to invert logic of binaryToHexDigit. Add EnumUtils.getEnumSystemProperty(...). Add TriConsumer. diff --git a/src/main/java/org/apache/commons/lang3/Conversion.java b/src/main/java/org/apache/commons/lang3/Conversion.java index e71070131..47b1a5799 100644 --- a/src/main/java/org/apache/commons/lang3/Conversion.java +++ b/src/main/java/org/apache/commons/lang3/Conversion.java @@ -435,39 +435,41 @@ public class Conversion { * @return a hexadecimal digit representing the selected bits * @throws IllegalArgumentException if {@code src} is empty * @throws NullPointerException if {@code src} is {@code null} + * @throws IndexOutOfBoundsException if {@code srcPos} is outside the array. */ public static char binaryBeMsb0ToHexDigit(boolean[] src, int srcPos) { - if (src.length == 0) { - throw new IllegalArgumentException("Cannot convert an empty array."); + // JDK 9: Objects.checkIndex(int index, int length) + if (Integer.compareUnsigned(srcPos, src.length) >= 0) { + // Throw the correct exception + if (src.length == 0) { + throw new IllegalArgumentException("Cannot convert an empty array."); + } + throw new IndexOutOfBoundsException(srcPos + " is not within array length " + src.length); } - final int beSrcPos = src.length - 1 - srcPos; - final int srcLen = Math.min(4, beSrcPos + 1); - final boolean[] paddedSrc = new boolean[4]; - System.arraycopy(src, beSrcPos + 1 - srcLen, paddedSrc, 4 - srcLen, srcLen); - src = paddedSrc; - srcPos = 0; - if (src[srcPos]) { - if (src.length > srcPos + 1 && src[srcPos + 1]) { - if (src.length > srcPos + 2 && src[srcPos + 2]) { - return src.length > srcPos + 3 && src[srcPos + 3] ? 'f' : 'e'; + // Little-endian bit 0 position + final int pos = src.length - 1 - srcPos; + if (3 <= pos && src[pos - 3]) { + if (src[pos - 2]) { + if (src[pos - 1]) { + return src[pos] ? 'f' : 'e'; } - return src.length > srcPos + 3 && src[srcPos + 3] ? 'd' : 'c'; + return src[pos] ? 'd' : 'c'; } - if (src.length > srcPos + 2 && src[srcPos + 2]) { - return src.length > srcPos + 3 && src[srcPos + 3] ? 'b' : 'a'; + if (src[pos - 1]) { + return src[pos] ? 'b' : 'a'; } - return src.length > srcPos + 3 && src[srcPos + 3] ? '9' : '8'; + return src[pos] ? '9' : '8'; } - if (src.length > srcPos + 1 && src[srcPos + 1]) { - if (src.length > srcPos + 2 && src[srcPos + 2]) { - return src.length > srcPos + 3 && src[srcPos + 3] ? '7' : '6'; + if (2 <= pos && src[pos - 2]) { + if (src[pos - 1]) { + return src[pos] ? '7' : '6'; } - return src.length > srcPos + 3 && src[srcPos + 3] ? '5' : '4'; + return src[pos] ? '5' : '4'; } - if (src.length > srcPos + 2 && src[srcPos + 2]) { - return src.length > srcPos + 3 && src[srcPos + 3] ? '3' : '2'; + if (1 <= pos && src[pos - 1]) { + return src[pos] ? '3' : '2'; } - return src.length > srcPos + 3 && src[srcPos + 3] ? '1' : '0'; + return src[pos] ? '1' : '0'; } /** diff --git a/src/test/java/org/apache/commons/lang3/CharSequenceUtilsTest.java b/src/test/java/org/apache/commons/lang3/CharSequenceUtilsTest.java index 9c3a5bec9..979b773b0 100644 --- a/src/test/java/org/apache/commons/lang3/CharSequenceUtilsTest.java +++ b/src/test/java/org/apache/commons/lang3/CharSequenceUtilsTest.java @@ -246,6 +246,10 @@ public class CharSequenceUtilsTest { testNewLastIndexOfSingle("apache", "x"); testNewLastIndexOfSingle("oraoraoraora", "r"); testNewLastIndexOfSingle("mudamudamudamuda", "d"); + // There is a route through checkLaterThan1#checkLaterThan1 + // which only gets touched if there is a two letter (or more) partial match + // (in this case "st") earlier in the searched string. + testNewLastIndexOfSingle("junk-ststarting", "starting"); final Random random = new Random(); final StringBuilder seg = new StringBuilder(); diff --git a/src/test/java/org/apache/commons/lang3/ConversionTest.java b/src/test/java/org/apache/commons/lang3/ConversionTest.java index 59b372bd6..db9794ac2 100644 --- a/src/test/java/org/apache/commons/lang3/ConversionTest.java +++ b/src/test/java/org/apache/commons/lang3/ConversionTest.java @@ -20,9 +20,13 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; +import java.util.Arrays; +import java.util.SplittableRandom; import java.util.UUID; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; /** @@ -413,6 +417,31 @@ public class ConversionTest { } + @Test + public void testBinaryToHexDigitReverse() { + final SplittableRandom rng = new SplittableRandom(); + final boolean[] x = new boolean[8]; + for (int i = 0; i < 100; i++) { + Conversion.longToBinary(rng.nextLong(), 0, x, 0, 8); + for (int j = 1; j <= 8; j++) { + final boolean[] a = Arrays.copyOf(x, j); + final boolean[] b = a.clone(); + ArrayUtils.reverse(b); + for (int k = 0; k < j; k++) { + assertEquals(Conversion.binaryToHexDigit(a, k), + Conversion.binaryBeMsb0ToHexDigit(b, k)); + } + } + } + } + + @ParameterizedTest + @ValueSource(ints = {-1, 8, 99}) + public void binaryBeMsb0ToHexDigitPosOutsideArray(int index) { + assertThrows(IndexOutOfBoundsException.class, + () -> Conversion.binaryBeMsb0ToHexDigit(new boolean[8], index)); + } + /** * Tests {@link Conversion#intToHexDigit(int)}. */