18 KiB
ConfigLib v2
A Bukkit and BungeeCord library for storing and loading configurations
This library facilitates creating, saving and loading configurations by reflectively converting configuration
instances to serializable Map
s which can be transformed to different representations (e.g. YAML) before being
stored to files or other storage systems.
Currently this library only supports storing configurations as YAML. However, users may provide their own storage systems.
For a step-by-step tutorial see: Tutorial
Features
- automatic creation, saving, loading and updating of configurations
- (YAML) automatic creation of files and directories
- support for all primitive types, their wrapper types and
String
s - support for (nested)
List
s,Set
s andMap
s - support for
Enum
s and POJOs - option to add explanatory comments by annotating classes and their fields
- option to provide custom configuration sources
- option to exclude fields from being converted
- option to provide custom conversion mechanisms
- option to format field names before conversion
- option to execute action before/after loading/saving the configuration
- (YAML) option to change the style of the configuration file
- (YAML) option to prepend/append text (e.g. color codes)
General information
Supported types
By default, the following types are converted automatically:
- simple types, i.e. primitive types, their wrapper types and
String
s Enum
s- any type that is annotated as a
ConfigurationElement
- (nested)
List
s,Set
s andMap
s of all the above (e.g.List<SomeType>
,Map<String, List<SomeEnum>>
)- only simple types can be
Map
keys
- only simple types can be
For fields whose types are not any of the above, you have two other options:
- Add a custom
Converter
that converts the field's value to any of the above types and back from it. - If the underlying storage system can handle the type, exclude the field from being converted.
Null values
This library does not support null
values. All non-primitive fields (e.g. Integer
, String
, List
, Enum
s)
must be assigned non-null default values.
Adding or removing configuration options
This library supports adding or removing configuration options by simply adding new fields to or removing old fields
from the configuration class. The next time the save
or loadAndSave
method is called, the changes will be saved.
Changing the type of configuration options
Changing the type of configuration options is not supported. Don't do that. This may lead to
ClassCastException
s when loading or accessing the field. This is especially important for generic fields.
For example, you should never change a List<String>
to a List<Integer>
.
If you need the type of an option to change, add a new field with a different name and the desired type and then remove the old one.
Subclassing configurations
Currently, subclassing configurations is not supported. If you have an instance of class B
where B
is a
subclass of A
and A
is a subclass of YamlConfiguration
and you save or load that instance, then only the
fields of class B
will be saved or loaded, respectively.
How-to (YAML)
For a step-by-step tutorial see: Tutorial
Creating configurations
To create a YAML configuration, create a new class and extend YamlConfiguration
. If you write a Bukkit plugin,
you can alternatively extend BukkitYamlConfiguration
which is a subclass of YamlConfiguration
and can
properly convert Bukkit classes like Inventory
and ItemStack
to YAML.
Instantiating configurations
- To instantiate a
YamlConfiguration
, you need to pass aPath
and optionally aYamlConfiguration.YamlProperties
object to its constructor. - To instantiate a
BukkitYamlConfiguration
, you need to pass aPath
and optionally aBukkitYamlConfiguration.BukkitYamlProperties
object to its constructor.
If you don't pass a (Bukkit-)YamlProperties
object, the (Bukkit-)YamlProperties.DEFAULT
instance will be used.
Instantiating (Bukkit-)YamlProperties
To instantiate a new (Bukkit-)YamlProperties
object, call (Bukkit-)YamlProperties.builder()
,
configure the builder and then call its build()
method.
Note: The BukkitYamlProperties
is a subclass of YamlProperties
but doesn't add any new methods to it.
Its sole purpose is to provide more appropriate defaults to the underlying YAML parser.
Saving and loading configurations
Instances of your configuration class have a load
, save
and loadAndSave
method:
load
first tries to load the configuration file and then updates the values of all fields of the configuration instance with the values it read from the file.- If the file contains an entry that doesn't have a corresponding field, the entry is ignored.
- If the instance contains a field for which no entry was found, the default value you assigned to that field is kept.
save
first converts the configuration instance with its current values to YAML and then tries to dump that YAML to a configuration file.- The configuration file is completely overwritten. This means any entries it contains are lost afterwards.
loadAndSave
is a convenience method that first callsload
and thensave
.- If the file doesn't exist, the configuration instance keeps its default values. Otherwise, the values are updated with the values read from the file.
- Subsequently the instance is saved so that the values of any newly added fields are also added to configuration file.
Adding and removing fields
Adding and removing fields is supported. However, changing the type of field is not.
For example, you can change the following YamlConfiguration
class MyConfiguration extends YamlConfiguration {
private String s = "1";
private double d = 4.2;
// ...
}
to this:
class MyConfiguration extends YamlConfiguration {
private String s = "2";
private int i = 1;
// ...
}
But you are not allowed to change the type of the variable d
to int
(or any other type).
Simple, enum and custom types
The following types are simple types (remember that null
values are not allowed):
class MyConfiguration extends YamlConfiguration {
private boolean primBool;
private Boolean refBool = false;
private byte primByte;
private Byte refByte = 0;
private char primChar;
private Character refChar = '\0';
private short primShort;
private Short refShort = 0;
private int primInt;
private Integer refInt = 0;
private long primLong;
private Long refLong = 0L;
private float primFloat;
private Float refFloat = 0F;
private double primDouble;
private Double refDouble = 0.0;
private String string = "";
// ...
}
Enums are supported:
class MyConfiguration extends YamlConfiguration {
private Material material = Material.AIR;
//...
}
Custom classes are supported if they are annotated as ConfigurationElement
s and if they have a no-args constructor.
Custom classes can have fields whose values are also instances of custom classes.
@ConfigurationElement
class MyCustomClass1 {/* fields etc.*/}
@ConfigurationElement
class MyCustomClass2 {
private MyCustomClass1 cls1 = new MyCustomClass1();
// ...
}
class MyConfiguration extends YamlConfiguration {
private MyCustomClass2 cls2 = new MyCustomClass2();
// ...
}
List
s, Set
s, Map
s
Lists, sets and maps of simple types can be used as is and don't need any special treatment.
class MyConfiguration extends YamlConfiguration {
private Set<Integer> ints = new HashSet<>();
private List<String> strings = new ArrayList<>();
private Map<Boolean, Double> doubleByBool = new HashMap<>();
// ...
}
Note: Even though sets are supported, their YAML-representation is 'pretty ugly', so it's better to use lists instead.
If you need set behavior, you can internally use lists and convert them to sets using the preSave/postLoad
-hooks.
Lists, sets and maps that contain other types (e.g. custom types or enums) must use the @ElementType
annotation.
Only simple types can be used as map keys.
@ConfigurationElement
class MyCustomClass {/* fields etc.*/}
class MyConfiguration extends YamlConfiguration {
@ElementType(Material.class)
private List<Material> materials = new ArrayList<>();
@ElementType(MyCustomClass.class)
private Set<MyCustomClass> customClasses = new HashSet<>();
@ElementType(MyCustomClass.class)
private Map<String, MyCustomClass> customClassesMap = new HashMap<>();
// ...
}
Lists, sets and maps can be nested. If nested collections contain custom types, you must specify the
nesting level using the @ElementType
annotation. Examples:
List<T>
requires a nesting level of 0, which is the default value, so you don't have to set itList<List<T>>
requires a nesting level of 1List<List<List<T>>>
requires a nesting level of 2List<Map<String, T>>
requires a nesting level of 1List<Map<String, Map<String, T>>>
requires a nesting level of 2
@ConfigurationElement
class MyCustomClass {/* fields etc.*/}
class MyConfiguration extends YamlConfiguration {
private List<List<Integer>> listsList = new ArrayList<>();
private Set<Set<String>> setsSet = new HashSet<>();
private Map<Integer, Map<String, Integer>> mapsMap = new HashMap<>();
@ElementType(value = MyCustomClass.class, nestingLevel = 1)
private List<List<MyCustomClass>> customClassListsList = new ArrayList<>();
@ElementType(value = MyCustomClass.class, nestingLevel = 1)
private Set<Set<MyCustomClass>> customClassSetsSet = new HashSet<>();
@ElementType(value = MyCustomClass.class, nestingLevel = 1)
private Map<Integer, Map<String, MyCustomClass>> customClassMapsMap = new HashMap<>();
// ...
}
Adding comments
You can add comments to a configuration class or a its field by using the @Comment
annotation.
Class comments are saved at the beginning of a configuration file.
@Comment({"A", "", "B"})
class MyConfiguration extends YamlConfiguration {
@Comment("the x")
private int x;
@Comment({"", "the y"})
private int y;
// ...
}
Empty strings are represented as newlines (i.e. lines that don't start with '# ').
Executing pre-save and post-load actions
To execute pre-save and post-load actions, override preSave()
and postLoad()
, respectively.
class MyConfiguration extends YamlConfiguration {
@Override
protected void preSave(){ /* do something ... */}
@Override
protected void postLoad(){ /* do something ... */}
// ...
}
Excluding fields from being converted
To exclude fields from being converted, annotate them with the @NoConvert
annotation. This may be useful if the
configuration knows how to (de-)serialize instances of that type. For example, a BukkitYamlConfiguration
knows how
to serialize ItemStack
instances.
class MyConfiguration extends BukkitYamlConfiguration {
@NoConvert
private ItemStack itemStack = new ItemStack(Material.STONE, 1);
// ...
}
Changing configuration properties
To change the properties of a configuration, use the properties builder object.
Formatting field names
To format field names before conversion, configure the properties builder to use a custom FieldNameFormatter
.
You can either define your own FieldNameFormatter
or use one from the FieldNameFormatters
enum.
YamlProperties properties = YamlProperties.builder()
.setFormatter(FieldNameFormatters.LOWER_UNDERSCORE)
// ...
.build();
Alternatively, you can annotate your Configuration
class with the @Format
annotation. The FieldNameFormatter
returned by this annotation takes precedence over the FieldNameFormatter
returned by a Properties
object.
@Format(FieldNameFormatters.UPPER_UNDERSCORE)
class MyConfiguration extends YamlConfiguration {
// ...
}
Note: You should neither remove nor replace a formatter with one that has a different formatting style because this could break existing configurations.
(YAML) Prepending/appending text
To prepend or append comments to a configuration file, use the setPrependedComments
and setAppendedComments
methods,
respectively.
YamlProperties properties = YamlProperties.builder()
.setPrependedComments(Arrays.asList("A", "B"))
.setAppendedComments(Arrays.asList("C", "D"))
// ...
.build();
(YAML) Changing the style of the configuration file
To change the configuration style, use the setConstructor
, setRepresenter
, setOptions
and setResolver
methods.
These methods change the behavior of the underlying YAML-parser.
See snakeyaml-Documentation.
YamlProperties properties = YamlProperties.builder()
.setConstructor(...)
.setRepresenter(/* */)
.setOptions(/* */)
.setResolver(/* */)
// ...
.build();
Note: Changing the configuration style may break adding comments using the @Comment
annotation.
Adding field filters
If your configuration has a lot of fields and you want to exclude some of these fields without
making them final, static or transient, you can configure your properties object to use additional
FieldFilter
s. A FieldFilter
filters the fields of a configuration class by a specified criterion.
For example, if you only want to include fields whose names don't start with ignore, you would add the following filter:
YamlProperties properties = YamlProperties.builder()
.addFilter(field -> !field.getName().startsWith("ignore"))
// ...
.build();
Note: A filter is not evaluated for a field if the field has already been filtered or by some
other FieldFilter
.
Adding custom converters
Any field can be converted using a custom converter. This can be useful if you don't like the default
conversion mechanism or if you have classes that cannot be annotated as ConfigurationElement
s
(e.g. because they are not under your control).
To create a new converter, you have to implement the Converter<F, T>
interface where F
represents
the type of the field and T
represents the type of the value to which the field value is converted.
import java.awt.Point;
class PointStringConverter implements Converter<Point, String> {
@Override
public String convertTo(Point element, ConversionInfo info) {
return element.x + ":" + element.y;
}
@Override
public Point convertFrom(String element, ConversionInfo info) {
String[] coordinates = element.split(":");
int x = Integer.parseInt(coordinates[0]);
int y = Integer.parseInt(coordinates[1]);
return new Point(x, y);
}
}
To use your custom converter, pass its class to the @Convert
annotation.
class MyConfiguration extends YamlConfiguration {
@Convert(PointStringConverter.class)
private Point point = new Point(2, 3);
//...
}
Note: Only a single converter instance is created which is cached.
Example
For a step-by-step tutorial of a more complex example see: Tutorial
import de.exlll.configlib.annotation.Comment;
import de.exlll.configlib.annotation.ConfigurationElement;
import de.exlll.configlib.configs.yaml.BukkitYamlConfiguration;
import de.exlll.configlib.configs.yaml.BukkitYamlConfiguration.BukkitYamlProperties;
import de.exlll.configlib.format.FieldNameFormatters;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
@ConfigurationElement
class Credentials {
private String username;
private String password;
// ConfigurationElements must have a no-args constructor
Credentials() {
this("", ""); // default values must be non-null
}
Credentials(String username, String password) {
this.username = username;
this.password = password;
}
String getUsername() { return username; }
}
@Comment("MAIN-DB CONFIG")
class DatabaseConfig extends BukkitYamlConfiguration {
private String host = "localhost";
@Comment("must be greater than 1024")
private int port = 3306;
private Credentials adminAccount = new Credentials("admin", "123");
private List<String> blockedUsers = Arrays.asList("root", "john");
/* You can use the other constructor instead which uses the
* BukkitYamlProperties.DEFAULT instance. */
DatabaseConfig(Path path, BukkitYamlProperties properties) {
super(path, properties);
}
Credentials getAdminAccount() { return adminAccount; }
}
public final class DatabasePlugin extends JavaPlugin {
@Override
public void onEnable() {
/* Creating a properties object is not necessary if the other
* DatabaseConfig constructor is used. */
BukkitYamlProperties props = BukkitYamlProperties.builder()
.setPrependedComments(Arrays.asList("Author: Pete", "Version: 1.0"))
.setFormatter(FieldNameFormatters.LOWER_UNDERSCORE)
.build();
Path configPath = new File(getDataFolder(), "config.yml").toPath();
DatabaseConfig config = new DatabaseConfig(configPath, props);
config.loadAndSave();
System.out.println(config.getAdminAccount().getUsername());
}
}
Import
Maven
<repository>
<id>de.exlll</id>
<url>https://maven.pkg.github.com/Exlll/ConfigLib</url>
</repository>
<!-- for Bukkit plugins -->
<dependency>
<groupId>de.exlll</groupId>
<artifactId>configlib-bukkit</artifactId>
<version>2.2.0</version>
</dependency>
<!-- for Bungee plugins -->
<dependency>
<groupId>de.exlll</groupId>
<artifactId>configlib-bungee</artifactId>
<version>2.2.0</version>
</dependency>
Gradle
repositories { maven { url 'https://maven.pkg.github.com/Exlll/ConfigLib' } }
dependencies {
// for Bukkit plugins
implementation group: 'de.exlll', name: 'configlib-bukkit', version: '2.2.0'
// for Bungee plugins
implementation group: 'de.exlll', name: 'configlib-bungee', version: '2.2.0'
}
repositories { maven { url = uri("https://maven.pkg.github.com/Exlll/ConfigLib") } }
dependencies {
// for Bukkit plugins
implementation("de.exlll:configlib-bukkit:2.2.0")
// for Bungee plugins
implementation("de.exlll:configlib-bungee:2.2.0")
}