Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions src/main/java/io/github/honhimw/uuid/Bytes.java
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,86 @@ public Bytes position(int index) {
return this;
}

/// Shift-right bits by n bits starting at bit offset.
/// The n-bit gap is part of the moved range, i.e. the bits at
/// [offset, offset + length + n) are shifted right by n bits,
/// directly overwriting the target positions,
/// and the vacated positions [offset, offset + n) are filled with 0.
/// All parameters are bit-indexed rather than byte-indexed.
///
/// @param offset bit offset of the first moved bit
/// @param length bit length of the shifted range without the gap
/// @param n shift distance in bits
/// @return self
public Bytes partialShiftRight(int offset, int length, int n) {
if (n <= 0 || length <= 0) {
return this;
}
final byte[] bytes = this.bytes;
final int total = bytes.length << 3;
final int count = length + n;
// Move from the highest bit down so already-written positions
// are never re-read as sources.
for (int i = count - 1; i >= 0; i--) {
int src = offset + i;
int dst = src + n;
if (dst >= total || src >= total) {
continue;
}
int bit = (bytes[src >>> 3] >> (7 - (src & 7))) & 1;
int dstIndex = dst >>> 3;
int dstShift = 7 - (dst & 7);
bytes[dstIndex] = (byte) ((bytes[dstIndex] & ~(1 << dstShift)) | (bit << dstShift));
}
for (int i = 0; i < n && offset + i < total; i++) {
int index = (offset + i) >>> 3;
int shift = 7 - ((offset + i) & 7);
bytes[index] &= (byte) ~(1 << shift);
}
return this;
}

/// Shift-left bits at range [offset, offset + length) by n bits.
/// The moved bits directly overwrite the target positions
/// [offset - n, offset + length - n),
/// bits shifted below index 0 are discarded,
/// and the vacated source positions which are not covered
/// by the target range are filled with 0.
/// Bits outside the moved range remain untouched.
/// All parameters are bit-indexed rather than byte-indexed.
///
/// @param offset bit offset of the first moved bit
/// @param length bit length of the moved range
/// @param n shift distance in bits
/// @return self
public Bytes partialShiftLeft(int offset, int length, int n) {
if (n <= 0 || length <= 0) {
return this;
}
final byte[] bytes = this.bytes;
final int total = bytes.length << 3;
// Move from the lowest bit up so already-written positions
// are never re-read as sources.
for (int i = 0; i < length; i++) {
int src = offset + i;
int dst = src - n;
if (dst < 0 || src >= total) {
continue;
}
int bit = (bytes[src >>> 3] >> (7 - (src & 7))) & 1;
int dstIndex = dst >>> 3;
int dstShift = 7 - (dst & 7);
bytes[dstIndex] = (byte) ((bytes[dstIndex] & ~(1 << dstShift)) | (bit << dstShift));
}
// Clear the vacated source positions which are not overwritten by targets.
int from = Math.max(offset, offset + length - n);
int to = Math.min(offset + length, total);
for (int i = from; i < to; i++) {
bytes[i >>> 3] &= (byte) ~(1 << (7 - (i & 7)));
}
return this;
}

@Override
public int compareTo(Bytes o) {
int cmp = Integer.compare(this.bytes.length, o.bytes.length);
Expand Down
24 changes: 0 additions & 24 deletions src/main/java/io/github/honhimw/uuid/UuidBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,30 +93,6 @@ public static UuidBuilder fromGregorian(Timestamp ts, NodeId nodeId) {
return builder;
}

public static UuidBuilder fromMd5Bytes(byte[] bytes) {
if (bytes.length != 16) {
throw new IllegalArgumentException("The length of the md5-bytes array must be 16.");
}
UuidBuilder builder = empty();
builder.bytes.put(bytes);
builder
.variant(Variant.RFC4122)
.version(Version.MD5);
return builder;
}

public static UuidBuilder fromSha1Bytes(byte[] bytes) {
if (bytes.length != 16) {
throw new IllegalArgumentException("The length of the sha1-bytes array must be 16.");
}
UuidBuilder builder = empty();
builder.bytes.put(bytes);
builder
.variant(Variant.RFC4122)
.version(Version.SHA1);
return builder;
}

public static UuidBuilder fromSortedGregorian(Timestamp ts, NodeId nodeId) {
long ticks = ts.asGregorian();
int timeLow = (int) ((ticks >>> 28) & (-1L >>> 32));
Expand Down
9 changes: 5 additions & 4 deletions src/main/java/io/github/honhimw/uuid/gen/V3.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package io.github.honhimw.uuid.gen;

import io.github.honhimw.uuid.Context;
import io.github.honhimw.uuid.UUIDs;
import io.github.honhimw.uuid.UuidBuilder;
import io.github.honhimw.uuid.*;
import org.jspecify.annotations.Nullable;

import java.security.MessageDigest;
Expand Down Expand Up @@ -67,7 +65,10 @@ protected String algorithm() {
public UUID of(byte[] name) {
MessageDigest md = getDigester();
byte[] digest = md.digest(name);
return UuidBuilder.fromMd5Bytes(digest).build();
return UuidBuilder.fromBytes(digest)
.variant(Variant.RFC4122)
.version(Version.MD5)
.build();
}

}
10 changes: 5 additions & 5 deletions src/main/java/io/github/honhimw/uuid/gen/V5.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package io.github.honhimw.uuid.gen;

import io.github.honhimw.uuid.Bytes;
import io.github.honhimw.uuid.Context;
import io.github.honhimw.uuid.UUIDs;
import io.github.honhimw.uuid.UuidBuilder;
import io.github.honhimw.uuid.*;
import org.jspecify.annotations.Nullable;

import java.security.MessageDigest;
Expand Down Expand Up @@ -80,7 +77,10 @@ public UUID of(byte[] name) {
MessageDigest md = getDigester();
byte[] digest = md.digest(name);
digest = Bytes.copyOf(digest, 16);
return UuidBuilder.fromSha1Bytes(digest).build();
return UuidBuilder.fromBytes(digest)
.variant(Variant.RFC4122)
.version(Version.SHA1)
.build();
}

}
31 changes: 31 additions & 0 deletions src/test/java/io/github/honhimw/uuid/BytesTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.junit.jupiter.api.Test;

import java.nio.ByteBuffer;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;

/// @author honhimW
Expand Down Expand Up @@ -41,4 +42,34 @@ void _long() {
Assertions.assertEquals(bytes.getLong(4), byteBuffer.getLong(4));
}

@Test
@SneakyThrows
void shiftRight() {
Bytes bytes = new Bytes(16);
bytes.putLong(-1);

bytes
.partialShiftRight(48, 12, 4)
.partialShiftRight(64, 4, 2);

Assertions.assertEquals("ffffffff-ffff-0fff-3c00-000000000000", new UUID(bytes.getLong(0), bytes.getLong(Long.BYTES)).toString());
}

@Test
@SneakyThrows
void shiftLeft() {
Bytes bytes = new Bytes(16);
bytes.putLong(0).putLong(-1);

bytes
.partialShiftLeft(72, 40, 32);

Assertions.assertEquals("00000000-00ff-ffff-ffff-00000000ffff", Uuid.fromBytes(bytes.unwrap()).toString());

bytes
.partialShiftRight(48, 12, 4);

Assertions.assertEquals("00000000-00ff-0fff-ffff-00000000ffff", new UUID(bytes.getLong(0), bytes.getLong(Long.BYTES)).toString());
}

}
30 changes: 30 additions & 0 deletions src/test/java/io/github/honhimw/uuid/V1Tests.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,34 @@ void context() {
}
}

/// [Example of a UUIDv1 Value](https://www.rfc-editor.org/rfc/rfc9562.html#name-example-of-a-uuidv1-value)
/// ```text
/// -------------------------------------------
/// field bits value
/// -------------------------------------------
/// time_low 32 0xC232AB00
/// time_mid 16 0x9414
/// ver 4 0x1
/// time_high 12 0x1EC
/// var 2 0b10
/// clock_seq 14 0b11, 0x3C8
/// node 48 0x9F6BDECED846
/// -------------------------------------------
/// total 128
/// -------------------------------------------
/// final: C232AB00-9414-11EC-B3C8-9F6BDECED846
/// ```
@Test
@SneakyThrows
void example() {
long ticks = 0x1EC9414C232AB00L;
long counter = 0x33C8;
Timestamp timestamp = Timestamp.fromGregorian(ticks, counter);
NodeId nodeId = NodeId.of(new byte[]{(byte) 0x9F, 0x6B, (byte) 0xDE, (byte) 0xCE, (byte) 0xD8, 0x46});
UUID uuid = V1.of(timestamp, nodeId);
Assertions.assertEquals("c232ab00-9414-11ec-b3c8-9f6bdeced846", uuid.toString());
Assertions.assertEquals(1, uuid.version());
Assertions.assertEquals(2, uuid.variant());
}

}
33 changes: 33 additions & 0 deletions src/test/java/io/github/honhimw/uuid/V3Tests.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,37 @@ void noNamespace() {
Assertions.assertEquals(md5UUID, uuid);
}

