Skip to content

Commit

Permalink
refactor: update Bytes and BytesSlice to 'mimic' System.arraycopy and…
Browse files Browse the repository at this point in the history
… Arrays.copyOfRange; add more tests
  • Loading branch information
Vovchyk committed Jul 26, 2024
1 parent 859b52a commit 401204a
Show file tree
Hide file tree
Showing 7 changed files with 279 additions and 18 deletions.
5 changes: 2 additions & 3 deletions rskj-core/src/main/java/co/rsk/core/types/bytes/Bytes.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.Objects;

/**
Expand Down Expand Up @@ -140,8 +139,8 @@ public byte byteAt(int index) {
}

@Override
public byte[] copyArrayOfRange(int from, int to) {
return Arrays.copyOfRange(byteArray, from, to);
public void arraycopy(int srcPos, byte[] dest, int destPos, int length) {
System.arraycopy(byteArray, srcPos, dest, destPos, length);
}

@Override
Expand Down
84 changes: 77 additions & 7 deletions rskj-core/src/main/java/co/rsk/core/types/bytes/BytesSlice.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,62 @@

package co.rsk.core.types.bytes;

import java.util.Arrays;

/**
* A {@link BytesSlice} is a subsequence of bytes backed by another broader byte sequence.
*/
public interface BytesSlice extends HexPrintableBytes {

/**
* Copies an array from the {@link BytesSlice} source, beginning at the
* specified position, to the specified position of the destination array.
* A subsequence of array components are copied from this instance to the
* destination array referenced by {@code dest}. The number of components
* copied is equal to the {@code length} argument. The components at
* positions {@code srcPos} through {@code srcPos+length-1} in the source
* array are copied into positions {@code destPos} through
* {@code destPos+length-1}, respectively, of the destination
* array.
* <p>
* If the underlying byte array and {@code dest} argument refer to the
* same array object, then the copying is performed as if the
* components at positions {@code srcPos} through
* {@code srcPos+length-1} were first copied to a temporary
* array with {@code length} components and then the contents of
* the temporary array were copied into positions
* {@code destPos} through {@code destPos+length-1} of the
* destination array.
* <p>
* If {@code dest} is {@code null}, then a
* {@code NullPointerException} is thrown.
* <p>
* Otherwise, if any of the following is true, an
* {@code IndexOutOfBoundsException} is
* thrown and the destination is not modified:
* <ul>
* <li>The {@code srcPos} argument is negative.
* <li>The {@code destPos} argument is negative.
* <li>The {@code length} argument is negative.
* <li>{@code srcPos+length} is greater than
* {@code src.length}, the length of the source array.
* <li>{@code destPos+length} is greater than
* {@code dest.length}, the length of the destination array.
* </ul>
*
* <p>
* Note: this method mimics behaviour of {@link System#arraycopy(Object, int, Object, int, int)}
*
* @param srcPos starting position in the source array.
* @param dest the destination array.
* @param destPos starting position in the destination data.
* @param length the number of array elements to be copied.
* @throws IndexOutOfBoundsException if copying would cause
* access of data outside array bounds.
* @throws NullPointerException if {@code dest} is {@code null}.
*/
void arraycopy(int srcPos, byte[] dest, int destPos, int length);

/**
* Copies the specified range of the specified array into a new array.
* The initial index of the range (<tt>from</tt>) must lie between zero
Expand All @@ -37,17 +88,30 @@ public interface BytesSlice extends HexPrintableBytes {
* greater than or equal to <tt>original.length - from</tt>. The length
* of the returned array will be <tt>to - from</tt>.
*
* <p>
* Note: this method mimics behaviour of {@link Arrays#copyOfRange(Object[], int, int)}
*
* @param from the initial index of the range to be copied, inclusive
* @param to the final index of the range to be copied, exclusive.
* (This index may lie outside the array.)
* @return a new array containing the specified range from the original array,
* truncated or padded with zeros to obtain the required length
* @throws ArrayIndexOutOfBoundsException if {@code from < 0}
* @throws IndexOutOfBoundsException if {@code from < 0}
* or {@code from > original.length}
* @throws IllegalArgumentException if <tt>from &gt; to</tt>
* @throws NullPointerException if <tt>original</tt> is null
*/
byte[] copyArrayOfRange(int from, int to);
default byte[] copyArrayOfRange(int from, int to) {
if (from < 0 || from > length()) {
throw new IndexOutOfBoundsException("invalid 'from': " + from);
}
int newLength = to - from;
if (newLength < 0) {
throw new IllegalArgumentException(from + " > " + to);
}
byte[] copy = new byte[newLength];
arraycopy(from, copy, 0, Math.min(length() - from, newLength));
return copy;
}

default byte[] copyArray() {
return copyArrayOfRange(0, length());
Expand Down Expand Up @@ -104,11 +168,17 @@ public byte byteAt(int index) {
}

@Override
public byte[] copyArrayOfRange(int from, int to) {
if (from < 0 || from > to || to > length()) {
throw new IndexOutOfBoundsException("invalid 'from' and/or 'to': [" + from + ";" + to + ")");
public void arraycopy(int srcPos, byte[] dest, int destPos, int length) {
if (length < 0) {
throw new IndexOutOfBoundsException("invalid 'length': " + length);
}
if (srcPos < 0 || srcPos + length > length()) {
throw new IndexOutOfBoundsException("invalid 'srcPos' and/or 'length': [" + srcPos + ";" + length + ")");
}
if (destPos < 0 || destPos + length > dest.length) {
throw new IndexOutOfBoundsException("invalid 'destPos' and/or 'length': [" + destPos + ";" + length + ")");
}
return originBytes.copyArrayOfRange(this.from + from, this.from + to);
originBytes.arraycopy(this.from + srcPos, dest, destPos, length);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public String toFormattedString(@Nonnull HexPrintableBytes printableBytes, int o
}

if (length > 32) {
return printableBytes.toHexString(off, 15) + ".." + printableBytes.toHexString(off + length - 15, 15);
return printableBytes.toHexString(off, 16) + ".." + printableBytes.toHexString(off + length - 15, 15);
}
return printableBytes.toHexString(off, length);
}
Expand Down
2 changes: 2 additions & 0 deletions rskj-core/src/main/java/co/rsk/util/StringUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public class StringUtils {

private static final int DEFAULT_MAX_LEN = 64;

private StringUtils() { /* hidden */ }

public static String trim(@Nullable String src) {
return trim(src, DEFAULT_MAX_LEN);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,56 @@

package co.rsk.core.types.bytes;

import co.rsk.util.Functions;
import org.junit.jupiter.api.Test;

import java.util.Arrays;

import static org.junit.jupiter.api.Assertions.*;

class BytesSliceTest {

@Test
void testBytesLength() {
assertEquals(0, Bytes.of(new byte[]{}).slice(0, 0).length());
assertEquals(0, Bytes.of(new byte[]{1}).slice(0, 0).length());
assertEquals(1, Bytes.of(new byte[]{1}).slice(0, 1).length());
assertEquals(0, Bytes.of(new byte[]{1,2,3}).slice(1, 1).length());
assertEquals(1, Bytes.of(new byte[]{1,2,3}).slice(1, 2).length());
assertEquals(2, Bytes.of(new byte[]{1,2,3}).slice(0, 2).length());
assertEquals(3, Bytes.of(new byte[]{1,2,3}).slice(0, 3).length());
}

@Test
void testBytesAt() {
assertThrows(IndexOutOfBoundsException.class, () -> Bytes.of(new byte[]{}).slice(0, 0).byteAt(0));
assertThrows(IndexOutOfBoundsException.class, () -> Bytes.of(new byte[]{1}).slice(0, 1).byteAt(1));
assertThrows(IndexOutOfBoundsException.class, () -> Bytes.of(new byte[]{1}).slice(0, 1).byteAt(-1));
assertThrows(IndexOutOfBoundsException.class, () -> Bytes.of(new byte[]{1,2,3}).slice(1, 2).byteAt(1));
assertEquals(1, Bytes.of(new byte[]{1}).slice(0, 1).byteAt(0));
assertEquals(2, Bytes.of(new byte[]{1,2}).slice(0, 2).byteAt(1));
assertEquals(2, Bytes.of(new byte[]{1,2,3}).slice(1, 2).byteAt(0));
assertEquals(4, Bytes.of(new byte[]{1,2,3,4}).slice(2, 4).byteAt(1));
}

@Test
void testBytesSliceArraycopy() {
checkArraycopy((src, srcPos, dest, destPos, length) -> Bytes.of((byte[]) src).slice(1, 4).arraycopy(srcPos, (byte[]) dest, destPos, length));
}

@Test
void testBytesSliceArraycopyMimicsSystemOne() {
checkArraycopy((src, srcPos, dest, destPos, length) -> System.arraycopy(Arrays.copyOfRange((byte[]) src, 1, 4), srcPos, dest, destPos, length));
}

@Test
void testCopyArrayOfRange() {
byte[] bArray = new byte[]{1, 2, 3, 4, 5, 6};
byte[] expectedResult = new byte[]{3, 4, 5};
assertArrayEquals(expectedResult, Bytes.of(bArray).slice(0, bArray.length).copyArrayOfRange(2, 5));
checkCopyOfRange(BytesSlice::copyArrayOfRange, (origin, from, to) -> Bytes.of(origin).slice(from, to));
}

@Test
void testCopyArrayOfRangeMimicsSystemOne() {
checkCopyOfRange(Arrays::copyOfRange, Arrays::copyOfRange);
}

@Test
Expand Down Expand Up @@ -74,4 +113,44 @@ void testEmptySlice() {
assertEquals(0, actualResult.length());
assertArrayEquals(expectedResult, actualResult.copyArray());
}

private static void checkArraycopy(Functions.Action5<Object, Integer, Object, Integer, Integer> fun) {
byte[] dest = new byte[3];
byte[] origin = new byte[]{1,2,3,4,5};

assertThrows(NullPointerException.class, () -> fun.apply(origin, 0, null, 0, 3));

assertThrows(IndexOutOfBoundsException.class, () -> fun.apply(origin, -1, dest, 0, 3));
assertThrows(IndexOutOfBoundsException.class, () -> fun.apply(origin, 0, dest, -1, 3));
assertThrows(IndexOutOfBoundsException.class, () -> fun.apply(origin, 0, dest, 0, -1));
assertThrows(IndexOutOfBoundsException.class, () -> fun.apply(origin, 0, dest, 0, 4));
assertThrows(IndexOutOfBoundsException.class, () -> fun.apply(origin, 1, dest, 0, 3));
assertThrows(IndexOutOfBoundsException.class, () -> fun.apply(origin, 0, dest, 1, 3));

assertArrayEquals(new byte[3], dest); // yet unmodified

fun.apply(origin, 0, dest, 0, 3);
assertArrayEquals(new byte[]{2,3,4}, dest);

byte[] dest2 = new byte[3];
fun.apply(origin, 1, dest2, 1, 1);
assertArrayEquals(new byte[]{0,3,0}, dest2);
}

private static <T> void checkCopyOfRange(Functions.Function3<T, Integer, Integer, byte[]> fun,
Functions.Function3<byte[], Integer, Integer, T> slicer) {
byte[] bArray = new byte[]{1, 2, 3, 4, 5, 6};

assertEquals(bArray.length, fun.apply(slicer.apply(bArray, 0, 6), 0, 6).length);
assertNotSame(bArray, fun.apply(slicer.apply(bArray, 0, 6), 0, 6));

assertArrayEquals(new byte[]{3, 4, 5}, fun.apply(slicer.apply(bArray, 0, 6), 2, 5));
assertArrayEquals(new byte[]{3, 4}, fun.apply(slicer.apply(bArray, 1, 5), 1, 3));
assertArrayEquals(new byte[]{3, 4, 5, 0, 0, 0, 0}, fun.apply(slicer.apply(bArray, 1, 5), 1, 8));
assertArrayEquals(new byte[]{}, fun.apply(slicer.apply(bArray, 1, 5), 4, 4));

assertThrows(IllegalArgumentException.class, () -> fun.apply(slicer.apply(bArray, 1, 5), 1, 0));
assertThrows(IndexOutOfBoundsException.class, () -> fun.apply(slicer.apply(bArray, 1, 5), -1, 0));
assertThrows(IndexOutOfBoundsException.class, () -> fun.apply(slicer.apply(bArray, 1, 5), 5, 5));
}
}
89 changes: 85 additions & 4 deletions rskj-core/src/test/java/co/rsk/core/types/bytes/BytesTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@

package co.rsk.core.types.bytes;

import co.rsk.util.Functions;
import org.ethereum.TestUtils;
import org.ethereum.util.ByteUtil;
import org.junit.jupiter.api.Test;

import java.util.Arrays;

import static org.junit.jupiter.api.Assertions.*;

class BytesTest {
Expand All @@ -34,6 +37,41 @@ void testBytesOf() {
assertNotNull(Bytes.of(new byte[]{1}));
}

@Test
void testBytesLength() {
assertEquals(0, Bytes.of(new byte[]{}).length());
assertEquals(1, Bytes.of(new byte[]{1}).length());
}

@Test
void testBytesAt() {
assertThrows(IndexOutOfBoundsException.class, () -> Bytes.of(new byte[]{}).byteAt(0));
assertThrows(IndexOutOfBoundsException.class, () -> Bytes.of(new byte[]{1}).byteAt(1));
assertThrows(IndexOutOfBoundsException.class, () -> Bytes.of(new byte[]{1}).byteAt(-1));
assertEquals(1, Bytes.of(new byte[]{1}).byteAt(0));
assertEquals(2, Bytes.of(new byte[]{1,2}).byteAt(1));
}

@Test
void testBytesArraycopy() {
checkArraycopy((src, srcPos, dest, destPos, length) -> Bytes.of((byte[]) src).arraycopy(srcPos, (byte[]) dest, destPos, length));
}

@Test
void testBytesArraycopyMimicsSystemOne() {
checkArraycopy(System::arraycopy);
}

@Test
void testCopyArrayOfRange() {
checkCopyOfRange((original, from, to) -> Bytes.of(original).copyArrayOfRange(from, to));
}

@Test
void testCopyArrayOfRangeMimicsSystemOne() {
checkCopyOfRange(Arrays::copyOfRange);
}

@Test
void testToPrintableString() {
assertEquals("0a", Bytes.toPrintableString(new byte[]{10}));
Expand Down Expand Up @@ -83,13 +121,17 @@ void testShortEnoughBytesToString() {

@Test
void testLongBytesToString() {
byte[] bArray1 = TestUtils.generateBytes("hash1",15);
byte[] bArray1 = TestUtils.generateBytes("hash1",16);
byte[] bArray2 = TestUtils.generateBytes("hash2",15);
byte[] finalArray = ByteUtil.merge(bArray1, new byte[]{1, 2, 3}, bArray2);

assertEquals(33, finalArray.length);
assertEquals(34, finalArray.length);

Bytes bytes = Bytes.of(finalArray);

assertEquals(64, String.format("%s", bytes).length());

String actualMessage = String.format("Some '%s' hex", Bytes.of(finalArray));
String actualMessage = String.format("Some '%s' hex", bytes);
String expectedMessage = "Some '"
+ ByteUtil.toHexString(bArray1)
+ ".."
Expand All @@ -116,4 +158,43 @@ void testEmptyBytesToString() {

assertEquals(expectedMessage, actualMessage);
}
}

private static void checkArraycopy(Functions.Action5<Object, Integer, Object, Integer, Integer> fun) {
byte[] dest = new byte[5];
byte[] origin = new byte[]{1,2,3,4,5};

assertThrows(NullPointerException.class, () -> fun.apply(origin, 0, null, 0, 5));

assertThrows(IndexOutOfBoundsException.class, () -> fun.apply(origin, -1, dest, 0, 5));
assertThrows(IndexOutOfBoundsException.class, () -> fun.apply(origin, 0, dest, -1, 5));
assertThrows(IndexOutOfBoundsException.class, () -> fun.apply(origin, 0, dest, 0, -1));
assertThrows(IndexOutOfBoundsException.class, () -> fun.apply(origin, 0, dest, 0, 6));
assertThrows(IndexOutOfBoundsException.class, () -> fun.apply(origin, 1, dest, 0, 5));
assertThrows(IndexOutOfBoundsException.class, () -> fun.apply(origin, 0, dest, 1, 5));

assertArrayEquals(new byte[5], dest); // yet unmodified

fun.apply(origin, 0, dest, 0, 5);
assertArrayEquals(new byte[]{1,2,3,4,5}, dest);

byte[] dest2 = new byte[5];
fun.apply(origin, 1, dest2, 1, 3);
assertArrayEquals(new byte[]{0,2,3,4,0}, dest2);
}

private static void checkCopyOfRange(Functions.Function3<byte[], Integer, Integer, byte[]> fun) {
byte[] bArray = new byte[]{1, 2, 3, 4, 5};

assertEquals(bArray.length, fun.apply(bArray, 0, 5).length);
assertNotSame(bArray, fun.apply(bArray, 0, 5));

assertArrayEquals(new byte[]{2, 3, 4}, fun.apply(bArray, 1, 4));
assertArrayEquals(new byte[]{2}, fun.apply(bArray, 1, 2));
assertArrayEquals(new byte[]{2, 3, 4, 5, 0, 0, 0}, fun.apply(bArray, 1, 8));
assertArrayEquals(new byte[]{}, fun.apply(bArray, 3, 3));

assertThrows(IllegalArgumentException.class, () -> fun.apply(bArray, 1, 0));
assertThrows(IndexOutOfBoundsException.class, () -> fun.apply(bArray, -1, 0));
assertThrows(IndexOutOfBoundsException.class, () -> fun.apply(bArray, 6, 6));
}
}
Loading

0 comments on commit 401204a

Please sign in to comment.