commit 48934420325c262c3654e5cdd9c11e4a6491fa63 Author: Alix von Schirp Date: Tue May 7 21:24:40 2024 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..d487549 --- /dev/null +++ b/pom.xml @@ -0,0 +1,63 @@ + + + 4.0.0 + + space.b00tload.utils + ConfigurationUtilities + 1.0.0 + + ConfigurationUtilities + A library to load config from cli-args, .env, toml-config and default values. + 2024 + https://github.com/B00tLoad/ConfigurationUtilities + + + + B00tLoad_ + Alix von Schirp + alix.von-schirp@bootmedia.de + + developer + + Europe/Berlin + + @b00tload_ + @b00tload_ + order-of-gathering.de + @b00tload.space + she/they + + + + + + GitHub + https://github.com/B00tLoad/SnowflakeService/issues + + + + 21 + 21 + UTF-8 + + + + + + + ch.qos.logback + logback-classic + 1.5.5 + + + + + com.electronwill.night-config + toml + 3.6.7 + + + + \ No newline at end of file diff --git a/src/main/java/space/b00tload/utils/configuration/ConfigValues.java b/src/main/java/space/b00tload/utils/configuration/ConfigValues.java new file mode 100644 index 0000000..7f2c353 --- /dev/null +++ b/src/main/java/space/b00tload/utils/configuration/ConfigValues.java @@ -0,0 +1,52 @@ +package space.b00tload.utils.configuration; + + +/** + * An interface to implement in enums containing all configurable values. + * + * @author Alix von Schirp + * @version 1.0.0 + * @since 1.0.0 + */ +public interface ConfigValues { + + /** + * The command line flag for this config value. + * + * @return the command line flag + * @example --discord-token + */ + String getFlag(); + + /** + * The command line alias for this config value. + * + * @return the command line alias + * @example -d + */ + String getFlagAlias(); + + /** + * The environment variable name for this config value. + * + * @return the environment variable name + * @example DISCORD_APP_TOKEN + */ + String getEnvironmentVariable(); + + /** + * The path to the config value in a toml file. + * + * @return the toml path. + * @example discord.token + */ + String getTomlPath(); + + /** + * The default value used if not set via other configuration means. + * + * @return the default value. + */ + String getDefaultValue(); + +} diff --git a/src/main/java/space/b00tload/utils/configuration/Configuration.java b/src/main/java/space/b00tload/utils/configuration/Configuration.java new file mode 100644 index 0000000..5a2cb39 --- /dev/null +++ b/src/main/java/space/b00tload/utils/configuration/Configuration.java @@ -0,0 +1,113 @@ +package space.b00tload.utils.configuration; + +import space.b00tload.utils.configuration.exceptions.ConfigException; +import space.b00tload.utils.configuration.exceptions.ConfigIncompleteException; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + + +/** + * Loads the configuration from all supported sources (cli-args, .env, toml-file, default). + * + * @author Alix von Schirp + * @version 1.0.0 + * @since 1.0.0 + */ +public class Configuration { + + /** + * The singleton instance + */ + private static Configuration INSTANCE; + /** + * Holds all configured values + */ + private final HashMap values = new HashMap<>(); + + /** + * Loads all values from all config sources. Errors out if values are missing. + * + * @param args The cli-args provided at software launch + * @param currentSoftwareVersion The current version number of the software using this util + * @param applicationBaseDir The directory in which the config folder and file are to be created + * @param configValues The enum.values() array of the ConfigValues enum. + * @throws ConfigIncompleteException if not all values without a default value were set using other configuration methods. + */ + private Configuration(String[] args, String currentSoftwareVersion, String applicationBaseDir, ConfigValues[] configValues) throws ConfigIncompleteException { + //load values set in cli-args + for (int i = 0; i < args.length; i++) { + for (ConfigValues v : configValues) { + if (v.getFlag().equalsIgnoreCase(args[i]) || v.getFlagAlias().equals(args[i])) { + if (args[i + 1].startsWith("-")) continue; + values.put(v, args[i + 1]); + i++; + } + } + } + + //load yet unset values from .env + for (ConfigValues v : configValues) { + if (values.containsKey(v)) continue; + if (System.getenv().containsKey(v.getEnvironmentVariable())) + values.put(v, System.getenv(v.getEnvironmentVariable())); + } + + //load yet unset values from toml config file + try (TomlConfiguration tomlConfig = new TomlConfiguration(applicationBaseDir, currentSoftwareVersion)) { + for (ConfigValues v : configValues) { + if (values.containsKey(v)) continue; + if (tomlConfig.contains(v.getTomlPath())) values.put(v, tomlConfig.getString(v.getTomlPath())); + } + } + + //load default values for as of yet unset methods + for (ConfigValues v : configValues) { + if (values.containsKey(v)) continue; + values.put(v, v.getDefaultValue()); + } + + //find all unset config values + List missingValues = new ArrayList<>(); + for (ConfigValues v : values.keySet()) { + if (values.get(v) == null) missingValues.add(v); + } + + //Error out on incomplete config + if (!missingValues.isEmpty()) throw new ConfigIncompleteException("Missing values: ", missingValues); + } + + /** + * Used to get the singleton instance + * + * @return the singleton instance. + */ + public static Configuration getInstance() { + if (INSTANCE == null) throw new ConfigException("Configuration not initialized"); + return INSTANCE; + } + + /** + * Initializes the singelton instance. + * + * @param args The cli-args provided at software launch + * @param currentSoftwareVersion The current version number of the software using this util + * @param applicationBaseDir The directory in which the config folder and file are to be created + * @param configValues The enum.values() array of the ConfigValues enum. + * @throws ConfigIncompleteException if not all values without a default value were set using other configuration methods. + */ + public static void init(String[] args, String currentSoftwareVersion, String applicationBaseDir, ConfigValues[] configValues) throws ConfigIncompleteException { + INSTANCE = new Configuration(args, currentSoftwareVersion, applicationBaseDir, configValues); + } + + /** + * Gets a value from the config + * + * @param v the key of the value to be returned + * @return the requested value + */ + public String get(ConfigValues v) { + return values.get(v); + } +} diff --git a/src/main/java/space/b00tload/utils/configuration/TomlConfiguration.java b/src/main/java/space/b00tload/utils/configuration/TomlConfiguration.java new file mode 100644 index 0000000..d87d31b --- /dev/null +++ b/src/main/java/space/b00tload/utils/configuration/TomlConfiguration.java @@ -0,0 +1,92 @@ +package space.b00tload.utils.configuration; + +import com.electronwill.nightconfig.core.file.FileConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import space.b00tload.utils.configuration.exceptions.ConfigException; + +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * Interfaces with the toml config file. + */ +public class TomlConfiguration implements AutoCloseable { + + private static FileConfig config; + private static Logger LOGGER; + + /** + * Loads the config file. + * + * @param APPLICATION_BASE The directory in which the config folder and file are to be created + * @param currentSoftwareVersion The current version number of the software using this util + */ + TomlConfiguration(String APPLICATION_BASE, String currentSoftwareVersion) { + LOGGER = LoggerFactory.getLogger(TomlConfiguration.class); + Path configBase = Paths.get(APPLICATION_BASE, "config"); + if (!configBase.toFile().exists()) //noinspection ResultOfMethodCallIgnored + configBase.toFile().mkdirs(); + config = FileConfig.builder(Paths.get(String.valueOf(configBase), "config.toml")).defaultResource("/empty.toml").autosave().autoreload().build(); + config.load(); + checkVersion(currentSoftwareVersion); + } + + /** + * Saves and closes the config file. + */ + public void close() { + config.save(); + config.close(); + } + + /** + * Checks whether the config version is the same as software version. Sends a warning to stderr if version was updated. + * @param currentVersion The current software version. + */ + public void checkVersion(String currentVersion) { + String configVersion = getString("file.version"); + if (currentVersion == null) { + LOGGER.error("Error: Failed to retrieve current version. Assuming 0.0.1"); + currentVersion = "0.0.1"; + } + if (configVersion == null) { + throw new ConfigException("Invalid config. \"file.version\" is not set."); + } else if (configVersion.equals("0.0.0")) { + LOGGER.info("New installation detected. Creating config file."); + setString("file.version", currentVersion); + } else if (currentVersion.compareTo(configVersion) < 0) { + LOGGER.warn("Software updated. Please check the wiki if and how to migrate."); + setString("file.version", currentVersion); + } + } + + /** + * Checks if the config contains a value at some path. + * @param key the path to check, each part separated by a dot. Example "a. b. c" + * @return true if the path is associated with a value, false if it's not. + */ + public boolean contains(String key) { + return config.contains(key); + } + + /** + * Gets a value from the config. + * @param key the value's path, each part separated by a dot. Example "a. b. c" + * @return the value at the given path, or null if there is no such value. + */ + public String getString(String key) { + return config.get(key); + } + + /** + * Sets a value to the config. + * @param key the value's path, each part separated by a dot. Example "a. b. c" + * @param value the value to set + */ + public void setString(String key, String value) { + config.set(key, value); + } + + +} diff --git a/src/main/java/space/b00tload/utils/configuration/exceptions/ConfigException.java b/src/main/java/space/b00tload/utils/configuration/exceptions/ConfigException.java new file mode 100644 index 0000000..ae70f44 --- /dev/null +++ b/src/main/java/space/b00tload/utils/configuration/exceptions/ConfigException.java @@ -0,0 +1,10 @@ +package space.b00tload.utils.configuration.exceptions; + +/** + * A wrapped {@code java.lang.RuntimeException} used for any generic errors while processing the config + * + * @author Alix von Schirp + * @since 1.0.0 + * @version 1.0.0 + */ +public class ConfigException extends RuntimeException {} diff --git a/src/main/java/space/b00tload/utils/configuration/exceptions/ConfigIncompleteException.java b/src/main/java/space/b00tload/utils/configuration/exceptions/ConfigIncompleteException.java new file mode 100644 index 0000000..76f47be --- /dev/null +++ b/src/main/java/space/b00tload/utils/configuration/exceptions/ConfigIncompleteException.java @@ -0,0 +1,88 @@ +package space.b00tload.utils.configuration.exceptions; + + +import space.b00tload.utils.configuration.ConfigValues; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * The ConfigIncompleteException is thrown when the config has missing values. + * + * @author Alix von Schirp + * @version 1.0.0 + * @since 1.0.0 + */ +public class ConfigIncompleteException extends RuntimeException { + + private final List missingValues; + + /** + * Constructs a new runtime exception with the specified detail message and missing values. The cause is not initialized, and may subsequently be initialized by a call to initCause. + * + * @param message the detail message. The detail message is saved for later retrieval by the {@link #getMessage()} method. + * @param missingValues a java.util.List of type space.b00tload.utils.configuration.ConfigValues containing all missing values + */ + public ConfigIncompleteException(String message, List missingValues) { + super(message + System.lineSeparator() + "\t-\t" + missingValues.stream().map(Object::toString).collect(Collectors.joining(";" + System.lineSeparator() + "\t-\t"))); + this.missingValues = missingValues; + } + + /** + * Constructs a new runtime exception with the specified detail message, missing values and cause. + * Note that the detail message associated with cause is not automatically incorporated in this runtime exception's detail message. + * + * @param message the detail message. The detail message is saved for later retrieval by the {@link #getMessage()} method. + * @param cause the cause (which is saved for later retrieval by the {@link #getMessage()} method). + * (A null value is permitted, and indicates that the cause is nonexistent or unknown.) + * @param missingValues a java.util.List of type space.b00tload.utils.configuration.ConfigValues containing all missing values + */ + public ConfigIncompleteException(String message, Throwable cause, List missingValues) { + super(message + System.lineSeparator() + "\t-\t" + missingValues.stream().map(Object::toString).collect(Collectors.joining(";" + System.lineSeparator() + "\t-\t")), cause); + this.missingValues = missingValues; + } + + /** + * Constructs a new runtime exception with the specified cause, the missing values and a detail message of {@code (cause==null ? null : cause.toString())} + * (which typically contains the class and detail message of {@code cause}). + * This constructor is useful for runtime exceptions that are little more than wrappers for other throwables. + * + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A {@code null} value is + * permitted, and indicates that the cause is nonexistent or + * unknown.) + * @param missingValues a java.util.List of type space.b00tload.utils.configuration.ConfigValues containing all missing values + */ + public ConfigIncompleteException(Throwable cause, List missingValues) { + super(cause); + this.missingValues = missingValues; + } + + /** + * Constructs a new runtime exception with the specified detail + * message, missing values, cause, suppression enabled or disabled, and writable + * stack trace enabled or disabled. + * + * @param message the detail message. + * @param cause the cause. (A {@code null} value is permitted, + * and indicates that the cause is nonexistent or unknown.) + * @param enableSuppression whether or not suppression is enabled + * or disabled + * @param writableStackTrace whether or not the stack trace should + * be writable + * @param missingValues a java.util.List of type space.b00tload.utils.configuration.ConfigValues containing all missing values + */ + public ConfigIncompleteException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, List missingValues) { + super(message + System.lineSeparator() + "\t-\t" + missingValues.stream().map(Object::toString).collect(Collectors.joining(";" + System.lineSeparator() + "\t-\t")), cause, enableSuppression, writableStackTrace); + this.missingValues = missingValues; + } + + /** + * Returns all missing values. + * @return a {@code java.util.List} with all missing values. + */ + public List getMissingValues() { + return missingValues; + } + +} diff --git a/src/main/resources/empty.toml b/src/main/resources/empty.toml new file mode 100644 index 0000000..f9ddc28 --- /dev/null +++ b/src/main/resources/empty.toml @@ -0,0 +1,2 @@ +[file] +version = "0.0.0"