Skip to content

Commit

Permalink
Merge pull request #19 from tomtom-international/dev
Browse files Browse the repository at this point in the history
Added additional code review fixes
  • Loading branch information
rijnb authored Aug 20, 2020
2 parents 55d33e1 + 93f48b1 commit d61ba4f
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 98 deletions.
6 changes: 3 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,9 @@
<equalsverifier.version>3.3</equalsverifier.version>
<junit.version>4.13</junit.version>
<junit-jupiter.version>5.6.1</junit-jupiter.version>
<kotlin.version>1.3.72</kotlin.version>
<kotlin-test.version>1.3.72</kotlin-test.version>
<kotlinx-coroutines-core.version>1.3.7</kotlinx-coroutines-core.version>
<kotlin.version>1.4.0</kotlin.version>
<kotlin-test.version>1.4.0</kotlin-test.version>
<kotlinx-coroutines-core.version>1.3.9</kotlinx-coroutines-core.version>
<mockito-core.version>3.3.3</mockito-core.version>
<mockk.version>1.10.0</mockk.version>
</properties>
Expand Down
143 changes: 56 additions & 87 deletions uid/src/main/java/com/tomtom/kotlin/uid/Uid.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
*/
package com.tomtom.kotlin.uid

import java.io.Serializable
import java.util.UUID


Expand All @@ -27,95 +28,30 @@ import java.util.UUID
*
* @param T Type tag for ID, to make IDs type-safe.
*/
class Uid<T> {
private val uuid: String
data class Uid<T> private constructor(private val uuid: String) : Serializable {

/**
* Create a new, unique UUID-based ID.
*/
constructor() {
uuid = UUID.randomUUID().toString()
}

/**
* Instantiates a [Uid] with a string. Mainly used when de-serializing existing entities.
*
* The format of 'uuid' is checked to comply with a standard UUID format, which is:
* - Dashes at positions 8, 13, 18, 23 (base 0).
* - Characters 0-9 and a-f (lowercase only).
*
* If this format is used, the creation of the [Uid] is very fast. If an alternative format
* us used, as accepted by [fromString], the call is much more expensive.
*
* @param uuidAsString An existing string representation of a UUID.
* @throws IllegalArgumentException If name does not conform to the string representation
* as described in [UUID.toString]. Use [isValid] to make sure the string is valid.
*/
constructor(uuidAsString: String) {
/**
* This code has been optimized to NOT just call UUID.fromString(uuid) to convert the
* UUID-String into a String (and catch an IllegalArgumentException).
*
* If the UUID does not comply, the expensive call to UUID.fromString is made after all.
*/
val length = uuidAsString.length
require(length in UUID_MIN_LENGTH..UUID_MAX_LENGTH) {
"Length of UUID must be [" + UUID_MIN_LENGTH + ", " +
UUID_MAX_LENGTH + "], but is " + uuidAsString.length + ", uuid=" + uuidAsString
}

// Check dashes.
this.uuid = if (areDashesAtCorrectPosition(uuidAsString)) {
uuidAsString.toLowerCase().also {
require(onlyContainsValidUuidCharacters(it)) {
"Incorrect UUID format, uuid=$uuidAsString"
}
}
} else {
UUID.fromString(uuidAsString).toString().toLowerCase()
}
}

/**
* Instantiates an ID with a [UUID].
*
* @param uuidAsUuid Existing [UUID].
*/
private constructor(uuidAsUuid: UUID) {
this.uuid = uuidAsUuid.toString()
}

/**
* Returns hex string representation of this Uid. Opposite of [fromHexString].
* Returns a hex string representation of this Uid. Opposite of [fromHexString].
* This representation is shorthand, it does not include dashes.
*
* @return Hex string representation of ID, exactly 32 characters long.
*/
fun toHexString(): String {
val uuid2 = UUID.fromString(uuid)
val msb = java.lang.Long.toHexString(uuid2.mostSignificantBits)
val lsb = java.lang.Long.toHexString(uuid2.leastSignificantBits)
val compactUuid = UUID.fromString(uuid)
val msb = java.lang.Long.toHexString(compactUuid.mostSignificantBits)
val lsb = java.lang.Long.toHexString(compactUuid.leastSignificantBits)
return "${msb.padStart(16, '0')}${lsb.padStart(16, '0')}"
}

/**
* Method converts given String representation to [Uid] and compares it with this instance.
* A String value of "0-0-0-0-0" would match a [Uid] of "00000-0000-0000-000000000-00" or so.
*
* @param uid String representation od Uid.
* @param uid String representation of the Uid.
* @return True in case String representation matches instance. False otherwise.
*/
fun matchesFromString(uid: String) = this == fromString<T>(uid)

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Uid<*>) return false
if (uuid != other.uuid) return false
return true
}

override fun hashCode() = uuid.hashCode()

/**
* Returns the string representation of this Uid. Opposite of [fromString].
*
Expand All @@ -124,11 +60,57 @@ class Uid<T> {
override fun toString() = uuid

companion object {
private val serialVersionUID = 1L
private const val UUID_DASH = '-'
private const val UUID_MIN_LENGTH = 9
private const val UUID_MAX_LENGTH = 36
private val UUID_DASH_POS = intArrayOf(8, 13, 18, 23)

/**
* Create a new, unique UUID-based ID.
*/
fun <T> new() = Uid<T>(UUID.randomUUID().toString())

