A Minecraft library for saving, loading, updating, and commenting YAML configuration files.
This library facilitates creating, saving, loading, updating, and commenting YAML configuration files. It does so by automatically mapping instances of configuration classes to serializable maps which are first transformed into YAML and then saved to some specified file.
Information on how to import this library can be found at the end of this documentation. For a step-by-step tutorial that shows most features of this library in action check out the Tutorial page on the wiki!
BigInteger and BigDecimalLocalDate, LocalTime, LocalDateTime, and InstantUUID, File, Path, URL, and URIConfigurationSerializable types (e.g. ItemStack)This section contains a short usage example to get you started. The whole range of features is discussed in the following sections. Information on how to import this library is located at the end of this documentation.
For a step-by-step tutorial with a more advanced example check out the Tutorial page on the wiki.
If you want support for Bukkit classes like ItemStack, check out
the Configuration properties section.
public final class Example {
// * To create a configuration annotate a class with @Configuration and make sure that
// it has a no-args constructor.
// * Now add fields to that class and assign them default values.
// * That's it! Fields can be private; setters are not required.
@Configuration
public static class BaseConfiguration {
private String host = "127.0.0.1";
private int port = 1234;
// The library supports lists, sets, and maps.
private Set<String> blockedAddresses = Set.of("8.8.8.8");
// Fields can be ignored by making them final, transient, static or by
// annotating them with @Ignore.
private final double ignoreMe = 3.14;
}
// This library supports records; no @Configuration annotation required
public record User(
String username,
@Comment("Please choose a strong password.")
String password
) {}
// Subclassing of configurations and nesting of configurations in other configurations
// is also supported. Subclasses don't need to be annotated again.
public static final class UserConfiguration extends BaseConfiguration {
// You can add comments with the @Comment annotation. Each string in the comment
// array is written (as a comment) on a new line.
@Comment({"The admin user has full access.", "Choose a proper password!"})
User admin = new User("root", "toor"); // The User class is a record!
List<User> blockedUsers = List.of(
new User("user1", null), // null values are supported
new User("user2", null)
);
}
public static void main(String[] args) {
var configFile = Paths.get("/tmp/config.yml");
var config = new UserConfiguration();
// Save an instance to the configuration file
YamlConfigurations.save(configFile, UserConfiguration.class, config);
// Load a new instance from the configuration file
config = YamlConfigurations.load(configFile, UserConfiguration.class);
System.out.println(config.admin.username);
System.out.println(config.blockedUsers);
// Modify the configuration and save it again
config.blockedUsers.add(new User("user3", "pass3"));
YamlConfigurations.save(configFile, UserConfiguration.class, config);
}
}
By running the above code, a new YAML configuration is created
at /tmp/config.yml. Its content looks like this:
host: 127.0.0.1
port: 1234
blockedAddresses:
- 8.8.8.8
# The admin user has full access.
# Choose a proper password!
admin:
username: root
# Please choose a strong password.
password: toor
blockedUsers:
- username: user1
- username: user2
- username: user3
password: pass3
Two things are noticeable here:
blockedUsers list has a password mapping. This is
because null values are not output by default. That behavior can be changed
by the builder.user3 has no comment. This is due to
limitations of the YAML library. Configurations in lists, sets, or maps
cannot have their comments printed.In the following sections the term configuration type refers to any Java
record type or to any non-generic class that is directly or indirectly (i.e.
through subclassing) annotated with@de.exlll.configlib.Configuration.
Accordingly, the term configuration refers to an instance of such a type. A
configuration element is either a class field or a record component of a
configuration type.
To declare a configuration type, either define a Java record or annotate a class
with @Configuration and make sure that it has a no-args constructor. The
no-args constructor can be private. Inner classes (i.e. the ones that are
nested but not static) have an implicit synthetic constructor with at least
one argument and are, therefore, not supported.
Now simply add components to your record or fields to your class whose type is any of the supported types listed in the next section. You can (and should) initialize all fields of a configuration class with non-null default values.
A configuration type may only contain configuration elements of the following types:
| Type class | Types |
|---|---|
| Boolean types | boolean, and Boolean |
| Integer types | byte, short, int, long, and their respective wrapper types |
| Floating point types | float, double, and their respective wrapper types |
| Characters and strings | char, Character, String |
| Big numeric types | BigInteger, BigDecimal |
| Time related types | LocalTime, LocalDate, LocalDateTime, Instant |
| Utility types | UUID, File, Path, URL, URI |
| Enums | Any Java enum |
| Configurations | Any Java record or any class annotated with @Configuration |
ConfigurationSerializable |
All Bukkit classes that implement this interface, like ItemStack |
| Collections | (Nested) Lists, sets, maps*, or arrays of previously listed types |
(*) Map keys can only be of simple or enum type, i.e. they cannot be in
the Collections, Configurations, or ConfigurationSerializable type class.
For all types that are not listed in the table above, you can provide your own custom serializer.
The following class contains examples of types that this library supports:
public final class SupportedTypes {
boolean supported;
Character supported;
String supported;
LocalTime supported;
UUID supported;
ExampleEnum supported; // where 'ExampleEnum' is some Java enum type
ExampleConfig supported; // where 'ExampleConfig' is a class annotated with @Configuration
ExampleRecord supported; // where 'ExampleRecord' is a Java record
/* collection types */
List<BigInteger> supported;
Set<Double> supported;
LocalDate[] supported;
Map<ExampleEnum, ExampleConfig> supported;
/* nested collection types */
List<Map<ExampleEnum, LocalDate>> supported;
int[][] supported;
Map<Integer, List<Map<Short, Set<ExampleRecord>>>> supported;
// supported if a custom serializer is registered
java.awt.Point supported;
// supported when a special properties object is used (explained further below)
org.bukkit.inventory.ItemStack supported;
}
Examples of unsupported types
The following class contains examples of types that this library does (and will) not support:
public final class UnsupportedTypes<T> {
Map<Point, String> unsupported; // invalid map key
Map<List<String>, String> unsupported; // invalid map key
Box<String> unsupported; // custom parameterized type
List<? extends String> unsupported; // wildcard type
List<?> unsupported; // wildcard type
List<?>[] unsupported; // wildcard type
T unsupported; // type variable
List unsupported; // raw type
List[] unsupported; // raw type
List<String>[] unsupported; // generic array type
Set<Integer>[] unsupported; // generic array type
Map<Byte, Byte>[] unsupported; // generic array type
}
NOTE: Even though this library does not support these types, it is still
possible to serialize them by providing a custom serializer via
the @SerializeWith annotation. That
serializer then has to be applied to top-level type (i.e. nesting must be set
to 0, which is the default).
There are two ways to load and save configurations. Which way you choose depends on your liking. Both ways have five methods in common:
save method converts a configuration to a string in YAML format and
saves that string to a file. The file is created if it does not exist and is
overwritten otherwise.load method creates a new configuration instance and populates it with
values taken from a file. For classes, the no-args constructor is used to
create a new instance. For records, the canonical constructor is called.update method is a combination of load and save and the method you'd
usually want to use: it takes care of creating the configuration file if it
does not exist and otherwise updates it to reflect changes to (the
configuration elements of) the configuration type.write method works the same way as the save method but writes the
string to a java.io.OutputStream.read method works the same way as the load method but reads the values
from a java.io.InputStream.Example of update behavior when configuration file exists
Let's say you have the following configuration type
@Configuration
public final class C {
int i = 10;
int j = 11;
}
... and a YAML configuration file that contains:
i: 20
k: 30
Now, when you call the update method for that configuration type and file
using any of the two options listed below, the configuration instance
that update returns will have its i variable initialized to 20 and its j
variable will have its default of 11. After the operation, the configuration
file will contain the following content (note that k has been dropped):
i: 20
j: 11
To exemplify the usage of these five methods we assume for the following
sections that you have implemented the configuration type below and have access
to some regular java.nio.file.Path object configurationFile.
@Configuration
public final class Config { /* some fields */ }
The first option is to create a YamlConfigurationStore instance and use it to
save, load, or update configurations.
YamlConfigurationProperties properties = YamlConfigurationProperties.newBuilder().build();
YamlConfigurationStore<Config> store = new YamlConfigurationStore<>(Config.class, properties);
Config config1 = store.load(configurationFile);
store.save(config1, configurationFile);
Config config2 = store.update(configurationFile);
Using a YamlConfigurationStore directly is always more efficient than the
second option show below, especially if you are calling any of its method
multiple times.
The second option is to use the static methods from the YamlConfigurations
class.
Config config1 = YamlConfigurations.load(configurationFile, Config.class);
YamlConfigurations.save(configurationFile, Config.class, config1);
Config config2 = YamlConfigurations.update(configurationFile, Config.class);
Each of these methods has two additional overloads: One that takes a properties
object and another that lets you configure a properties object builder. For
example, the overloads of the load method are:
```java // overload 1 YamlConfigurationProperties properties = YamlConfigurationProperties.newBuilder().build(); Config config1 = YamlConfigurations.load(configurationFile, Config.class, properties);
// overload 2 Config config2 = YamlC
$ claude mcp add ConfigLib \
-- python -m otcore.mcp_server <graph>