Skip to content

Instantly share code, notes, and snippets.

@Tvaroh
Last active October 6, 2018 06:06
Show Gist options
  • Save Tvaroh/9b50295e8e36ddf03a86 to your computer and use it in GitHub Desktop.
Save Tvaroh/9b50295e8e36ddf03a86 to your computer and use it in GitHub Desktop.
Flake UUID generator in Scala
package io.treev.common.util
import java.math.BigInteger
import java.net.NetworkInterface
import java.nio.ByteBuffer
import java.util
import java.util.Base64
import java.util.concurrent.atomic.AtomicReference
/** UUID in 'flake' format.
* <p>Flake ids are 128-bits wide described here from most significant to least significant bits.
* <ul>
* <li>64-bit timestamp - milliseconds since the epoch (Jan 1 1970);</li>
* <li>48-bit worker id - MAC address from a configurable device;</li>
* <li>
* 16-bit sequence number - usually 0, incremented when more than one id is requested in the same millisecond
* and reset to 0 when the clock ticks forward.
* </li>
* </ul> */
trait UUID extends Serializable {
/** Get time part in milliseconds since the epoch (Jan 1 1970). */
def getTime: Long
/** Get MAC address part. */
def getMac: Array[Byte]
/** Get sequence part. */
def getSequence: Short
/** Get string representation in standard format.
* Delegates to [[java.util.UUID]]. */
def toString: String
/** Get UUID value as byte array. */
def toBytes: Array[Byte]
/** Get base-64 representation (22 characters). Includes no padding. */
def toBase64: String
/** Convert to [[java.util.UUID]]. */
def toJavaUUID: java.util.UUID
}
/** UUID implementation.
* @param time underlying time component
* @param mac underlying hardware address
* @param sequence underlying sequence component */
private class UUIDImpl(time: Long, mac: Array[Byte], sequence: Short) extends UUID {
/** Compare for equality. */
override def equals(obj: Any): Boolean = obj match {
case that: UUID ⇒
that.getTime == this.time && util.Arrays.equals(that.getMac, this.mac) && that.getSequence == this.sequence
case _ ⇒
false
}
/** Get UUID hashcode.
* Delegates to [[java.util.UUID]]. */
override def hashCode: Int =
toJavaUUID.hashCode()
/** Get string representation in standard format.
* Delegates to [[java.util.UUID]]. */
override def toString: String =
toJavaUUID.toString
/** Get UUID value as byte array. */
@transient override lazy val toBytes: Array[Byte] = UUID.toBytes(time, mac, sequence)
/** Get base-64 representation (22 characters). Includes no padding. */
override def toBase64: String = UUID.base64Encoder.encodeToString(toBytes)
/** Convert to [[java.util.UUID]]. */
override def toJavaUUID: java.util.UUID = {
val buf = ByteBuffer.wrap(toBytes)
new java.util.UUID(buf.getLong(), buf.getLong())
}
/** Get time part in milliseconds since the epoch (Jan 1 1970). */
override def getTime: Long = time
/** Get MAC address part. */
override def getMac: Array[Byte] = util.Arrays.copyOf(mac, mac.length)
/** Get sequence part. */
override def getSequence: Short = sequence
}
object UUID {
/** Generate new UUID.
* @see [[io.treev.common.util.UUID]] */
def apply(): UUID = {
val TimeAndSequence(time, sequence) = genTimeAndSequence()
new UUIDImpl(time, MacAddress, sequence)
}
/** Hand-craft a UUID.
* @param time underlying time component
* @param mac underlying hardware address (6 bytes)
* @param sequence underlying sequence component
* @see [[io.treev.common.util.UUID]] */
def apply(time: Long, mac: Array[Byte], sequence: Short): UUID = {
require(mac.length == MacAddressSize, s"MAC address should be $MacAddressSize bytes wide")
new UUIDImpl(time, mac, sequence)
}
/** Parse UUID from string representation.
* Delegates to [[java.util.UUID]]. */
def apply(name: String): UUID = {
val javaUUID = java.util.UUID.fromString(name)
fromJavaUUID(javaUUID)
}
/** Create UUID from byte array.
* @param bytes byte array of size 16
* @see [[io.treev.common.util.UUID]] */
def fromBytes(bytes: Array[Byte]): UUID = {
require(bytes.length == UUIDSize, s"UUID should be $UUIDSize bytes wide")
val buf = ByteBuffer.wrap(bytes)
val mac = new Array[Byte](MacAddressSize)
val time = buf.getLong()
buf.get(mac)
val sequence = buf.getShort()
apply(time, mac, sequence)
}
/** Create UUID from base64-encoded string
* @param s base64-encoded string */
def fromBase64(s: String): UUID = {
val bytes = base64Decoder.decode(s)
fromBytes(bytes)
}
/** Create UUID from Java UUID.
* @param javaUUID java UUID */
def fromJavaUUID(javaUUID: java.util.UUID): UUID = {
val buf = buffer.get()
buf.rewind()
buf.putLong(javaUUID.getMostSignificantBits)
buf.putLong(javaUUID.getLeastSignificantBits)
fromBytes(buf.array())
}
/** Implicit ordering for [[io.treev.common.util.UUID]] values. */
implicit object uuidOrdering extends Ordering[UUID] {
override def compare(x: UUID, y: UUID): Int =
if (x eq y) 0
else {
if (x.getTime > y.getTime) 1
else if (x.getTime < y.getTime) -1
else {
val result = new BigInteger(x.getMac).compareTo(new BigInteger(y.getMac))
if (result != 0) result
else {
if (x.getSequence > y.getSequence) 1
else if (x.getSequence < y.getSequence) -1
else 0
}
}
}
}
private[util] val base64Encoder = Base64.getEncoder.withoutPadding()
private[util] val base64Decoder = Base64.getDecoder
private val UUIDSize = 16
private[util] def toBytes(time: Long, mac: Array[Byte], sequence: Short): Array[Byte] = {
val buf = buffer.get()
buf.rewind()
buf.putLong(time)
buf.put(mac)
buf.putShort(sequence)
buf.rewind()
val arr = new Array[Byte](UUIDSize)
buf.get(arr)
arr
}
// MAC address part
private val MacAddressSize = 6
private lazy val MacAddress: Array[Byte] = {
import scala.collection.JavaConversions._
val interfaces = NetworkInterface.getNetworkInterfaces
interfaces.find { interface ⇒
val mac = interface.getHardwareAddress
mac != null && mac.length == MacAddressSize && mac(1) != 0xff
} map {
_.getHardwareAddress
} getOrElse {
sys.error("Cannot initialize flake id generator: MAC address not found")
}
}
// time and sequence parts
private case class TimeAndSequence(time: Long, sequence: Short) {
def incrementSequence: TimeAndSequence =
if (sequence < Short.MaxValue) this.copy(sequence = (sequence + 1).toShort)
else sys.error("Sequence value overflow")
}
private val lastTimeAndSequence = new AtomicReference[TimeAndSequence](TimeAndSequence(0L, 0))
@annotation.tailrec
private def genTimeAndSequence(): TimeAndSequence = {
val currentLastTimeAndSequence = lastTimeAndSequence.get()
val currentTime = System.currentTimeMillis()
if (currentTime > currentLastTimeAndSequence.time) {
val nextTimeAndSequence = TimeAndSequence(currentTime, 0)
if (lastTimeAndSequence.compareAndSet(currentLastTimeAndSequence, nextTimeAndSequence)) nextTimeAndSequence
else genTimeAndSequence()
} else if (currentTime == currentLastTimeAndSequence.time) {
val nextTimeAndSequence = currentLastTimeAndSequence.incrementSequence
if (lastTimeAndSequence.compareAndSet(currentLastTimeAndSequence, nextTimeAndSequence)) nextTimeAndSequence
else genTimeAndSequence()
} else {
genTimeAndSequence()
}
}
// per-thread byte buffer
private val buffer: ThreadLocal[ByteBuffer] = new ThreadLocal[ByteBuffer] {
override def initialValue(): ByteBuffer = ByteBuffer.allocate(UUIDSize)
}
}
@Tvaroh
Copy link
Author

