package edu.berkeley.thebes.common.data; import com.google.common.base.Objects; import com.google.common.collect.ComparisonChain; import edu.berkeley.thebes.common.thrift.ThriftVersion; public class Version implements Comparable<Version> { public static final Version NULL_VERSION = new Version((short) -1, -1, -1); private ThriftVersion thriftVersion; private static final int numBitsTimestamp = 29; // ~3 days private static final int numBitsLogicalTime = 27; // ~128 million operations per client (2-hour run) private static final int numBitsClientID = 8; // 256 clients static { assert (numBitsTimestamp + numBitsLogicalTime + numBitsClientID == 64); } private final short clientID; private final long logicalTime; /** Timestamp only carries "numBitsTimestamp" bits, so it's actually circular! * See compareTo() for the rammifications of this. */ private final long timestamp; public Version(short clientID, long logicalTime, long timestamp) { this.clientID = (short) (pow2Less1(numBitsClientID) & clientID); this.logicalTime = pow2Less1(numBitsLogicalTime) & logicalTime; this.timestamp = pow2Less1(numBitsTimestamp) & timestamp; thriftVersion = Version.toThrift(this); } public ThriftVersion getThriftVersion() { return thriftVersion; } public static Version fromLong(long version) { short clientID = (short) (pow2Less1(numBitsClientID) & version); version >>= numBitsClientID; long logicalTime = pow2Less1(numBitsLogicalTime) & version; version >>= numBitsLogicalTime; long timestamp = pow2Less1(numBitsTimestamp) & version; return new Version(clientID, logicalTime, timestamp); } public static Version fromThrift(ThriftVersion thriftVersion) { if(thriftVersion == null) return null; return fromLong(thriftVersion.getVersion()); } public static ThriftVersion toThrift(Version version) { if (version == null || version == NULL_VERSION) return null; long l = 0; l |= (pow2Less1(numBitsTimestamp) & version.getTimestamp()); l <<= numBitsLogicalTime; l |= pow2Less1(numBitsLogicalTime) & version.getLogicalTime(); l <<= numBitsClientID; l |= pow2Less1(numBitsClientID) & version.getClientID(); return new ThriftVersion(l); } private static long pow2Less1(int b) { return Math.round(Math.pow(2, b)) - 1; } public short getClientID() { return clientID; } public long getLogicalTime() { return logicalTime; } public long getTimestamp() { return timestamp; } @Override public int compareTo(Version other) { // If the timestamps are more than half the period (2^numBitsTimestamp) apart, // then we assume we've wrapped around, so the lower is actually higher! if (Math.abs(timestamp - other.getTimestamp()) > pow2Less1(numBitsTimestamp-1)) { return new Long(other.getTimestamp()).compareTo(timestamp); } return ComparisonChain.start() .compare(timestamp, other.getTimestamp()) .compare(clientID, other.getClientID()) .compare(logicalTime, other.getLogicalTime()) .result(); } @Override public String toString() { return String.format("%d:%d:%d", getClientID(), getLogicalTime(), getTimestamp()); } @Override public int hashCode() { return Objects.hashCode(clientID, logicalTime, timestamp); } @Override public boolean equals(Object other) { if (! (other instanceof Version)) { return false; } Version v = (Version) other; return Objects.equal(getClientID(), v.getClientID()) && Objects.equal(getLogicalTime(), v.getLogicalTime()) && Objects.equal(getTimestamp(), v.getTimestamp()); } }