/* Copyright (c) 2012-2014 Boundless and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Distribution License v1.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/org/documents/edl-v10.html * * Contributors: * Gabriel Roldan (Boundless) - initial implementation */ package org.locationtech.geogig.api; import java.io.Serializable; import java.nio.charset.Charset; import java.util.Arrays; import com.google.common.base.Preconditions; import com.google.common.collect.Ordering; import com.google.common.hash.HashCode; import com.google.common.hash.HashFunction; import com.google.common.hash.Hashing; import com.google.common.primitives.UnsignedBytes; /** * A {@link RevObject} identifier backed by a hash function (SHA1 for instance) */ public final class ObjectId implements Comparable<ObjectId>, Serializable { private static final long serialVersionUID = -2445723120477753654L; /** * A "natural order" {@link Ordering comparator} */ public static final Ordering<ObjectId> NATURAL_ORDER = Ordering.<ObjectId> natural(); /** * ObjectId instance that represents a NULL id. */ public static final ObjectId NULL; /** * Hash function to create object ids out of its contents (SHA-1) */ public static final HashFunction HASH_FUNCTION; public static final int NUM_BYTES; private static final int NUM_CHARS; static { HASH_FUNCTION = Hashing.sha1(); NUM_BYTES = HASH_FUNCTION.bits() / 8; NUM_CHARS = 2 * NUM_BYTES; NULL = new ObjectId(new byte[20]); } private final byte[] hashCode; /** * Constructs a new {@code NULL} object id. */ public ObjectId() { this.hashCode = NULL.hashCode; } /** * Constructs a new object id with the given byte code. * * @param raw the byte code to use */ public ObjectId(byte[] raw) { this(raw, true); } private ObjectId(byte[] raw, boolean cloneArg) { Preconditions.checkNotNull(raw); Preconditions.checkArgument(raw.length == NUM_BYTES, "expected a byte[%s], got byte[%s]", NUM_BYTES, raw.length); this.hashCode = cloneArg ? raw.clone() : raw; } public static ObjectId createNoClone(byte[] rawHash) { return new ObjectId(rawHash, false); } /** * @return whether or not this object id represents the {@link #NULL} object id */ public boolean isNull() { return NULL.equals(this); } /** * Determines if this object id is the same as the given object id. * * @param o the object id to compare against */ @Override public boolean equals(Object o) { if (o == this) { return true; } if (!(o instanceof ObjectId)) { return false; } return Arrays.equals(hashCode, ((ObjectId) o).hashCode); } /** * @return a hash code based on the contents of the byte array. */ @Override public int hashCode() { return (hashCode[0] & 0xFF)// | ((hashCode[1] & 0xFF) << 8)// | ((hashCode[2] & 0xFF) << 16)// | ((hashCode[3] & 0xFF) << 24); } private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray(); /** * @return a human friendly representation of this SHA1 * @see java.lang.Object#toString() */ @Override public String toString() { StringBuilder sb = new StringBuilder(2 * NUM_BYTES); byte b; for (int i = 0; i < NUM_BYTES; i++) { b = hashCode[i]; sb.append(HEX_DIGITS[(b >> 4) & 0xf]).append(HEX_DIGITS[b & 0xf]); } return sb.toString(); } /** * Converts a {@code String} representation of a hash code into an {@code ObjectId}. * * @param hash the string to convert * @return the object id represented by its string form, this method is the inverse of * {@link #toString()} */ public static ObjectId valueOf(final String hash) { Preconditions.checkNotNull(hash); Preconditions.checkArgument(hash.length() == NUM_CHARS, hash, String.format("ObjectId.valueOf: Invalid hash string %s", hash)); // this is perhaps the worse way of doing this... final byte[] raw = new byte[NUM_BYTES]; final int radix = 16; for (int i = 0; i < NUM_BYTES; i++) { raw[i] = (byte) Integer.parseInt(hash.substring(2 * i, 2 * i + 2), radix); } return new ObjectId(raw, false); } /** * Converts a {@code String} representation of a byte code into a byte array. * * @param hash the string to convert * @return the byte array represented by its string form */ public static byte[] toRaw(final String hash) { Preconditions.checkNotNull(hash); for (int i = 0; i < hash.length(); i++) { char c = hash.charAt(i); if (-1 == Character.digit(c, 16)) { throw new IllegalArgumentException("At index " + i + ": partialId is not a valid hash subsequence '" + hash + "'"); } } final byte[] raw = new byte[hash.length() / 2]; final int radix = 16; for (int i = 0; i < raw.length; i++) { raw[i] = (byte) Integer.parseInt(hash.substring(2 * i, 2 * i + 2), radix); } return raw; } /** * Implementation of {@link Comparable#compareTo(Object)} that compares the hash code bytes * treating them as unsigned bytes. * * @see java.lang.Comparable#compareTo(java.lang.Object) */ public int compareTo(final ObjectId o) { byte[] left = this.hashCode; byte[] right = o.hashCode; return compare(left, right); } public static int compare(byte[] left, byte[] right) { return UnsignedBytes.lexicographicalComparator().compare(left, right); } /** * @return a raw byte array of the hash code for this object id. Changes to the returned array * do not affect this object. */ public byte[] getRawValue() { return hashCode.clone(); } public void getRawValue(byte[] target) { System.arraycopy(hashCode, 0, target, 0, NUM_BYTES); } public void getRawValue(byte[] target, int size) { System.arraycopy(hashCode, 0, target, 0, size); } /** * Utility method to quickly hash a String and create an ObjectId out of the string SHA-1 hash. * <p> * Note this method is to hash a string, not to convert the string representation of an * ObjectId. Use {@link #valueOf(String)} for that purpose. * </p> * * @param strToHash * @return the {@code ObjectId} generated from the string */ public static ObjectId forString(final String strToHash) { Preconditions.checkNotNull(strToHash); HashCode hashCode = HASH_FUNCTION.hashString(strToHash, Charset.forName("UTF-8")); return new ObjectId(hashCode.asBytes(), false); } /** * Returns the value of this ObjectId's internal hash at the given index without having to go * through {@link #getRawValue()} and hence create excessive defensive copies of the byte array. * * @param index the index of the byte inside this objectid's internal hash to return * @return the byte at the given index as an integer */ public int byteN(int index) { int b = this.hashCode[index] & 0xFF; return b; } }