Initial commit

This commit is contained in:
2024-05-07 21:24:40 +02:00
commit 4893442032
8 changed files with 458 additions and 0 deletions

38
.gitignore vendored Normal file
View File

@@ -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

63
pom.xml Normal file
View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>space.b00tload.utils</groupId>
<artifactId>ConfigurationUtilities</artifactId>
<version>1.0.0</version>
<name>ConfigurationUtilities</name>
<description>A library to load config from cli-args, .env, toml-config and default values.</description>
<inceptionYear>2024</inceptionYear>
<url>https://github.com/B00tLoad/ConfigurationUtilities</url>
<developers>
<developer>
<id>B00tLoad_</id>
<name>Alix von Schirp</name>
<email>alix.von-schirp@bootmedia.de</email>
<roles>
<role>developer</role>
</roles>
<timezone>Europe/Berlin</timezone>
<properties>
<disordHandle>@b00tload_</disordHandle>
<mastodonhandle>@b00tload_</mastodonhandle>
<mastodoninstance>order-of-gathering.de</mastodoninstance>
<blueskyHandle>@b00tload.space</blueskyHandle>
<pronouns>she/they</pronouns>
</properties>
</developer>
</developers>
<issueManagement>
<system>GitHub</system>
<url>https://github.com/B00tLoad/SnowflakeService/issues</url>
</issueManagement>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- logging-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.5</version>
</dependency>
<!-- config-->
<dependency>
<groupId>com.electronwill.night-config</groupId>
<artifactId>toml</artifactId>
<version>3.6.7</version>
</dependency>
</dependencies>
</project>

View File

@@ -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();
}

View File

@@ -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<ConfigValues, String> 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<ConfigValues> 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);
}
}

View File

@@ -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 <code>true</code> if the path is associated with a value, <code>false</code> 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 <code>null</code> 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);
}
}

View File

@@ -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 {}

View File

@@ -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 <code>ConfigIncompleteException</code> 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<ConfigValues> 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 <code>java.util.List</code> of type <code>space.b00tload.utils.configuration.ConfigValues</code> containing all missing values
*/
public ConfigIncompleteException(String message, List<ConfigValues> 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 <code>cause</code> 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 <code>null</code> value is permitted, and indicates that the cause is nonexistent or unknown.)
* @param missingValues a <code>java.util.List</code> of type <code>space.b00tload.utils.configuration.ConfigValues</code> containing all missing values
*/
public ConfigIncompleteException(String message, Throwable cause, List<ConfigValues> 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 <code>java.util.List</code> of type <code>space.b00tload.utils.configuration.ConfigValues</code> containing all missing values
*/
public ConfigIncompleteException(Throwable cause, List<ConfigValues> 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 <code>java.util.List</code> of type <code>space.b00tload.utils.configuration.ConfigValues</code> containing all missing values
*/
public ConfigIncompleteException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, List<ConfigValues> 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<ConfigValues> getMissingValues() {
return missingValues;
}
}

View File

@@ -0,0 +1,2 @@
[file]
version = "0.0.0"