diff --git a/src/main/java/org/qortal/utils/ByteArray.java b/src/main/java/org/qortal/utils/ByteArray.java index 05e82ecb..d3464c9f 100644 --- a/src/main/java/org/qortal/utils/ByteArray.java +++ b/src/main/java/org/qortal/utils/ByteArray.java @@ -1,6 +1,7 @@ package org.qortal.utils; -import java.math.BigInteger; +import java.util.Arrays; +import java.util.Objects; public class ByteArray implements Comparable { @@ -8,7 +9,11 @@ public class ByteArray implements Comparable { public final byte[] value; public ByteArray(byte[] value) { - this.value = value; + this.value = Objects.requireNonNull(value); + } + + public static ByteArray of(byte[] value) { + return new ByteArray(value); } @Override @@ -16,36 +21,39 @@ public class ByteArray implements Comparable { if (this == other) return true; - if (other instanceof ByteArray) - return this.compareTo((ByteArray) other) == 0; - if (other instanceof byte[]) - return this.compareTo((byte[]) other) == 0; + return Arrays.equals(this.value, (byte[]) other); + + if (other instanceof ByteArray) + return Arrays.equals(this.value, ((ByteArray) other).value); return false; } @Override public int hashCode() { - int h = hash; - if (h == 0 && value.length > 0) { - byte[] val = value; + int h = this.hash; + byte[] val = this.value; + + if (h == 0 && val.length > 0) { + h = 1; for (int i = 0; i < val.length; ++i) h = 31 * h + val[i]; - hash = h; + this.hash = h; } return h; } @Override public int compareTo(ByteArray other) { - return this.compareTo(other.value); + Objects.requireNonNull(other); + return this.compareToPrimitive(other.value); } - public int compareTo(byte[] otherValue) { - byte[] val = value; + public int compareToPrimitive(byte[] otherValue) { + byte[] val = this.value; if (val.length < otherValue.length) return -1; @@ -66,6 +74,16 @@ public class ByteArray implements Comparable { } public String toString() { - return String.format("%x", new BigInteger(1, this.value)); + StringBuilder sb = new StringBuilder(3 + this.value.length * 6); + sb.append("["); + + if (this.value.length > 0) + sb.append(this.value[0]); + + for (int i = 1; i < this.value.length; ++i) + sb.append(", ").append(this.value[i]); + + return sb.append("]").toString(); } + } diff --git a/src/test/java/org/qortal/test/ByteArrayTests.java b/src/test/java/org/qortal/test/ByteArrayTests.java index 32c692ef..8fb6f1cf 100644 --- a/src/test/java/org/qortal/test/ByteArrayTests.java +++ b/src/test/java/org/qortal/test/ByteArrayTests.java @@ -3,10 +3,12 @@ package org.qortal.test; import static org.junit.Assert.*; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; +import java.util.TreeMap; import org.junit.Before; import org.junit.Test; @@ -28,15 +30,13 @@ public class ByteArrayTests { } } - private void fillMap(Map map) { + private static void fillMap(Map map) { for (byte[] testValue : testValues) map.put(new ByteArray(testValue), String.valueOf(map.size())); } - private byte[] dup(byte[] value) { - byte[] copiedValue = new byte[value.length]; - System.arraycopy(value, 0, copiedValue, 0, copiedValue.length); - return copiedValue; + private static byte[] dup(byte[] value) { + return Arrays.copyOf(value, value.length); } @Test @@ -92,7 +92,7 @@ public class ByteArrayTests { @Test @SuppressWarnings("unlikely-arg-type") - public void testMapContainsKey() { + public void testHashMapContainsKey() { Map testMap = new HashMap<>(); fillMap(testMap); @@ -105,8 +105,59 @@ public class ByteArrayTests { assertTrue("boxed not equal to primitive", ba.equals(copiedValue)); - // This won't work because copiedValue.hashCode() will not match ba.hashCode() - assertFalse("Primitive shouldn't be found in map", testMap.containsKey(copiedValue)); + /* + * Unfortunately this doesn't work because HashMap::containsKey compares hashCodes first, + * followed by object references, and copiedValue.hashCode() will never match ba.hashCode(). + */ + assertFalse("Primitive shouldn't be found in HashMap", testMap.containsKey(copiedValue)); + } + + @Test + @SuppressWarnings("unlikely-arg-type") + public void testTreeMapContainsKey() { + Map testMap = new TreeMap<>(); + fillMap(testMap); + + // Create new ByteArray object with an existing value. + byte[] copiedValue = dup(testValues.get(3)); + ByteArray ba = new ByteArray(copiedValue); + + // Confirm object can be found in map + assertTrue("ByteArray not found in map", testMap.containsKey(ba)); + + assertTrue("boxed not equal to primitive", ba.equals(copiedValue)); + + /* + * Unfortunately this doesn't work because TreeMap::containsKey(x) wants to cast x to + * Comparable and byte[] does not fit + * so this throws a ClassCastException. + */ + try { + assertFalse("Primitive shouldn't be found in TreeMap", testMap.containsKey(copiedValue)); + fail(); + } catch (ClassCastException e) { + // Expected + } + } + + @Test + @SuppressWarnings("unlikely-arg-type") + public void testArrayListContains() { + // Create new ByteArray object with an existing value. + byte[] copiedValue = dup(testValues.get(3)); + ByteArray ba = new ByteArray(copiedValue); + + // Confirm object can be found in list + assertTrue("ByteArray not found in map", testValues.contains(ba)); + + assertTrue("boxed not equal to primitive", ba.equals(copiedValue)); + + /* + * Unfortunately this doesn't work because ArrayList::contains performs + * copiedValue.equals(x) for each x in testValues, and byte[].equals() + * simply compares object references, so will never match any ByteArray. + */ + assertFalse("Primitive shouldn't be found in ArrayList", testValues.contains(copiedValue)); } @Test @@ -116,8 +167,9 @@ public class ByteArrayTests { byte[] copiedValue = dup(testValue); + System.out.println(String.format("Primitive hashCode: 0x%08x", testValue.hashCode())); System.out.println(String.format("Boxed hashCode: 0x%08x", ba1.hashCode())); - System.out.println(String.format("Primitive hashCode: 0x%08x", copiedValue.hashCode())); + System.out.println(String.format("Duplicated primitive hashCode: 0x%08x", copiedValue.hashCode())); } @Test