From 7ada4e6012ba501765731e5750bfa12255f30613 Mon Sep 17 00:00:00 2001 From: "Alix von Schirp @ BootMedia Media Production" Date: Thu, 9 May 2024 19:52:17 +0200 Subject: [PATCH] Config, Generator Singleton rewrite Started setting up main class, started documentation --- .../snowflake/ConfigurationValues.java | 91 +++++++++++ .../snowflake/SnowflakeIDGenerator.java | 146 ++++++++++++++++-- .../services/snowflake/SnowflakeService.java | 54 +++++++ 3 files changed, 279 insertions(+), 12 deletions(-) create mode 100644 src/main/java/space/b00tload/services/snowflake/ConfigurationValues.java 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 { + + } }