Tvaroh commented Nov 18, 2015

Example UUIDs:

00000151-19df-0743-0022-4d874d660000
00000151-19df-07d5-0022-4d874d660000
00000151-19df-07d5-0022-4d874d660001
00000151-19df-07d6-0022-4d874d660000
00000151-19df-07d6-0022-4d874d660001
00000151-19df-07d6-0022-4d874d660002
00000151-19df-07d6-0022-4d874d660003
00000151-19df-07d6-0022-4d874d660004
00000151-19df-07d6-0022-4d874d660005
00000151-19df-07d6-0022-4d874d660006
00000151-19df-07d6-0022-4d874d660007
00000151-19df-07d6-0022-4d874d660008
00000151-19df-07d6-0022-4d874d660009
00000151-19df-07d6-0022-4d874d66000a
00000151-19df-07d6-0022-4d874d66000b
00000151-19df-07d6-0022-4d874d66000c
00000151-19df-07d6-0022-4d874d66000d
00000151-19df-07d6-0022-4d874d66000e
00000151-19df-07d6-0022-4d874d66000f
00000151-19df-07d7-0022-4d874d660000
00000151-19df-07d7-0022-4d874d660001
00000151-19df-07d7-0022-4d874d660002
00000151-19df-07d7-0022-4d874d660003
00000151-19df-07d7-0022-4d874d660004
00000151-19df-07d7-0022-4d874d660005
00000151-19df-07d7-0022-4d874d660006
00000151-19df-07d7-0022-4d874d660007
00000151-19df-07d7-0022-4d874d660008
00000151-19df-07d7-0022-4d874d660009
00000151-19df-07d7-0022-4d874d66000a
00000151-19df-07d7-0022-4d874d66000b
00000151-19df-07d7-0022-4d874d66000c
00000151-19df-07d7-0022-4d874d66000d
00000151-19df-07d7-0022-4d874d66000e
00000151-19df-07d7-0022-4d874d66000f
00000151-19df-07d8-0022-4d874d660000
00000151-19df-07d8-0022-4d874d660001
00000151-19df-07d8-0022-4d874d660002
00000151-19df-07d8-0022-4d874d660003
00000151-19df-07d8-0022-4d874d660004
00000151-19df-07d8-0022-4d874d660005
00000151-19df-07d8-0022-4d874d660006
00000151-19df-07d8-0022-4d874d660007
00000151-19df-07d8-0022-4d874d660008
00000151-19df-07d8-0022-4d874d660009
00000151-19df-07d8-0022-4d874d66000a
00000151-19df-07d8-0022-4d874d66000b
00000151-19df-07d8-0022-4d874d66000c
00000151-19df-07d8-0022-4d874d66000d

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment