Author: eevans
Date: Sat Jan 29 17:25:52 2011
New Revision: 1065079

URL: http://svn.apache.org/viewvc?rev=1065079&view=rev
Log:
CASSANDRA-2067 o.a.c.utils.UUIDGen adapted from flewton

Patch by eevans for CASSANDRA-2067

Modified:
    cassandra/trunk/src/java/org/apache/cassandra/utils/UUIDGen.java

Modified: cassandra/trunk/src/java/org/apache/cassandra/utils/UUIDGen.java
URL: 
http://svn.apache.org/viewvc/cassandra/trunk/src/java/org/apache/cassandra/utils/UUIDGen.java?rev=1065079&r1=1065078&r2=1065079&view=diff
==============================================================================
--- cassandra/trunk/src/java/org/apache/cassandra/utils/UUIDGen.java (original)
+++ cassandra/trunk/src/java/org/apache/cassandra/utils/UUIDGen.java Sat Jan 29 
17:25:52 2011
@@ -1,56 +1,44 @@
 package org.apache.cassandra.utils;
-/*
- * 
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- * 
- *   http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * 
- */
-
 
 import java.net.InetAddress;
+import java.net.UnknownHostException;
 import java.nio.ByteBuffer;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
 import java.util.UUID;
 
-import org.safehaus.uuid.EthernetAddress;
-import org.safehaus.uuid.UUIDGenerator;
-
 /**
- * Generates type 1 (time-based) UUIDs
+ * The goods are here: www.ietf.org/rfc/rfc4122.txt.
  */
 public class UUIDGen
 {
-    /** creates a type1 uuid but substitutes hash of the IP where the mac 
would go. */
-    public static synchronized UUID makeType1UUIDFromHost(InetAddress addr)
+    // A grand day! millis at 00:00:00.000 15 Oct 1582.
+    private static final long START_EPOCH = -12219292800000L;
+    private static final long clock = new 
Random(System.currentTimeMillis()).nextLong();
+    
+    // placement of this singleton is important.  It needs to be instantiated 
*AFTER* the other statics.
+    private static final UUIDGen instance = new UUIDGen();
+    
+    private long lastNanos;
+    private final Map<InetAddress, Long> nodeCache = new HashMap<InetAddress, 
Long>();
+    
+    private UUIDGen()
     {
-        try
-        {
-            MessageDigest digest = MessageDigest.getInstance("MD5");
-            digest.update(addr.getAddress());
-            byte[] md5 = digest.digest();
-            byte[] fauxMac = new byte[6];
-            System.arraycopy(md5, 0, fauxMac, 0, Math.min(md5.length, 
fauxMac.length));
-            return 
getUUID(ByteBuffer.wrap(UUIDGenerator.getInstance().generateTimeBasedUUID(new 
EthernetAddress(fauxMac)).toByteArray()));
-        }
-        catch (NoSuchAlgorithmException ex)
-        {
-            throw new RuntimeException("Your platform has no support for 
generating MD5 sums");
-        }
+        // make sure someone didn't whack the clock by changing the order of 
instantiation.
+        if (clock == 0) throw new RuntimeException("singleton instantiation is 
misplaced.");
+    }
+    
+    /**
+     * Creates a type 1 UUID (time-based UUID) that substitutes a hash of
+     * an IP address in place of the MAC (unavailable to Java).
+     * 
+     * @param addr the host address to use
+     * @return a UUID instance
+     */
+    public static UUID makeType1UUIDFromHost(InetAddress addr)
+    {
+        return new UUID(instance.createTime(System.currentTimeMillis()), 
instance.getClockSeqAndNode(addr));
     }
     
     /** creates a type 1 uuid from raw bytes. */
@@ -72,4 +60,106 @@ public class UUIDGen
         }
         return b;
     }
+    
+    /**
+     * Returns a 16 byte representation of a type 1 UUID (a time-based UUID),
+     * based on the current system time.
+     * 
+     * @return a type 1 UUID represented as a byte[]
+     */
+    public static byte[] getTimeUUIDBytes()
+    {
+        return getTimeUUIDBytes(System.currentTimeMillis());
+    }
+    
+    /**
+     * Converts a milliseconds-since-epoch timestamp into the 16 byte 
representation
+     * of a type 1 UUID (a time-based UUID).
+     * 
+     * @param timeMillis
+     * @return a type 1 UUID represented as a byte[]
+     */
+    public static byte[] getTimeUUIDBytes(long timeMillis)
+    {
+        long msb = instance.createTime(timeMillis), lsb = 
instance.getClockSeqAndNode();
+        byte[] uuidBytes = new byte[16];
+        
+        for (int i = 0; i < 8; i++)
+            uuidBytes[i] = (byte) (msb >>> 8 * (7 - i));
+        
+        for (int i = 8; i < 16; i++)
+            uuidBytes[i] = (byte) (lsb >>> 8 * (7 - i));
+        
+        return uuidBytes;
+    }
+    
+    private long getClockSeqAndNode()
+    {
+        try
+        {
+            return getClockSeqAndNode(InetAddress.getLocalHost());
+        }
+        catch (UnknownHostException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+    
+    // todo: could cache value if we assume node doesn't change.
+    private long getClockSeqAndNode(InetAddress addr)
+    {
+        long lsb = 0;
+        lsb |= (clock & 0x3f00000000000000L) >>> 56; // was 58?
+        lsb |= 0x0000000000000080;
+        lsb |= (clock & 0x00ff000000000000L) >>> 48; 
+        lsb |= makeNode(addr);
+        return lsb;
+    }
+    
+    // needs to return two different values for the same when.
+    // we can generate at most 10k UUIDs per ms.
+    private synchronized long createTime(long when)
+    {
+        long nanosSince = (when - START_EPOCH) * 10000;
+        if (nanosSince > lastNanos)
+            lastNanos = nanosSince;
+        else
+            nanosSince = ++lastNanos;
+        
+        long msb = 0L; 
+        msb |= (0x00000000ffffffffL & nanosSince) << 32;
+        msb |= (0x0000ffff00000000L & nanosSince) >>> 16; 
+        msb |= (0xffff000000000000L & nanosSince) >>> 48;
+        msb |= 0x0000000000001000L; // sets the version to 1.
+        return msb;
+    }
+    
+    // Lazily create node hashes, and cache them for later
+    private long makeNode(InetAddress addr)
+    {
+        if (nodeCache.containsKey(addr))
+            return nodeCache.get(addr);
+        
+        // ideally, we'd use the MAC address, but java doesn't expose that.
+        byte[] hash = 
FBUtilities.hash(ByteBuffer.wrap(addr.toString().getBytes()));
+        long node = 0;
+        for (int i = 0; i < Math.min(6, hash.length); i++)
+            node |= (0x00000000000000ff & (long)hash[i]) << (5-i)*8;
+        assert (0xff00000000000000L & node) == 0;
+        
+        nodeCache.put(addr, node);
+        
+        return node;
+    }
 }
+
+// for the curious, here is how I generated START_EPOCH
+//        Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT-0"));
+//        c.set(Calendar.YEAR, 1582);
+//        c.set(Calendar.MONTH, Calendar.OCTOBER);
+//        c.set(Calendar.DAY_OF_MONTH, 15);
+//        c.set(Calendar.HOUR_OF_DAY, 0);
+//        c.set(Calendar.MINUTE, 0);
+//        c.set(Calendar.SECOND, 0);
+//        c.set(Calendar.MILLISECOND, 0);
+//        long START_EPOCH = c.getTimeInMillis();
\ No newline at end of file


Reply via email to