Add Hash ULID generator methods. #25

This commit is contained in:
Fabio Lima 2023-04-25 23:39:22 -03:00
parent 41c15148d3
commit 644ba86e2c
4 changed files with 111 additions and 4 deletions

View File

@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
Add Hash ULID generator methods. #25
Add a MIN and MAX constants and methods. #26
## [5.2.0] - 2023-??-??

View File

@ -43,7 +43,7 @@ Add these lines to your `pom.xml`.
<dependency>
<groupId>com.github.f4b6a3</groupId>
<artifactId>ulid-creator</artifactId>
<version>5.1.0</version>
<version>5.2.0</version>
</dependency>
```
See more options in [maven.org](https://search.maven.org/artifact/com.github.f4b6a3/ulid-creator).

View File

@ -1,7 +1,7 @@
/*
* MIT License
*
* Copyright (c) 2020-2022 Fabio Lima
* Copyright (c) 2020-2023 Fabio Lima
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -24,11 +24,19 @@
package com.github.f4b6a3.ulid;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
/**
* A class that generates ULIDs.
* <p>
* Both types of ULID can be easily created by this generator, i.e. monotonic
* and non-monotonic.
* <p>
* In addition, a "non-standard" hash-based ULID can also be generated, in which
* the random component is replaced with the first 10 bytes of an SHA-256 hash.
*/
public final class UlidCreator {
@ -37,6 +45,8 @@ public final class UlidCreator {
/**
* Returns a ULID.
* <p>
* The random component is reset for each new ULID generated.
*
* @return a ULID
*/
@ -45,7 +55,9 @@ public final class UlidCreator {
}
/**
* Returns a ULID with a given time.
* Returns a ULID.
* <p>
* The random component is reset for each new ULID generated.
*
* @param time a number of milliseconds since 1970-01-01 (Unix epoch).
* @return a ULID
@ -56,6 +68,9 @@ public final class UlidCreator {
/**
* Returns a Monotonic ULID.
* <p>
* The random component is incremented for each new ULID generated in the same
* millisecond.
*
* @return a ULID
*/
@ -64,7 +79,10 @@ public final class UlidCreator {
}
/**
* Returns a Monotonic ULID with a given time.
* Returns a Monotonic ULID.
* <p>
* The random component is incremented for each new ULID generated in the same
* millisecond.
*
* @param time a number of milliseconds since 1970-01-01 (Unix epoch).
* @return a ULID
@ -73,6 +91,68 @@ public final class UlidCreator {
return MonotonicFactoryHolder.INSTANCE.create(time);
}
/**
* Returns a Hash ULID.
* <p>
* The random component is replaced with the first 10 bytes of an SHA-256 hash.
* <p>
* It always returns the same ULID for a specific pair of {@code time} and
* {@code string}.
* <p>
* Usage example:
*
* <pre>{@code
* long time = file.getCreatedAt();
* String name = file.getFileName();
* Ulid ulid = HashUlid.generate(time, name);
* }</pre>
*
* @param time a number of milliseconds since 1970-01-01 (Unix epoch).
* @param string a string to be hashed using SHA-256 algorithm.
* @return a ULID
* @since 5.2.0
*/
public static Ulid getHashUlid(final long time, String string) {
byte[] bytes = string.getBytes(StandardCharsets.UTF_8);
return getHashUlid(time, bytes);
}
/**
* Returns a Hash ULID.
* <p>
* The random component is replaced with the first 10 bytes of an SHA-256 hash.
* <p>
* It always returns the same ULID for a specific pair of {@code time} and
* {@code bytes}.
* <p>
* Usage example:
*
* <pre>{@code
* long time = file.getCreatedAt();
* byte[] bytes = file.getFileBinary();
* Ulid ulid = HashUlid.generate(time, bytes);
* }</pre>
*
* @param time a number of milliseconds since 1970-01-01 (Unix epoch).
* @param bytes a byte array to be hashed using SHA-256 algorithm.
* @return a ULID
* @since 5.2.0
*/
public static Ulid getHashUlid(final long time, byte[] bytes) {
// Calculate the hash and take the first 10 bytes
byte[] hash = hasher("SHA-256").digest(bytes);
byte[] rand = Arrays.copyOf(hash, 10);
return new Ulid(time, rand);
}
private static MessageDigest hasher(final String algorithm) {
try {
return MessageDigest.getInstance(algorithm);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(String.format("%s not supported", algorithm));
}
}
private static class UlidFactoryHolder {
static final UlidFactory INSTANCE = UlidFactory.newInstance();
}

View File

@ -629,6 +629,32 @@ public class UlidTest extends UlidFactoryTest {
checkCreationTime(list, startTime, endTime);
}
@Test
public void testGetHashUlid() throws NoSuchAlgorithmException {
Ulid prev = Ulid.MIN;
for (int i = 0; i < DEFAULT_LOOP_MAX; i++) {
long time = (new Random()).nextLong() >>> 16;
String string = UUID.randomUUID().toString();
Ulid ulid = UlidCreator.getHashUlid(time, string);
assertNotNull(ulid);
assertNotEquals(prev, ulid);
assertNotEquals(Ulid.MIN, ulid);
assertNotEquals(Ulid.MAX, ulid);
assertEquals(time, ulid.getTime());
assertEquals(Arrays.toString(ulid.getRandom()), Arrays.toString(ulid.getRandom()));
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] utf8 = string.getBytes(StandardCharsets.UTF_8);
byte[] hash = Arrays.copyOf(md.digest(utf8), 10);
assertEquals(Arrays.toString(ulid.getRandom()), Arrays.toString(hash));
prev = ulid;
}
}
public static Ulid fromString(String string) {
long time = 0;