A java library for parsing yaml config files with support for comments and default values.
- Loading and saving of config files from various sources (File, Path, Memory)
- Sections (including nested sections)
- Default comments and config header
- Default values for options
- Keeping of user comments and formatting (as long as the config is not broken)
- Config versioning and migration
- Automatically fixing broken configs (adding missing options, removing unknown options, resetting options with invalid values)
- Custom type serializers
- Validators for options
- Option dependencies for validators and type serializers
First you need to create a ConfigLoader
to load your config from a file. You need to pass the class of the config object you want to load to the constructor.
ConfigLoader<TestConfig> loader = new ConfigLoader<>(TestConfig.class);
After that you can load the config from a file.
ConfigContext<TestConfig> configContext = loader.load(ConfigProvider.path(FileSystems.getDefault().getPath("test.yml")));
The returned ConfigContext
object contains the instance of the loaded config (if the config is not static) and provides a reload
and save
method.
configContext.reload();
configContext.save();
The ConfigProvider
class gives you the ability to choose where the config is loaded from and where it is saved to. This also allows for fully in-memory configs.
Some default providers can be created using the static methods of the ConfigProvider
class.
ConfigProvider#file(final File file);
ConfigProvider#path(final Path path);
ConfigProvider#memory(final String content, final Consumer<String> contentConsumer);
ConfigProvider#memory(final Supplier<String> contentSupplier, final Consumer<String> contentConsumer);
The ConfigLoader
has some settings that can be changed to modify the behavior of the loader.
Here are some example options, but there are more available.
loader.getConfigOptions().setResetInvalidOptions(true); //Reset options with invalid values
loader.getConfigOptions().setSpaceBetweenOptions(true); //Add a newline between options
loader.getConfigOptions().setRewriteConfig(true); //Rewrite the config file on load (resetting user comments and formatting)
loader.getConfigOptions().setNotReloadableComment(false); //Add a comment to options that are not reloadable
...
Check out the ConfigOptions
class for all available options. All options are documented using javadoc.
When wanting to use custom types in your config, you need to create a type serializer for that specific type.
Here is an example of a type serializer for the Month
enum:
import net.lenni0451.optconfig.serializer.ConfigTypeSerializer;
import java.time.Month;
import java.util.Locale;
public class TestMonthSerializer extends ConfigTypeSerializer<TestConfig, Month> {
//The required default constructor taking the config instance as parameter
public TestMonthSerializer(final TestConfig config) {
super(config);
}
//Deserialize the object from a value in the config
//The type may vary depending on the type of the option
@Override
public Month deserialize(Class<Month> typeClass, Object serializedObject) {
//this.config is the instance of the config class
//You can access other options using it. Setting dependencies is recommended to ensure the correct order of loading.
//Make sure to handle invalid values
//If the type is not usable, throw an exception (e.g. InvalidSerializedObjectException)
return Month.valueOf(((String) serializedObject).toUpperCase(Locale.ROOT));
}
@Override
public Object serialize(Month object) {
//Serialize the object to a yaml compatible object
return object.name().toLowerCase(Locale.ROOT);
}
}
The OptConfig
annotation takes a config version (default is 1) which can be used to migrate the config from one version to another.
Registering migrators is not required, but recommended when the config structure changes. When no migrator is found for a version, the changed values will be reset to their default values.
Here is an example of a migrator:
import net.lenni0451.optconfig.migrate.ConfigMigrator;
import java.util.Map;
public class TestMigrator implements ConfigMigrator {
@Override
public void migrate(int currentVersion, Map<String, Object> loadedValues) {
loadedValues.remove("TestOption2"); //Removed old option
loadedValues.put("TestOption3", "Test String 3"); //Added new option
Object value = loadedValues.remove("TestOption");
loadedValues.put("TestOption4", value); //Renamed option
}
}
The migrator takes the current version of the config and a map of all loaded values.
The map contains a name-value pair for each option in the config.
Section are in the map as a nested map with the section name as key.
When the migrator is done, the map is used to update the config object.
Here is an example of a config class using all features of the library:
import net.lenni0451.optconfig.ConfigContext;
import net.lenni0451.optconfig.annotations.*;
import net.lenni0451.optconfig.migrate.ConfigMigrator;
import java.time.Month;
@OptConfig(header = {
"-----------------------------------",
"| This is a great example config! |",
"-----------------------------------"
}) //The header will be appended to the top of the config file
@Migrator(from = 1, to = 2, migrator = ConfigMigrator.class) //Migrator for version 1 to 2
@Migrator(from = 2, to = 3, migrator = ConfigMigrator.class) //Migrator for version 2 to 3
public class TestConfig {
public ConfigContext<TestConfig> context; //This field is automatically set by the ConfigLoader
//Reloadable by default
@Option("TestOption") //The name of the option in the config
@Description({"This is a test option", "It is used for testing"}) //The description of the option
public String test = "Test String";
@NotReloadable //Not reloadable
@Option("TestOption2")
@Description({"This is a test option", "It is used for testing"})
public String test2 = "Test String 2";
@Option //No name specified -> Field name is used
//@NotReloadable //Sections can also be marked as not reloadable. This will also affect all options in this section
@Description("This is a test section") //Sections can have descriptions
public TestSection section; //Sections are automatically instantiated. Instantiating them manually will also work
@Section //Sections need to be annotated with @Section
public static class TestSection {
@Option(dependencies = "test") //This option depends on "test" being loaded first. The value of "test" can be accessed in the validator or type serializers
@Description("Enum option")
@TypeSerializer(TestMonthSerializer.class) //Custom type serializer only for this option
public Month month = Month.JUNE;
@Option //No name specified -> Field name is used
//No description specified -> No comments are added
public String test = "Test String 3";
@Validator("test") //Validate the value of the option "test"
private String validate(String test) { //Validators need to take and return the same type as the option
//The returned value is used instead of the value in the config
if (test.length() > 20) return "String is too long";
else return test;
}
}
}
The serialized config will look like this:
#-----------------------------------
#| This is a great example config! |
#-----------------------------------
#This is a test option
#It is used for testing
TestOption: Test String
#This is a test option
#It is used for testing
TestOption2: Test String 2
#This is a test section
section:
#Enum option
month: june
test: Test String 3
Check out maven central for the latest version.
repositories {
mavenCentral()
}
dependencies {
implementation "net.lenni0451:optconfig:x.x.x"
}
<dependency>
<groupId>net.lenni0451</groupId>
<artifactId>optconfig</artifactId>
<version>x.x.x</version>
</dependency>