/// [Example of a UUIDv3 Value](https://www.rfc-editor.org/rfc/rfc9562.html#name-example-of-a-uuidv3-value)
/// ```text
/// Namespace (DNS): 6ba7b810-9dad-11d1-80b4-00c04fd430c8
/// Name: www.example.com
/// ------------------------------------------------------
/// MD5: 5df418813aed051548a72f4a814cf09e
///
/// -------------------------------------------
/// field bits value
/// -------------------------------------------
/// md5_high 48 0x5df418813aed
/// ver 4 0x3
/// md5_mid 12 0x515
/// var 2 0b10
/// md5_low 62 0b00, 0x8a72f4a814cf09e
/// -------------------------------------------
/// total 128
/// -------------------------------------------
/// final: 5df41881-3aed-3515-88a7-2f4a814cf09e
///
/// MD5 hex and dash: 5df41881-3aed-0515-48a7-2f4a814cf09e
/// Ver and Var Overwrite: xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
/// Final: 5df41881-3aed-3515-88a7-2f4a814cf09e
/// ```
@Test
@SneakyThrows
void example() {
UUID uuid = V3.dns().of("www.example.com");
Assertions.assertEquals("5df41881-3aed-3515-88a7-2f4a814cf09e", uuid.toString());
Assertions.assertEquals(3, uuid.version());
Assertions.assertEquals(2, uuid.variant());
}

}
32 changes: 32 additions & 0 deletions src/test/java/io/github/honhimw/uuid/V4Tests.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.math.BigInteger;
import java.util.UUID;

/// @author honhimW
Expand All @@ -18,4 +19,35 @@ void random() {
Assertions.assertEquals(2, uuid.variant());
}

/// [Example of a UUIDv4 Value](https://www.rfc-editor.org/rfc/rfc9562.html#name-example-of-a-uuidv4-value)
/// ```text
/// -------------------------------------------
/// field bits value
/// -------------------------------------------
/// random_a 48 0x919108f752d1
/// ver 4 0x4
/// random_b 12 0x320
/// var 2 0b10
/// random_c 62 0b01, 0xbacf847db4148a8
/// -------------------------------------------
/// total 128
/// -------------------------------------------
/// final: 919108f7-52d1-4320-9bac-f847db4148a8
///
/// Random hex: 919108f752d133205bacf847db4148a8
/// Random hex and dash: 919108f7-52d1-3320-5bac-f847db4148a8
/// Ver and Var Overwrite: xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
/// Final: 919108f7-52d1-4320-9bac-f847db4148a8
/// ```
@Test
@SneakyThrows
void example() {
long h = 0x919108f752d13320L;
long l = 0x5bacf847db4148a8L;
UUID uuid = UuidBuilder.fromPair(h, l).version(Version.RANDOM).variant(Variant.RFC4122).build();
Assertions.assertEquals("919108f7-52d1-4320-9bac-f847db4148a8", uuid.toString());
Assertions.assertEquals(4, uuid.version());
Assertions.assertEquals(2, uuid.variant());
}

}
35 changes: 34 additions & 1 deletion src/test/java/io/github/honhimw/uuid/V5Tests.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.github.honhimw.uuid;

import io.github.honhimw.uuid.gen.V3;
import io.github.honhimw.uuid.gen.V5;
import lombok.SneakyThrows;
import org.junit.jupiter.api.Assertions;
Expand Down Expand Up @@ -55,4 +54,38 @@ void noNamespace() {
Assertions.assertEquals(md5UUID, uuid);
}

/// [Example of a UUIDv5 Value](https://www.rfc-editor.org/rfc/rfc9562.html#name-example-of-a-uuidv5-value)
/// ```text
/// Namespace (DNS): 6ba7b810-9dad-11d1-80b4-00c04fd430c8
/// Name: www.example.com
/// ----------------------------------------------------------
/// SHA-1: 2ed6657de927468b55e12665a8aea6a22dee3e35
///
/// -------------------------------------------
/// field bits value
/// -------------------------------------------
/// sha1_high 48 0x2ed6657de927
/// ver 4 0x5
/// sha1_mid 12 0x68b
/// var 2 0b10
/// sha1_low 62 0b01, 0x5e12665a8aea6a2
/// -------------------------------------------
/// total 128
/// -------------------------------------------
/// final: 2ed6657d-e927-568b-95e1-2665a8aea6a2
///
/// SHA-1 hex and dash: 2ed6657d-e927-468b-55e1-2665a8aea6a2-2dee3e35
/// Ver and Var Overwrite: xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
/// Final: 2ed6657d-e927-568b-95e1-2665a8aea6a2
/// Discarded: -2dee3e35
/// ```
@Test
@SneakyThrows
void example() {
UUID uuid = V5.dns().of("www.example.com");
Assertions.assertEquals("2ed6657d-e927-568b-95e1-2665a8aea6a2", uuid.toString());
Assertions.assertEquals(5, uuid.version());
Assertions.assertEquals(2, uuid.variant());
}

}
Loading