You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ConfigLib/README.md

936 lines
36 KiB
Markdown

2 years ago
# ConfigLib
**A Minecraft library for saving, loading, updating, and commenting YAML configuration files.**
This library facilitates creating, saving, loading, updating, and commenting YAML configuration
2 years ago
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](#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](https://github.com/Exlll/ConfigLib/wiki/Tutorial) page on the wiki!
2 years ago
## Features
8 years ago
2 years ago
* Automatic creation, saving, loading, and updating of configuration files
* Support for comments through annotations
* Support for all primitive types, their wrapper types, and strings
* Support for all Java enums, records, and POJOs (+ inheritance!)
* Support for (nested) lists, sets, arrays, and maps
* Support for `BigInteger` and `BigDecimal`
* Support for `LocalDate`, `LocalTime`, `LocalDateTime`, and `Instant`
* Support for `UUID`, `File`, `Path`, `URL`, and `URI`
* Support for Bukkit's `ConfigurationSerializable` types (e.g. `ItemStack`)
* Option to format the names of configuration elements
2 years ago
* Option to exclude fields from being converted
* Option to customize null handling
* Option to customize serialization by providing your own serializers
* Option to add headers and footers to configuration files
2 years ago
* ...and a few more!
2 years ago
## Usage example
8 years ago
2 years ago
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](#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](https://github.com/Exlll/ConfigLib/wiki/Tutorial) page on the wiki.
If you want support for Bukkit classes like `ItemStack`, check out
the [Configuration properties](#configuration-properties) section.
```java
2 years ago
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.
2 years ago
@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");
2 years ago
// 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.
2 years ago
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!
2 years ago
List<User> blockedUsers = List.of(
new User("user1", null), // null values are supported
new User("user2", null)
);
}
2 years ago
public static void main(String[] args) {
var configFile = Paths.get("/tmp/config.yml");
var config = new UserConfiguration();
2 years ago
// Save an instance to the configuration file
YamlConfigurations.save(configFile, UserConfiguration.class, config);
2 years ago
// 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);
2 years ago
// Modify the configuration and save it again
config.blockedUsers.add(new User("user3", "pass3"));
YamlConfigurations.save(configFile, UserConfiguration.class, config);
2 years ago
}
}
```
2 years ago
By running the above code, a new YAML configuration is created at `/tmp/config.yml`. Its content
looks like this:
```yaml
host: 127.0.0.1
port: 1234
blockedAddresses:
- 8.8.8.8
2 years ago
# 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
```
2 years ago
Two things are noticeable here:
1. Not every user in the `blockedUsers` list has a `password` mapping. This is because null values
are not output by default. That behavior can be changed by the builder.
2. The password of the user with username `user3` has no comment. This is due to limitations of
2 years ago
the YAML library. Configurations in lists, sets, or maps cannot have their comments printed.
## General information
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
2 years ago
`@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.
2 years ago
### Declaring configuration types
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.
2 years ago
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.
2 years ago
### Supported types
A configuration type may only contain configuration elements of the following types:
2 years ago
| 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 |
2 years ago
(*) Map keys can only be of simple or enum type, i.e. they cannot be in the `Collections`,
`Configurations`, or `ConfigurationSerializable` type class.
2 years ago
For all types that are not listed in the table above, you can provide your
own [custom serializer](#custom-serializers).
#### Examples of supported types
2 years ago
The following class contains examples of types that this library supports:
```java
2 years ago
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
2 years ago
/* collection types */
List<BigInteger> supported;
Set<Double> supported;
LocalDate[] supported;
Map<ExampleEnum, ExampleConfig> supported;
2 years ago
/* 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;
}
```
2 years ago
<details>
<summary>Examples of unsupported types</summary>
The following class contains examples of types that this library does (and will) not support:
```java
2 years ago
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`](#the-serializewith-annotation)
annotation. That serializer then has to be applied to top-level type (i.e. `nesting` must be set
to `0`, which is the default).
2 years ago
</details>
### Loading and saving configurations
There are two ways to load and save configurations. Which way you choose depends on your liking.
Both ways have three methods in common:
* The `save` method saves a configuration to a file. The file is created if it does not exist and
is overwritten otherwise.
* The `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.
* The `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.
2 years ago
<details>
<summary>Example of <code>update</code> behavior when configuration file exists</summary>
Let's say you have the following configuration type:
```java
@Configuration
public final class C {
int i = 10;
int j = 11;
}
```
2 years ago
... and a YAML configuration file that contains:
```yaml
i: 20
k: 30
```
2 years ago
Now, when you use one of the methods below to call `update` for that configuration type and file,
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:
2 years ago
```yaml
i: 20
j: 11
```
2 years ago
</details>
2 years ago
To exemplify the usage of these three 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`.
2 years ago
```java
@Configuration
public final class Config { /* some fields */ }
```
2 years ago
#### Way 1
2 years ago
The first way is to create a configuration store and use it directly to save, load, or update
configurations.
```java
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);
```
2 years ago
#### Way 2
The second way is to use the static methods from the `YamlConfigurations` class.
2 years ago
```java
Config config1 = YamlConfigurations.load(configurationFile, Config.class);
YamlConfigurations.save(configurationFile, Config.class, config1);
Config config2 = YamlConfigurations.update(configurationFile, Config.class);
```
2 years ago
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:
2 years ago
```java
// overload 1
YamlConfigurationProperties properties = YamlConfigurationProperties.newBuilder().build();
Config config1 = YamlConfigurations.load(configurationFile, Config.class, properties);
2 years ago
// overload 2
Config config2 = YamlConfigurations.load(
2 years ago
configurationFile,
Config.class,
builder -> builder.inputNulls(true).outputNulls(false)
);
```
<hr>
All three methods can also be passed a Java record instead of a class. To provide default values
for records when calling the `update` method, you can add a constructor with no parameters that
initializes its components. This constructor is only called if the configuration file does not
exist.
```java
record User(String name, String email) {
User() { this("John Doe", "john@doe.com"); }
}
User user = YamlConfigurations.update(configurationFile, User.class);
```
### Configuration properties
2 years ago
Instances of the `ConfigurationProperties` class allow customization of how configurations are
stored and loaded. To create such an instance, instantiate a new builder using
the `YamlConfigurationProperties.newBuilder()` method, configure it, and finally call its `build()`
method. Alternatively, you can use the `toBuilder()` method of an
existing `YamlConfigurationProperties` to create a new builder that is initialized with values
takes from the properties object.
2 years ago
Check out the methods of the builder class to see which configuration options are available.
2 years ago
#### Support for Bukkit classes like `ItemStack`
2 years ago
There is a special `YamlConfigurationProperties` object with name `BUKKIT_DEFAULT_PROPERTIES`
that adds support for Bukkit's `ConfigurationSerializable` types. If you want to use any of these
types in your configuration, you have to use that object as a starting point:
2 years ago
```java
YamlConfigurationProperties properties = ConfigLib.BUKKIT_DEFAULT_PROPERTIES.toBuilder()
// ...further configure the builder...
.build();
```
To get access to this object, you have to import `configlib-paper` instead of `configlib-yaml` as
described in the [Import](#import) section.
2 years ago
### Comments
The configuration elements of a configuration type can be annotated with the `@Comment` annotation.
This annotation takes an array of strings. Each of these strings is written onto a new line as a
comment. The strings can contain `\n` characters. Empty strings are written as newlines (not as
comments).
2 years ago
If a configuration type _C_ that defines comments is used (as a configuration element) within
another configuration type, the comments of _C_ are written with the proper indentation. However, if
instances of _C_ are stored inside a collection, their comments are not printed when the collection
is written.
2 years ago
Serializing the following configuration as YAML ...
```java
@Configuration
public final class ExampleConfiguration {
@Comment({"Hello", "", " ", "World"})
private String commentedField = "commented field";
8 years ago
}
```
2 years ago
... results in the YAML file shown below:
```yaml
# Hello
#
# World
commentedField: commented field
```
Similarly, if you define the following record configuration and save it ...
```java
record Address(@Comment("The street") String street) {}
record User(@Comment("The name") String name, @Comment("The address") Address address) {}
User user = new User("John Doe", new Address("10 Downing St"));
```
... you get:
```yaml
# The name
name: John Doe
# The address
address:
# The street
street: 10 Downing St
```
### Subclassing
2 years ago
Subclassing of configurations types is supported. Subclasses of configuration types don't need to be
annotated with `@Configuration`. When a configuration is written, the fields of parent classes
are written before the fields of the child in a top to bottom manner. Parent configurations can
be `abstract`.
2 years ago
#### Shadowing of fields
2 years ago
Shadowing of fields refers to the situation where a subclass of configuration has a field that has
the same name as a field in one of its super classes. Shadowing of fields is currently not
supported. (This restriction might easily be lifted. If you need this feature, please open an issue
and describe how to handle name clashes.)
### Ignoring and filtering fields
Fields that are `final`, `static`, `transient` or annotated with `@Ignore` are neither serialized
nor updated during deserialization. You can filter out additional fields by providing an instance of
`FieldFilter` to the configuration properties. Record components cannot be filtered.
### Handling of missing and `null` values
#### Missing values
When a configuration file is read, values that correspond to a configuration element might be
missing. That can happen, for example, when somebody deleted that value from the configuration file,
when you add configuration elements to your configuration type, or when the `NameFormatter` that
was used to create that file is replaced.
In such cases, fields of configuration classes keep the default value you assigned to them and
record components are initialized with the default value of their corresponding type.
#### Null values
**NOTE:** Null values written to a configuration file generally don't give any indication about
which kinds of values the configuration expects. Therefore, they not only make it harder for the
users of that configuration file to properly configure it, but they might also prevent loading a
configuration if the values the users set are of the wrong type.
Although strongly discouraged, null values are supported and `ConfigurationProperties` let you
configure how they are handled when serializing and deserializing a configuration:
* By setting `outputNulls` to false, configuration elements, and collection elements that
are null are not output. Any comments that belong to such fields are also not written.
* By setting `inputNulls` to false, null values read from the configuration file are treated as
missing and are, therefore, handled as described in the section above.
* By setting `inputNulls` to true, null values read from the configuration file override the
corresponding default values of a configuration class with null or set the component value of a
record type to null. If the configuration element type is primitive, an exception is thrown.
The following code forbids null values to be output but allows null values to be input. By default,
both are forbidden which makes the call to `outputNulls` in this case redundant.
```java
YamlConfigurationProperties.newBuilder()
.outputNulls(false)
.inputNulls(true)
.build();
2 years ago
```
### Formatting the names of configuration elements
You can define how the names of configuration elements are formatted by configuring the
configuration properties with a custom formatter. Formatters are implementations of
the `NameFormatter` interface. You can implement this interface yourself or use one of the several
formatters this library provides. These pre-defined formatters can be found in the `NameFormatters`
class.
The following code formats fields using the `IDENTITY` formatter (which is the default).
```java
YamlConfigurationProperties.newBuilder()
.setNameFormatter(NameFormatters.IDENTITY)
.build();
```
2 years ago
### Type conversion and serializer selection
2 years ago
Before instances of the types listed in the [supported types](#supported-types) section can be
stored, they need to be converted into serializable types (i.e. into types the underlying YAML
library knows how to handle). The conversion happens according to the following table:
| Source type | Target type |
|-----------------------------|------------------|
| Boolean types | `Boolean` |
| Integer types | `Long` |
| Floating point types | `Double` |
| Characters and strings | `String` |
| Big numeric types | `String` |
| Time related types | `String` |
| Utility types | `String` |
| Enums | `String` |
| Configurations | `Map<String, ?>` |
| `Set<S>` | `List<T>`* |
| `List<S>` | `List<T>` |
| `S[]` | `List<T>` |
| `Map<S1, S2>` | `Map<T1, T2>` |
| `ConfigurationSerializable` | `String` |
2 years ago
(*) By default, sets are serialized as lists. This can be changed through the configuration
properties. This also means that `Set`s are valid target types.
#### Serializer selection
To convert the value of a configuration element `E` with (source) type `S` into a serializable
value of some target type, a serializer has to be selected. Serializers are instances of
2 years ago
the `de.exlll.configlib.Serializer` interface and are selected based on `S`. Put differently,
serializers are, by default, always selected based on the compile-time type of `E` and never on the
runtime type of its value.
2 years ago
<details>
<summary>Why should I care about this?</summary>
This distinction makes a difference (and might lead to confusion) when you have configuration
elements that are configuration classes, and you extend those classes. Concretely,
assume you have written two configuration classes `A` and `B` where `B extends A`. Then, if you
use `A a = new B()` in your main configuration, only the fields of a `A` will be stored when you
save your main configuration. That is because the serializer of field `a` was selected based on the
compile-time type of `a` which is `A` and not `B`. The same happens if you have a `List<A>` and put
instances of `B` (or some other subclass of `A`) in it.
2 years ago
</details>
You can override the default selection by annotating a configuration
element with [`@SerializeWith`](#the-serializewith-annotation), by annotating a type
with `@SerializeWith`, or by adding your own serializer for `S` to the configuration properties.
When you do so, it can happen that there multiple serializers available for a particular
configuration element and its type. In that case, one of them chosen according to the following
precedence rules:
1. If the element is annotated with `@SerializeWith` and the `nesting` matches, the serializer
referenced by the annotation is selected.
2. Otherwise, if the `ConfigurationProperties` contain a serializer for the type in question, that
serializer is returned.
* Serializers created by factories that were added through `addSerializerFactory` for some type
take precedence over serializers added by `addSerializer` for the same type.
3. If the type is annotated `@SerializeWith`, the serializer referenced by the annotation is
selected.
4. If the type is annotated with an annotation which is annotated with `@SerializeWith`, the
serializer referenced by `@SerializeWith` is returned.
5. If this library defines a serializer for that type, that serializer is selected.
6. Ultimately, if no serializer can be found, an exception is thrown.
For lists, sets, and maps, the algorithm is applied to their generic type arguments recursively
first.
##### The `SerializeWith` annotation
The `SerializeWith` annotation enforces the use of the specified serializer for a configuration
element or type. It can be applied to configuration elements (i.e. class fields and
record components), to types, and to other annotations.
```java
@SerializeWith(serializer = MyPointSerializer.class)
Point point;
```
```java
@SerializeWith(serializer = SomeClassSerializer.class)
public final class SomeClass {/* ... */}
```
The serializer referenced by this annotation is selected regardless of whether the annotated type or
type of configuration element matches the type the serializer expects.
If the annotation is applied to a configuration element and that element is an array, list, set, or
map, a nesting level can be set to apply the serializer not to the top-level type but to its
elements. For maps, the serializer is applied to the values and not the keys.
```java
@SerializeWith(serializer = MySetSerializer.class, nesting = 1)
List<Set<String>> list;
```
Setting `nesting` to an invalid value, i.e. a negative one or one that is greater than the number
of levels the element actually has, results in the serializer not being selected. For type
annotations, the `nesting` has no effect.
<details>
<summary>More <code>nesting</code> examples</summary>
In this example...
```java
@SerializeWith(serializer = MySetSerializer.class, nesting = 1)
List<Set<String>> list;
```
* a nesting of `0` would apply the serializer to `list` (which is of
type `List<Set<String>>`),
* a nesting of `1` would apply it to the `Set<String>` elements within `list`, and
* a nesting of `2` would apply it to the strings within the sets of `list`.
However, since the referenced serializer `MySetSerializer` most likely expects `Set`s as input,
setting `nesting` to `0` or `2` would result in an exception being thrown when the configuration
is serialized.
Some more examples:
```java
// MyListSerializer is applied to 'list'
@SerializeWith(serializer = MyListSerializer.class)
List<Set<String>> list;
// MySetSerializer is applied to the Set<String> elements of 'list'
@SerializeWith(serializer = MySetSerializer.class, nesting = 1)
List<Set<String>> list;
// MyStringSerializer is applied to the strings within the set elements of 'list'
@SerializeWith(serializer = MyStringSerializer.class, nesting = 2)
List<Set<String>> list;
// MyMap0Serializer is applied to 'map'
@SerializeWith(serializer = MyMap0Serializer.class)
Map<Integer, Map<String, Double>> map;
// MyMap1Serializer is applied to the Map<String, Double> values of 'map'
@SerializeWith(serializer = MyMap1Serializer.class, nesting = 1)
Map<Integer, Map<String, Double>> map;
// MyDoubleSerializer is applied to the doubles within the nested values of 'map'
@SerializeWith(serializer = MyDoubleSerializer.class, nesting = 2)
Map<Integer, Map<String, Double>> map;
```
</details>
#### The `@Polymorphic` annotation
The `@Polymorphic` annotation indicates that the annotated type is polymorphic. Serializers for
polymorphic types are not selected based on the compile-time types of configuration elements, but
instead are chosen at runtime based on the actual types of their values.
This enables adding instances of subclasses / implementations of a polymorphic type to collections.
The subtypes must be valid configurations.
```java
@Polymorphic
@Configuration
static abstract class A { ... }
static final class Impl1 extends A { ... }
static final class Impl2 extends A { ... }
List<A> as = List.of(new Impl1(...), new Impl2(...), ...);
```
For correct deserialization, if an instance of polymorphic type (or one of its implementations /
subclasses) is serialized, an additional property that holds type information is added to its
serialization. By default, that type information is the Java class name of the actual type. It is
possible to provide type aliases by using the `PolymorphicTypes` annotation.
```java
@Polymorphic
@PolymorphicTypes({
@PolymorphicTypes.Type(type = Impl1.class, alias = "IMPL_1"),
@PolymorphicTypes.Type(type = Impl2.class, alias = "IMPL_2")
})
interface B { ... }
record Impl1(...) implements B { ... }
record Impl2(...) implements B { ... }
```
### Custom serializers
If you want to add support for a type that is not a Java record or whose class is not annotated
with `@Configuration`, or if you don't like how one of the supported types is serialized by default,
you can write your own custom serializer.
Serializers are instances of the `de.exlll.configlib.Serializer` interface. When implementing that
interface you have to make sure that you convert your source type into one of the valid target types
listed in [type conversion](#type-conversion-and-serializer-selection) section.
The serializer then has to be registered through a `ConfigurationProperties` object or alternatively
be applied to a configuration element or type
with [`@SerializeWith`](#the-serializewith-annotation). If you want to use the `@SerializeWith`
annotation, your serializer class must either have a constructor with no parameters or one with
exactly one parameter of type [`SerializerContext`](#the-serializercontext-interface).
2 years ago
The following `Serializer` serializes instances of `java.awt.Point` into strings and vice versa.
2 years ago
```java
public final class PointSerializer implements Serializer<Point, String> {
@Override
public String serialize(Point element) {
return element.x + ":" + element.y;
}
2 years ago
@Override
public Point deserialize(String element) {
String[] parts = element.split(":");
int x = Integer.parseInt(parts[0]);
int y = Integer.parseInt(parts[1]);
return new Point(x, y);
}
}
2 years ago
```
```java
YamlConfigurationProperties properties = YamlConfigurationProperties.newBuilder()
.addSerializer(Point.class, new PointSerializer())
.build();
```
##### The `SerializerContext` interface
Instances of the `SerializerContext` interface contain contextual information for custom
serializers. A context object gives access to the configuration properties, configuration element,
and the annotated type for which the serializer was selected.
Context objects can be obtained when adding serializer factories through the `addSerializerFactory`
method:
```java
public final class PointSerializer implements Serializer<Point, String> {
private final SerializerContext context;
public PointSerializer(SerializerContext context) {
this.context = context;
}
// implementation ...
}
```
```java
YamlConfigurationProperties properties = YamlConfigurationProperties.newBuilder()
.addSerializerFactory(Point.class, PointSerializer::new)
.build();
```
Custom serializers used with `@SerializeWith` are allowed to declare a constructor with one
parameter of type `SerializerContext`. If such a constructor exists, a context object is passed to
it when the serializer is instantiated by this library.
### Changing the type of configuration elements
2 years ago
Changing the type of configuration elements is not supported. If you change the type of one of
these but your configuration file still contains a value of the old type, a type mismatch will
occur when loading a configuration from that file. Instead, remove the old element and add a new one
with a different name.
### Recursive type definitions
Recursive type definitions are currently not allowed but might be supported in a future version if
this feature is requested.
<details>
<summary>Examples of recursive type definitions</summary>
Neither direct nor indirect recursive type definitions are supported.
```java
public final class RecursiveTypDefinitions {
// Direct recursive definition
@Configuration
static final class R {
R r;
}
// Indirect recursive definition
@Configuration
static final class R1 {
R2 r2;
}
@Configuration
static final class R2 {
R1 r1;
}
}
```
</details>
## Project and repository structure
This project contains three classes of modules:
* The `configlib-core` module contains most of the logic of this library. In it, you can find (among
other things), the object mapper that converts configuration instances to maps (and vice versa),
most serializers, and the classes responsible for the extraction of comments. It does not
contain anything Minecraft related.
* The `configlib-yaml` module contains the classes that can save configuration instances as YAML
files and instantiate new instances from such files. This module does not contain anything
Minecraft related, either.
* The `configlib-paper`, `configlib-velocity`, and `configlib-waterfall` modules contain basic
plugins that are used to conveniently load this library. These three modules shade the `-core`
module, the `-yaml` module, and the YAML parser when the `shadowJar` task is executed. The shaded
jar files are released on the [releases page](https://github.com/Exlll/ConfigLib/releases).
* The `configlib-paper` module additionally contains the `ConfigLib.BUKKIT_DEFAULT_PROPERTIES`
object which adds support for the serialization of Bukkit classes like `ItemStack` as
described [here](#support-for-bukkit-classes-like-itemstack).
The GitHub repository of this project uses two branches:
* The `master` branch contains the functionality of the latest release version.
* The `dev` branch contains the newest, possibly unstable features and refactorings.
If you plan to contribute to this project, please base your commits on the `dev` branch.
## Import
2 years ago
To use this library, import it into your project with Maven or Gradle. Examples of how to do that
are at the end of this section within the spoilers. Currently, there are two repositories from
which you can choose: [jitpack.io](https://jitpack.io/#Exlll/ConfigLib) and GitHub (which requires
authentication, see this [issue](https://github.com/Exlll/ConfigLib/issues/12) if you have any
problems).
2 years ago
This library has additional dependencies (namely, a YAML parser) which are not exposed by the
artifact you import. You can download _plugin versions_ of this library that bundle all its
dependencies. The artifacts of these versions can be found on
2 years ago
the [releases page](https://github.com/Exlll/ConfigLib/releases) where you can identify them by
their `-paper-`, `-waterfall-`, and `-velocity-` infix and `-all` suffix. Except for
the `-paper-` version, the other plugin versions currently do not add any additional features.
A benefit of these versions is that they make it easier for you to update this library if you have
written multiple plugins that use it. If you plan to use these versions, don't forget to add the
plugin as a dependency to the `plugin.yml` (for Paper and Waterfall) or to the dependencies array
(for Velocity) of your own plugin.
2 years ago
Alternatively, if you don't want to use an extra plugin, you can shade the `-yaml` version with its
2 years ago
YAML parser yourself.
### Import examples
If you want serialization support for Bukkit classes like `ItemStack`, replace `configlib-yaml`
with `configlib-paper` (see [here](#support-for-bukkit-classes-like-itemstack)).
<details>
<summary>
Import via <a href="https://jitpack.io/#Exlll/ConfigLib">jitpack.io</a>
</summary>
**Maven**
```xml
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
<dependency>
<groupId>com.github.Exlll.ConfigLib</groupId>
<artifactId>configlib-yaml</artifactId>
<version>v4.2.0</version>
</dependency>
```
**Gradle**
```groovy
repositories { maven { url 'https://jitpack.io' } }
dependencies { implementation 'com.github.Exlll.ConfigLib:configlib-yaml:v4.2.0' }
```
```kotlin
repositories { maven { url = uri("https://jitpack.io") } }
dependencies { implementation("com.github.Exlll.ConfigLib:configlib-yaml:v4.2.0") }
```
</details>
<details>
<summary>
Import via GitHub
</summary>
Importing via GitHub requires authentication. Check
this [issue](https://github.com/Exlll/ConfigLib/issues/12) if you have any trouble with that.
**Maven**
2 years ago
```xml
<repository>
<id>de.exlll</id>
<url>https://maven.pkg.github.com/Exlll/ConfigLib</url>
</repository>
<dependency>
<groupId>de.exlll</groupId>
<artifactId>configlib-yaml</artifactId>
<version>4.2.0</version>
</dependency>
```
2 years ago
**Gradle**
2 years ago
```groovy
repositories { maven { url 'https://maven.pkg.github.com/Exlll/ConfigLib' } }
dependencies { implementation 'de.exlll:configlib-yaml:4.2.0' }
```
```kotlin
repositories { maven { url = uri("https://maven.pkg.github.com/Exlll/ConfigLib") } }
dependencies { implementation("de.exlll:configlib-yaml:4.2.0") }
2 years ago
```
</details>
2 years ago
## Future work
This section contains ideas for upcoming features. If you want any of these to happen any time soon,
please [open an issue](https://github.com/Exlll/ConfigLib/issues/new) where we can discuss the
details.
- JSON, TOML, XML support
- Post-load/Pre-save hooks
- More features and control over updating/versioning
- More control over the ordering of fields, especially in parent/child class scenarios
- Recursive definitions
- Shadowing of fields