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] ## [Unreleased]
Add Hash ULID generator methods. #25
Add a MIN and MAX constants and methods. #26 Add a MIN and MAX constants and methods. #26
## [5.2.0] - 2023-??-?? ## [5.2.0] - 2023-??-??

View File

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

View File

@ -1,7 +1,7 @@
/* /*
* MIT License * 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 * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@ -24,11 +24,19 @@
package com.github.f4b6a3.ulid; 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. * A class that generates ULIDs.
* <p> * <p>
* Both types of ULID can be easily created by this generator, i.e. monotonic * Both types of ULID can be easily created by this generator, i.e. monotonic
* and non-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 { public final class UlidCreator {
@ -37,6 +45,8 @@ public final class UlidCreator {
/** /**
* Returns a ULID. * Returns a ULID.
* <p>
* The random component is reset for each new ULID generated.
* *
* @return a ULID * @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). * @param time a number of milliseconds since 1970-01-01 (Unix epoch).
* @return a ULID * @return a ULID
@ -56,6 +68,9 @@ public final class UlidCreator {
/** /**
* Returns a Monotonic ULID. * Returns a Monotonic ULID.
* <p>
* The random component is incremented for each new ULID generated in the same
* millisecond.
* *
* @return a ULID * @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). * @param time a number of milliseconds since 1970-01-01 (Unix epoch).
* @return a ULID * @return a ULID
@ -73,6 +91,68 @@ public final class UlidCreator {
return MonotonicFactoryHolder.INSTANCE.create(time); 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 { private static class UlidFactoryHolder {
static final UlidFactory INSTANCE = UlidFactory.newInstance(); static final UlidFactory INSTANCE = UlidFactory.newInstance();
} }

View File

@ -629,6 +629,32 @@ public class UlidTest extends UlidFactoryTest {
checkCreationTime(list, startTime, endTime); 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) { public static Ulid fromString(String string) {
long time = 0; long time = 0;