/**
* Instantiates a [Uid] with a string. Mainly used when de-serializing existing entities.
*
* The format of 'uuid' is checked to comply with a standard UUID format, which is:
* - Dashes at positions 8, 13, 18, 23 (base 0).
* - Characters 0-9 and a-f (lowercase only).
*
* If this format is used, the creation of the [Uid] is very fast. If an alternative format
* us used, as accepted by [fromString], the call is much more expensive.
*
* @param uuidAsString An existing string representation of a UUID.
* @throws [IllegalArgumentException] If name does not conform to the string representation
* as described in [UUID.toString]. Use [isValid] to make sure the string is valid.
*/
fun <T> fromString(uuidAsString: String): Uid<T> {
/**
* This code has been optimized to NOT just call [UUID.fromString] to convert the
* UUID-String into a String (and catch an [IllegalArgumentException]).
*
* If the UUID does not comply, the expensive call to [UUID.fromString] is made after all.
*/
val length = uuidAsString.length
require(length in UUID_MIN_LENGTH..UUID_MAX_LENGTH) {
"Length of UUID must be [" + UUID_MIN_LENGTH + ", " +
UUID_MAX_LENGTH + "], but is " + uuidAsString.length + ", uuid=" + uuidAsString
}

// Check dashes.
val convertedUuidString = if (areDashesAtCorrectPosition(uuidAsString)) {
uuidAsString.toLowerCase().also {
require(onlyContainsValidUuidCharacters(it)) {
"Incorrect UUID format, uuid=$uuidAsString"
}
}
} else {
UUID.fromString(uuidAsString).toString().toLowerCase()
}
return Uid<T>(convertedUuidString)
}

/**
* Returns an ID if it is a valid UUID, or `null` if it's not.
*
Expand All @@ -139,31 +121,18 @@ class Uid<T> {
if (id == null) {
null
} else try {
Uid<T>(id)
fromString<T>(id)
} catch (ignored: IllegalArgumentException) {
null
}

/**
* Instantiates a [Uid] with given ID as a string. Mainly used for deserialization.
* Opposite of [toString].
*
* @param <T> Uid type.
* @param id String representation of ID.
* @return Uid.
* @throws IllegalArgumentException If name does not conform to the string
* representation as described in [UUID.toString]. Use [isValid] to make sure the
* string is valid.
</T> */
fun <T> fromString(id: String) = Uid<T>(id)

/**
* Instantiates a [Uid] with given ID as hex-formatted string. Opposite of [toHexString].
*
* @param <T> Uid type.
* @param id Hex string representation of ID, must be exactly 32 characters long.
* @return Uid.
* @throws IllegalArgumentException If name does not conform to the string
* @throws [IllegalArgumentException] If name does not conform to the string
* representation as described in [UUID.toString]. Use [isValid] to make sure the
* string is valid.
</T> */
Expand All @@ -173,7 +142,7 @@ class Uid<T> {
id.substring(0, 8).toLong(16) shl 32 or id.substring(8, 16).toLong(16)
val lsb: Long =
id.substring(16, 24).toLong(16) shl 32 or id.substring(24, 32).toLong(16)
return Uid<T>(UUID(msb, lsb))
return Uid<T>(UUID(msb, lsb).toString())
}

/**
Expand Down
16 changes: 8 additions & 8 deletions uid/src/test/java/com/tomtom/kotlin/uid/UidTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class UidTest {
val size = 100
val map: MutableMap<String, Boolean> = HashMap(size)
for (i in 0 until size) {
val id: Uid<*> = Uid<Any>()
val id = Uid.new<Any>()
assertNull(map.put(id.toString(), true))
}
}
Expand All @@ -62,22 +62,22 @@ class UidTest {

@Test
fun testFromString() {
val a: Uid<Any> = Uid.fromString("d32b6789-bfbb-4194-87f3-72ce34609902")
val a = Uid.fromString<Any>("d32b6789-bfbb-4194-87f3-72ce34609902")
val s = "d32b6789-bfbb-4194-87f3-72ce34609902"
val b: Uid<Any> = Uid.fromString(s)
val b = Uid.fromString<Any>(s)
assertEquals(a, b)
}

@Test
fun testToString() {
val x: Uid<Any> = Uid("1-2-3-4-5")
val x = Uid.fromString<Any>("1-2-3-4-5")
assertEquals("00000001-0002-0003-0004-000000000005", x.toString())
}

@Test
fun testAs() {
val a: Uid<Long> = Uid()
val b: Uid<Int> = a as Uid<Int>
val a = Uid.new<Long>()
val b = a as Uid<Int>
assertTrue(a.equals(b))
}

Expand All @@ -97,13 +97,13 @@ class UidTest {

@Test
fun testToHexString1() {
val a: Uid<Any> = Uid()
val a = Uid.new<Any>()
assertEquals(a.toString().replace("-", ""), a.toHexString())
}

@Test
fun testToHexString2() {
val a: Uid<Any> = Uid("1-2-3-4-5")
val a = Uid.fromString<Any>("1-2-3-4-5")
assertEquals("00000001000200030004000000000005", a.toHexString())
}

Expand Down

0 comments on commit d61ba4f

Please sign in to comment.