1. Your First Config
This tutorial walks through MittenLib's Configuration Annotation Processor System.
This feature allows you to define configuration structures using simple, clean interfaces, letting the compiler generate all the heavy lifting for parsing, validation, and serialization.
Let's start by defining a basic configuration for a server connection.
The Configuration
Suppose our plugin connects to some server. We want to let admins configure the host and port of the server.
Our config.yml might look like this:
host: localhost
port: 25565
MittenLib makes parsing and validating this config easy and safe!
Defining the Config Structure
We first have to define a type which describes the shape of our configuration. This is often called a DTO (Data Transfer Object) or Config Type.
You can use either an interface or a class to define your configuration structure. Interfaces are the recommended option in most situations.
- Interface (Recommended)
- Class (Alternative)
package me.bristermitten.mittenlib.docs.tutorial;
import me.bristermitten.mittenlib.config.Config;
@Config
public interface SimpleInterfaceConfig {
String host();
int port();
}
What happens here?
- The
annotation tells the MittenLib annotation processor to inspect this type during compilation.@Config - The processor inspects the
hostandportmethods. - It generates a full implementation class (
SimpleInterfaceConfigImpl), along with deserializers and serializers for loading/saving data.
When using classes, the recommended naming convention is to end the class name with DTO.
We then add fields to describe each field in our config. Access modifiers and final are ignored -- all
properties will be public, non-static, and final.
package me.bristermitten.mittenlib.docs.tutorial;
import me.bristermitten.mittenlib.config.Config;
@Config
public class SimpleClassConfigDTO {
String host;
int port;
}
What happens here?
- The
annotation tells the MittenLib annotation processor to inspect this type during compilation.@Config - The processor inspects the
hostandportfields. - It generates a full implementation class (
SimpleInterfaceConfig), along with deserializers and serializers for loading/saving data.
The Generated Implementation
All properties are non-nullable by default, and always immutable.
Data Class
The generated class provides a type-safe, immutable representation of your configuration.
// Simplified view of the generated implementation
public class SimpleInterfaceConfigImpl implements SimpleInterfaceConfig {
private final String host;
private final int port;
public SimpleInterfaceConfigImpl(String host, int port) {
this.host = host;
this.port = port;
}
@Override
public String host() { return host; }
@Override
public int port() { return port; }
// Also generated: withHost(), withPort(), equals(), hashCode(), toString()
}
Deserializer and Serializer
MittenLib also generates separate deserializer and serializer classes for your config:
public class SimpleInterfaceConfigDeserializer implements DeserializationFunction<SimpleInterfaceConfig> {
private Result<String> deserializeHost(DeserializationContext context) {
// ...
}
private Result<Integer> deserializePort(DeserializationContext context) {
// ...
}
// the function to actually deserialize the config!
public Result<SimpleInterfaceConfig> apply(final DeserializationContext context) {
// ...
}
}
public class SimpleInterfaceConfigSerializer implements SerializationFunction<SimpleInterfaceConfig> {
// This function serializes the config to a DataTree
public DataTree apply(SimpleInterfaceConfig config, SerializationContext context) {
// ...
}
private DataTree serializeHost(String value, SerializationContext context) {
// ...
}
private DataTree serializePort(int value, SerializationContext context) {
// ...
}
}
Now that we have our basic structure, let's look at how we can add defaults, optional fields, and validation in the next step.