package com.ripple.core.coretypes.hash; import com.ripple.core.serialized.BinaryParser; import com.ripple.core.serialized.BytesSink; import com.ripple.core.serialized.SerializedType; import com.ripple.core.serialized.TypeTranslator; import com.ripple.encodings.common.B16; import java.math.BigInteger; import java.util.Arrays; abstract public class Hash<Subclass extends Hash> implements SerializedType, Comparable<Subclass> { protected final byte[] hash; protected int hashCode = -1; public Hash(byte[] bytes, int size) { hash = normalizeAndCheckHash(bytes, size); } @Override public String toString() { return B16.toString(hash); } @Override public int hashCode() { if (hashCode == -1) { hashCode = Arrays.hashCode(hash); } return hashCode; } private byte[] normalizeAndCheckHash(byte[] bytes, int size) { int length = bytes.length; if (length > size) { String simpleName = ""; throw new RuntimeException("Hash length of " + length + " is too wide for " + simpleName); } if (length == size) { return bytes; } else { byte[] hash = new byte[size]; System.arraycopy(bytes, 0, hash, size - length, length); return hash; } } BigInteger bigInteger() { return new BigInteger(1, hash); } public byte[] bytes() { return hash; } @Override public boolean equals(Object obj) { if (obj instanceof Hash) { return Arrays.equals(hash, ((Hash) obj).hash); } return super.equals(obj); } @Override public int compareTo(Subclass another) { byte[] thisBytes = bytes(); byte[] bytes = another.bytes(); return compareBytes(thisBytes, bytes, 0, thisBytes.length); } public int compareStartingAt(Subclass another, int start) { byte[] thisBytes = bytes(); byte[] bytes = another.bytes(); return compareBytes(thisBytes, bytes, start, thisBytes.length); } public int compareBytes(byte[] thisBytes, byte[] bytes, int start, int numBytes) { int thisLength = thisBytes.length; if (!(bytes.length == thisLength)) { throw new RuntimeException(); } for (int i = start; i < numBytes; i++) { int cmp = (thisBytes[i] & 0xFF) - (bytes[i] & 0xFF); if (cmp != 0) { return cmp < 0 ? -1 : 1; } } return 0; } public byte[] slice(int start) { return slice(start, 0); } public byte get(int i) { if (i < 0) i += hash.length; return hash[i]; } public byte[] slice(int start, int end) { if (start < 0) start += hash.length; if (end <= 0) end += hash.length; int length = end - start; byte[] slice = new byte[length]; System.arraycopy(hash, start, slice, 0, length); return slice; } static public abstract class HashTranslator<T extends Hash> extends TypeTranslator<T> { public abstract T newInstance(byte[] b); public abstract int byteWidth(); @Override public T fromParser(BinaryParser parser, Integer hint) { return newInstance(parser.read(byteWidth())); } @Override public Object toJSON(T obj) { return B16.toString(obj.hash); } @Override public T fromString(String value) { return newInstance(B16.decode(value)); } @Override public void toBytesSink(T obj, BytesSink to) { to.add(obj.hash); } } }