/** * Copyright (C) 2012-2013 Selventa, Inc. * * This file is part of the OpenBEL Framework. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The OpenBEL Framework is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the OpenBEL Framework. If not, see <http://www.gnu.org/licenses/>. * * Additional Terms under LGPL v3: * * This license does not authorize you and you are prohibited from using the * name, trademarks, service marks, logos or similar indicia of Selventa, Inc., * or, in the discretion of other licensors or authors of the program, the * name, trademarks, service marks, logos or similar indicia of such authors or * licensors, in any marketing or advertising materials relating to your * distribution of the program or any covered product. This restriction does * not waive or limit your obligation to keep intact all copyright notices set * forth in the program as delivered to you. * * If you distribute the program in whole or in part, or any modified version * of the program, and you assume contractual liability to the recipient with * respect to the program or modified version, then you will indemnify the * authors and licensors of the program for any liabilities that these * contractual assumptions directly impose on those licensors and authors. */ package org.openbel.framework.common.protonetwork.model; import java.io.Serializable; import java.util.UUID; import org.openbel.framework.common.InvalidArgument; /** * A 'skinny' form of a {@link UUID} that stores only the most and least * significant bits allowing the memory requirement of each UUID to be reduced. * <p> * For a 64 bit VM, a UUID will use 64 bytes of memory (4 longs @ 8 bytes + 4 * ints @ 4 bytes + 16 byte object header) versus a SkinnyUUID using 36 bytes of * memory (2 longs @ 8 bytes + 1 int @ 4 bytes + 16 byte object header). <br> * Storing a 40char {@link String} for the UUID would require 120 bytes. (see * http://www.javamex.com/tutorials/memory/string_memory_usage.shtml) * </p> * * @author Steve Ungerer */ public class SkinnyUUID implements Serializable, Comparable<SkinnyUUID> { private static final long serialVersionUID = 570740213581352952L; private final long mostSigBits; private final long leastSigBits; private transient int hashCode = -1; /** * Construct a SkinnyUUID with the most and least significant bits. * * @param mostSigBits * @param leastSigBits */ public SkinnyUUID(long mostSigBits, long leastSigBits) { this.mostSigBits = mostSigBits; this.leastSigBits = leastSigBits; } /** * Construct a SkinnyUUID from a source {@link UUID} * * @param uuid * The {@link UUID}, must not be <code>null</code> * @throws InvalidArgument * Thrown if <code>uuid</code> is <code>null</code> */ public SkinnyUUID(UUID uuid) { if (uuid == null) { throw new InvalidArgument("UUID must not be null"); } this.mostSigBits = uuid.getMostSignificantBits(); this.leastSigBits = uuid.getLeastSignificantBits(); } /** * Creates a <tt>SkinnyUUID</tt> from the string standard representation as * described in the {@link #toString} method. * * @param name * a string that specifies a <tt>UUID</tt>. * @return a <tt>UUID</tt> with the specified value. * @throws IllegalArgumentException * if name does not conform to the string representation as * described in {@link #toString}. */ public static SkinnyUUID fromString(String name) { String[] components = name.split("-"); if (components.length != 5) throw new IllegalArgumentException("Invalid UUID string: " + name); for (int i = 0; i < 5; i++) components[i] = "0x" + components[i]; long mostSigBits = Long.decode(components[0]).longValue(); mostSigBits <<= 16; mostSigBits |= Long.decode(components[1]).longValue(); mostSigBits <<= 16; mostSigBits |= Long.decode(components[2]).longValue(); long leastSigBits = Long.decode(components[3]).longValue(); leastSigBits <<= 48; leastSigBits |= Long.decode(components[4]).longValue(); return new SkinnyUUID(mostSigBits, leastSigBits); } /** * Returns the least significant 64 bits of this UUID's 128 bit value. * * @return the least significant 64 bits of this UUID's 128 bit value. * @see UUID#getLeastSignificantBits() */ public long getLeastSignificantBits() { return leastSigBits; } /** * Returns the most significant 64 bits of this UUID's 128 bit value. * * @return the most significant 64 bits of this UUID's 128 bit value. * @see UUID#getMostSignificantBits() */ public long getMostSignificantBits() { return mostSigBits; } /** * Returns a <code>String</code> object representing this * <code>SkinnyUUID</code>. * * <p> * The UUID string representation is as described by this BNF : <blockquote> * * <pre> * {@code * UUID = <time_low> "-" <time_mid> "-" * <time_high_and_version> "-" * <variant_and_sequence> "-" * <node> * time_low = 4*<hexOctet> * time_mid = 2*<hexOctet> * time_high_and_version = 2*<hexOctet> * variant_and_sequence = 2*<hexOctet> * node = 6*<hexOctet> * hexOctet = <hexDigit><hexDigit> * hexDigit = * "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" * | "a" | "b" | "c" | "d" | "e" | "f" * | "A" | "B" | "C" | "D" | "E" | "F" * } * </pre> * * </blockquote> * * @return a string representation of this <tt>SkinnyUUID</tt>. * @see UUID#toString() */ @Override public String toString() { return (digits(mostSigBits >> 32, 8) + "-" + digits(mostSigBits >> 16, 4) + "-" + digits(mostSigBits, 4) + "-" + digits(leastSigBits >> 48, 4) + "-" + digits( leastSigBits, 12)); } /** * Returns val represented by the specified number of hex digits. * * @see UUID#digits() */ private static String digits(long val, int digits) { long hi = 1L << (digits * 4); return Long.toHexString(hi | (val & (hi - 1))).substring(1); } /** * Returns a hash code for this <code>SkinnyUUID</code>. * * @return a hash code value for this <tt>SkinnyUUID</tt>. */ @Override public int hashCode() { if (hashCode == -1) { hashCode = (int) ((mostSigBits >> 32) ^ mostSigBits ^ (leastSigBits >> 32) ^ leastSigBits); } return hashCode; } /** * Compares this object to the specified object. The result is <tt>true</tt> * if and only if the argument is not <tt>null</tt>, is a * <tt>SkinnyUUID</tt> object, has the same variant, and contains the same * value, bit for bit, as this <tt>SkinnyUUID</tt>. * * @param obj * the object to compare with. * @return <code>true</code> if the objects are the same; <code>false</code> * otherwise. */ @Override public boolean equals(Object obj) { if (!(obj instanceof SkinnyUUID)) { return false; } SkinnyUUID id = (SkinnyUUID) obj; return (mostSigBits == id.mostSigBits && leastSigBits == id.leastSigBits); } /** * Compares this SkinnyUUID with the specified SkinnyUUID. * * <p> * The first of two SkinnyUUIDs follows the second if the most significant * field in which the SkinnyUUIDs differ is greater for the first * SkinnyUUID. * * @param val * <tt>SkinnyUUID</tt> to which this <tt>SkinnyUUID</tt> is to be * compared. * @return -1, 0 or 1 as this <tt>SkinnyUUID</tt> is less than, equal to, or * greater than <tt>val</tt>. * @see UUID#compareTo(UUID) */ @Override public int compareTo(SkinnyUUID val) { // The ordering is intentionally set up so that the UUIDs // can simply be numerically compared as two numbers return (this.mostSigBits < val.mostSigBits ? -1 : (this.mostSigBits > val.mostSigBits ? 1 : (this.leastSigBits < val.leastSigBits ? -1 : (this.leastSigBits > val.leastSigBits ? 1 : 0)))); } /** * Reconstitute the <tt>SkinnyUUID</tt> instance from a stream (that is, * deserialize it). This is necessary to set the transient fields to their * correct uninitialized value so they will be recomputed on demand. * * @see UUID#readObject() */ private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException { in.defaultReadObject(); // Set "cached computation" fields to their initial values hashCode = -1; } }