diff --git a/src/main/java/space/b00tload/services/snowflake/ConfigurationValues.java b/src/main/java/space/b00tload/services/snowflake/ConfigurationValues.java
new file mode 100644
index 0000000..728dfcd
--- /dev/null
+++ b/src/main/java/space/b00tload/services/snowflake/ConfigurationValues.java
@@ -0,0 +1,91 @@
+package space.b00tload.services.snowflake;
+
+import space.b00tload.utils.configuration.ConfigValues;
+
+public enum ConfigurationValues implements ConfigValues {
+ MACHINE_ID_BITS("machinebits", "M", "MACHINE_ID_BITS", "bitcount.machine", "10"),
+ SEQUENCE_BITS("sequencebits", "S", "SEQUENCE_BITS", "bitcount.sequence", "12"),
+ EPOCH("epoch", "E", "EPOCH", "epoch", "1704067200000"),
+ MACHINE_ID("machineid", "I", "MACHINE_ID", "machineid", "-1"),
+ ORCHESTRATOR_IP("orchestrator", "O", "ORCHESTRATOR_IP", "orchestrator.ip", "localhost"),
+ ;
+
+ private final String cliFlag;
+ private final String cliAlias;
+ private final String envVariable;
+ private final String tomlPath;
+ private final String defaultValue;
+
+ ConfigurationValues(String cliFlag, String cliAlias, String envVariable, String tomlPath, String defaultValue) {
+ this.cliFlag = cliFlag;
+ this.cliAlias = cliAlias;
+ this.envVariable = envVariable;
+ this.tomlPath = tomlPath;
+ this.defaultValue = defaultValue;
+ }
+
+ /**
+ * The command line flag for this config value.
+ *
+ * @return the command line flag
+ * @example --discord-token
+ */
+ @Override
+ public String getFlag() {
+ return cliFlag;
+ }
+
+ /**
+ * The command line alias for this config value.
+ *
+ * @return the command line alias
+ * @example -d
+ */
+ @Override
+ public String getFlagAlias() {
+ return cliAlias;
+ }
+
+ /**
+ * The environment variable name for this config value.
+ *
+ * @return the environment variable name
+ * @example DISCORD_APP_TOKEN
+ */
+ @Override
+ public String getEnvironmentVariable() {
+ return envVariable;
+ }
+
+ /**
+ * The path to the config value in a toml file.
+ *
+ * @return the toml path.
+ * @example discord.token
+ */
+ @Override
+ public String getTomlPath() {
+ return tomlPath;
+ }
+
+ /**
+ * The default value used if not set via other configuration means.
+ *
+ * @return the default value.
+ */
+ @Override
+ public String getDefaultValue() {
+ return defaultValue;
+ }
+
+ @Override
+ public String toString() {
+ return "ConfigurationValues{" +
+ "defaultValue='" + defaultValue + '\'' +
+ ", tomlPath='" + tomlPath + '\'' +
+ ", envVariable='" + envVariable + '\'' +
+ ", cliAlias='" + cliAlias + '\'' +
+ ", cliFlag='" + cliFlag + '\'' +
+ '}';
+ }
+}
diff --git a/src/main/java/space/b00tload/services/snowflake/SnowflakeIDGenerator.java b/src/main/java/space/b00tload/services/snowflake/SnowflakeIDGenerator.java
index ef784d6..cdbcdb7 100644
--- a/src/main/java/space/b00tload/services/snowflake/SnowflakeIDGenerator.java
+++ b/src/main/java/space/b00tload/services/snowflake/SnowflakeIDGenerator.java
@@ -1,27 +1,107 @@
package space.b00tload.services.snowflake;
+import org.jetbrains.annotations.Nullable;
import org.slf4j.LoggerFactory;
+import space.b00tload.utils.configuration.Configuration;
+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.
+ *
+ * @author Alix von Schirp
+ * @since 1.0.0
+ * @version 1.0.0
+ */
public class SnowflakeIDGenerator {
+ //Singleton instance
+ private static SnowflakeIDGenerator INSTANCE;
+
// Constants
- private static final long EPOCH = 1704067200000L; // January 1, 2024, 00:00:00 UTC
- private static final long MACHINE_ID_BITS = 10L;
- private static final long SEQUENCE_BITS = 12L;
+ /**
+ * Epoch for this generator.
+ * Default is {@code January 1, 2024, 00:00:00 UTC}.
+ */
+ private final long EPOCH;
+ /**
+ * Amount of bits reserved for machine ID.
+ * Default is 10.
+ */
+ private final long MACHINE_ID_BITS;
+ /**
+ * Amount of bits reserved for sequence counter.
+ * Default is 12.
+ */
+ private final long SEQUENCE_BITS;
- private static final long MAX_MACHINE_ID = (1L << MACHINE_ID_BITS) - 1;
- private static final long MAX_SEQUENCE = (1L << SEQUENCE_BITS) - 1;
+ /**
+ * The calculated maximum machine ID.
+ * Defaults to 1023.
+ */
+ private final long MAX_MACHINE_ID;
+ /**
+ * The calculated maximum sequence counter value.
+ * Defaults to 4095.
+ */
+ private final long MAX_SEQUENCE;
- private static final long MACHINE_ID_SHIFT = SEQUENCE_BITS;
- private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + MACHINE_ID_BITS;
+ /**
+ * How far to shift the machine ID (timestamp + machine + sequence)
+ */
+ private final long MACHINE_ID_SHIFT;
+ /**
+ * How far to shift the timestamp (timestamp + machine + sequence
+ */
+ private final long TIMESTAMP_SHIFT;
// State
+ /**
+ * The ID used by the machine.
+ */
private final long machineId;
+ /**
+ * The sequence counter.
+ */
private long sequence = 0L;
+ /**
+ * The unix timestamp at which the last ID was generated.
+ */
private long lastTimestamp = -1L;
// Constructor
- public SnowflakeIDGenerator(long 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
+ */
+ private SnowflakeIDGenerator(long machineId, @Nullable Long epoch, @Nullable Long machineIdBits, @Nullable Long sequenceBits) {
+ //init constants
+ MACHINE_ID_BITS = Objects.requireNonNullElse(machineIdBits,
+ Long.parseLong(Configuration.getInstance().get(ConfigurationValues.MACHINE_ID_BITS)));
+ MAX_MACHINE_ID = (1L << MACHINE_ID_BITS) - 1;
+
+ SEQUENCE_BITS = Objects.requireNonNullElse(sequenceBits,
+ Long.parseLong(Configuration.getInstance().get(ConfigurationValues.SEQUENCE_BITS)));
+ MAX_SEQUENCE = (1L << SEQUENCE_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;
+
+ if(EPOCH > currentTimeMillis()){
+ throw new IllegalArgumentException("Epoch may not be in the future.");
+ }
+
if (machineId < 0 || machineId > MAX_MACHINE_ID) {
throw new IllegalArgumentException("Machine ID must be between 0 and " + MAX_MACHINE_ID);
}
@@ -55,10 +135,7 @@ public class SnowflakeIDGenerator {
| sequence;
}
- // Get the current timestamp in milliseconds
- private long currentTimeMillis() {
- return System.currentTimeMillis();
- }
+
// Wait for the next timestamp
private long waitForNextTimestamp(long currentTimestamp) {
@@ -68,6 +145,51 @@ public class SnowflakeIDGenerator {
}
return nextTimestamp;
}
+
+ public static void init(long machineId, long epoch, long machineIdBits, long sequenceBits){
+ INSTANCE = new SnowflakeIDGenerator(machineId, epoch, machineIdBits, sequenceBits);
+ }
+
+ public static void init(long machineId){
+ INSTANCE = new SnowflakeIDGenerator(machineId, null, null, null);
+ }
+
+ public static SnowflakeIDGenerator getInstance() {
+ if(Objects.isNull(INSTANCE)) throw new UnsupportedOperationException("SnowflakeIDGenerator is not initialized.");
+ return INSTANCE;
+ }
+
+ public long getEPOCH() {
+ return EPOCH;
+ }
+
+ public long getMACHINE_ID_BITS() {
+ return MACHINE_ID_BITS;
+ }
+
+ public long getSEQUENCE_BITS() {
+ return SEQUENCE_BITS;
+ }
+
+ public long getMAX_MACHINE_ID() {
+ return MAX_MACHINE_ID;
+ }
+
+ public long getMAX_SEQUENCE() {
+ return MAX_SEQUENCE;
+ }
+
+ public long getMACHINE_ID_SHIFT() {
+ return MACHINE_ID_SHIFT;
+ }
+
+ public long getTIMESTAMP_SHIFT() {
+ return TIMESTAMP_SHIFT;
+ }
+
+ public long getMachineId() {
+ return machineId;
+ }
}
diff --git a/src/main/java/space/b00tload/services/snowflake/SnowflakeService.java b/src/main/java/space/b00tload/services/snowflake/SnowflakeService.java
index 10cd196..39f352b 100644
--- a/src/main/java/space/b00tload/services/snowflake/SnowflakeService.java
+++ b/src/main/java/space/b00tload/services/snowflake/SnowflakeService.java
@@ -1,8 +1,62 @@
package space.b00tload.services.snowflake;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.joran.JoranConfigurator;
+import ch.qos.logback.core.joran.spi.JoranException;
+import ch.qos.logback.core.util.StatusPrinter2;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import space.b00tload.utils.configuration.Configuration;
+
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.Objects;
+
public class SnowflakeService {
+ private static String SOFTWARE_VERSION;
+ private static String APPLICATION_BASE;
+ private static String USER_AGENT;
+ private static Logger LOGGER;
+
public static void main(String[] args) {
+ //Set up constants
+ SOFTWARE_VERSION = Objects.requireNonNullElse(SnowflakeService.class.getPackage().getImplementationVersion(), "0.0.1-indev");
+ USER_AGENT = "SnowflakeService " + SOFTWARE_VERSION + "(" + System.getProperty("os.name") + "; " + System.getProperty("os.arch") + ") Java/" + System.getProperty("java.version");
+ APPLICATION_BASE = List.of(args).contains("--docker") ? Paths.get("data", "b00tload-tools", "snowflake").toString() : Paths.get(System.getProperty("user.home"), ".b00tload-tools", "snowflake").toString();
+
+ //Set up logger
+ LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
+ try{
+ JoranConfigurator configurator = new JoranConfigurator();
+ configurator.setContext(loggerContext);
+ loggerContext.reset();
+ if(List.of(args).contains("--docker")) {
+ configurator.doConfigure(Objects.requireNonNull(SnowflakeService.class.getResource("/config/logback/logback-docker.xml")));
+ } else {
+ configurator.doConfigure(Objects.requireNonNull(SnowflakeService.class.getResource("/config/logback/logback-bare.xml")));
+ }
+ } catch (JoranException ignored){}
+ (new StatusPrinter2()).printInCaseOfErrorsOrWarnings(loggerContext);
+
+ //Set up logger
+ LOGGER = LoggerFactory.getLogger(SnowflakeService.class);
+
+ //Init config
+ Configuration.init(args, SOFTWARE_VERSION, APPLICATION_BASE, ConfigurationValues.values());
+
+ long machineID;
+ long sequenceBits;
+ long machineBits;
+ long epoch;
+
+ if(!Configuration.getInstance().get(ConfigurationValues.ORCHESTRATOR_IP).equals("localhost")){
+
+ } else if((Long.parseLong(Configuration.getInstance().get(ConfigurationValues.MACHINE_ID))) != -1L) {
+
+ } else {
+
+ }
}