Additional documentation and bounding checks

This commit is contained in:
2024-05-15 19:36:23 +02:00
parent 7ada4e6012
commit c52d80ff6a

View File

@@ -9,8 +9,9 @@ import java.util.Objects;
import static java.lang.System.currentTimeMillis;
/**
* Generates the SnowflakeIDs. Must be initialized with a machine ID between 0 and 1023.
* Can generate up to 4096 IDs per millisecond. If this limit is hit, generation will be halted until the next millisecond.
* Generates the SnowflakeIDs. Must be initialized with a machine ID between 0 and 1023.<br>
* Can generate up to 4096 IDs per millisecond. If this limit is hit, generation will be halted until the next millisecond.<br>
* Snowflakes always have a max size of 63 bits and can therefore be saved as a {@code long} (64-bit integer).
*
* @author Alix von Schirp
* @since 1.0.0
@@ -37,7 +38,11 @@ public class SnowflakeIDGenerator {
* Default is 12.
*/
private final long SEQUENCE_BITS;
/**
* Amount of bits reserved for timestamp.
* Default is 41.
*/
private final long TIMESTAMP_BITS;
/**
* The calculated maximum machine ID.
* Defaults to 1023.
@@ -48,7 +53,11 @@ public class SnowflakeIDGenerator {
* Defaults to 4095.
*/
private final long MAX_SEQUENCE;
/**
* The calculated maximum timestamp.
* Defaults to 2199023255551, which gives roughly 69 years.
*/
private final long MAX_TIMESTAMP;
/**
* How far to shift the machine ID (timestamp + machine + <b><i>sequence</i></b>)
*/
@@ -72,17 +81,16 @@ public class SnowflakeIDGenerator {
*/
private long lastTimestamp = -1L;
// Constructor
/**
* Used for initiating constants and state and performing bound checks on {@code EPOCH}, {@code MAX_TIMESTAMP} and {@code machineId}.
*
*
* @param machineId The machineID used for generation
* @param epoch The snowflake epoch used for generation
* @param machineIdBits The amount of bits used for the machineID
* @param sequenceBits The amount of bits used for sequence counter
* @param machineId The machineID used for generation // expected to be null if not initialized by orchestrator, loaded from config
* @param epoch The snowflake epoch used for generation // expected to be null if not initialized by orchestrator, loaded from config
* @param machineIdBits The amount of bits used for the machineID // expected to be null if not initialized by orchestrator, loaded from config
* @param sequenceBits The amount of bits used for sequence counter // expected to be null if not initialized by orchestrator, loaded from config
* @throws IllegalArgumentException if any bound checks fail.
*/
private SnowflakeIDGenerator(long machineId, @Nullable Long epoch, @Nullable Long machineIdBits, @Nullable Long sequenceBits) {
private SnowflakeIDGenerator(@Nullable Long machineId, @Nullable Long epoch, @Nullable Long machineIdBits, @Nullable Long sequenceBits) throws IllegalArgumentException{
//init constants
MACHINE_ID_BITS = Objects.requireNonNullElse(machineIdBits,
Long.parseLong(Configuration.getInstance().get(ConfigurationValues.MACHINE_ID_BITS)));
@@ -92,23 +100,63 @@ public class SnowflakeIDGenerator {
Long.parseLong(Configuration.getInstance().get(ConfigurationValues.SEQUENCE_BITS)));
MAX_SEQUENCE = (1L << SEQUENCE_BITS) - 1;
TIMESTAMP_BITS = 63L - MACHINE_ID_BITS - SEQUENCE_BITS;
MAX_TIMESTAMP = (1L << TIMESTAMP_BITS) -1;
EPOCH = Objects.requireNonNullElse(epoch,
Long.parseLong(Configuration.getInstance().get(ConfigurationValues.EPOCH)));
MACHINE_ID_SHIFT = SEQUENCE_BITS;
TIMESTAMP_SHIFT = SEQUENCE_BITS + MACHINE_ID_BITS;
//init state
this.machineId = Objects.requireNonNullElse(machineId,
Long.parseLong(Configuration.getInstance().get(ConfigurationValues.MACHINE_ID)));
//Bound checks
if(EPOCH > currentTimeMillis()){
throw new IllegalArgumentException("Epoch may not be in the future.");
}
if (machineId < 0 || machineId > MAX_MACHINE_ID) {
if(EPOCH+MAX_TIMESTAMP <= currentTimeMillis()){
throw new IllegalArgumentException("Latest possible timestamp may not be in the past.");
}
if (this.machineId < 0 || this.machineId > MAX_MACHINE_ID) {
throw new IllegalArgumentException("Machine ID must be between 0 and " + MAX_MACHINE_ID);
}
this.machineId = machineId;
}
// Method to generate a new ID
/**
* Generates a new snowflake ID.<br>
* It uses a long (64-bit signed int) made up of (in default configuration):
* <table>
* <tr>
* <th>bit(s)</th>
* <th>content</th>
* </tr>
* <tr>
* <td>0</td>
* <td>0 (signed positive)</td>
* </tr>
* <tr>
* <td>1-41</td>
* <td>timestamp (<code>current unix time</code> - <code>epoch</code>)</td>
* </tr>
* <tr>
* <td>42-51</td>
* <td>machineID</td>
* </tr>
* <tr>
* <td>52-63</td>
* <td>sequence</td>
* </tr>
* </table>
* Generation will be halted
* <ul>
* <li>if the clock moves backwards (e.g. fast clock after ntp synchronization) until {@code last generated milli}<{@code current milli}</li>
* <li>if {@code count(generated IDs this millisecond)} is greater than {@code MAX_SEQUENCE} until next millisecond</li>
* </ul>
* @return the generated ID
*/
public synchronized long generateID() {
long currentTimestamp = currentTimeMillis();
@@ -150,8 +198,8 @@ public class SnowflakeIDGenerator {
INSTANCE = new SnowflakeIDGenerator(machineId, epoch, machineIdBits, sequenceBits);
}
public static void init(long machineId){
INSTANCE = new SnowflakeIDGenerator(machineId, null, null, null);
public static void init(){
INSTANCE = new SnowflakeIDGenerator(null, null, null, null);
}
public static SnowflakeIDGenerator getInstance() {
@@ -190,6 +238,14 @@ public class SnowflakeIDGenerator {
public long getMachineId() {
return machineId;
}
public long getTIMESTAMP_BITS() {
return TIMESTAMP_BITS;
}
public long getMAX_TIMESTAMP() {
return MAX_TIMESTAMP;
}
}