Improve documentation

dev
Exlll 6 years ago
parent 71a6052e29
commit 7f600834ff

@ -5,9 +5,11 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
/** /**
* Indicates the of elements a {@code Collection} or {@code Map} contains. * Indicates the type of elements a {@code Collection} or {@code Map} contains.
* <p> * <p>
* This annotation must be used if element type is not simple. * This annotation must only be used if a {@code Collection} or {@code Map} contains
* elements whose type is not simple. Note that {@code Map} keys can only be of some
* simple type.
*/ */
@Target(java.lang.annotation.ElementType.FIELD) @Target(java.lang.annotation.ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)

@ -6,7 +6,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
/** /**
* Indicates that the annotated field should not be converted but used as is. * Indicates that the annotated field should not be converted but instead used as is.
* <p> * <p>
* This may be useful if the configuration knows how to (de-)serialize * This may be useful if the configuration knows how to (de-)serialize
* instances of that type. For example, a {@code BukkitYamlConfiguration} * instances of that type. For example, a {@code BukkitYamlConfiguration}

@ -9,11 +9,13 @@ stored to files or other storage systems.
Currently this library only supports storing configurations as YAML. However, users may provide their own Currently this library only supports storing configurations as YAML. However, users may provide their own
storage systems. storage systems.
For a step-by-step tutorial see: [Tutorial](https://github.com/Exlll/ConfigLib/wiki/Tutorial)
## Features ## Features
* automatic creation, saving, loading and updating of configurations * automatic creation, saving, loading and updating of configurations
* (_YAML_) automatic creation of files and directories * (_YAML_) automatic creation of files and directories
* support for all primitive types, their wrapper types and `String`s * support for all primitive types, their wrapper types and `String`s
* support for `List`s, `Set`s and `Map`s * support for (nested) `List`s, `Set`s and `Map`s
* support for `Enum`s and POJOs * support for `Enum`s and POJOs
* option to add explanatory comments by annotating classes and their fields * option to add explanatory comments by annotating classes and their fields
* option to provide custom configuration sources * option to provide custom configuration sources
@ -59,6 +61,9 @@ subclass of `A` and `A` is a subclass of `YamlConfiguration` and you save or loa
fields of class `B` will be saved or loaded, respectively. fields of class `B` will be saved or loaded, respectively.
## How-to (_YAML_) ## How-to (_YAML_)
For a step-by-step tutorial see: [Tutorial](https://github.com/Exlll/ConfigLib/wiki/Tutorial)
#### Creating configurations #### Creating configurations
To create a YAML configuration, create a new class and extend `YamlConfiguration`. If you write a Bukkit plugin, 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 you can alternatively extend `BukkitYamlConfiguration` which is a subclass of `YamlConfiguration` and can
@ -181,7 +186,7 @@ class MyConfiguration extends YamlConfiguration {
``` ```
Note: Even though sets are supported, their YAML-representation is 'pretty ugly', so it's better to use lists instead. Note: Even though sets are supported, their YAML-representation is 'pretty ugly', so it's better to use lists instead.
If you need the set behavior, you can internally use lists and convert them to sets using the `preSave/postLoad`-hooks. 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. 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. Only simple types can be used as map keys.
@ -351,6 +356,10 @@ class MyConfiguration extends YamlConfiguration {
Note: Only a single converter instance is created which is cached. Note: Only a single converter instance is created which is cached.
## Example ## Example
For a step-by-step tutorial of a more complex example see:
[Tutorial](https://github.com/Exlll/ConfigLib/wiki/Tutorial)
```java ```java
import de.exlll.configlib.annotation.Comment; import de.exlll.configlib.annotation.Comment;
import de.exlll.configlib.annotation.ConfigurationElement; import de.exlll.configlib.annotation.ConfigurationElement;

@ -1,7 +0,0 @@
This section covers some the advanced features of this library.
## Converters
## Configuration sources

@ -0,0 +1,181 @@
This tutorial is intended to show how to add a custom configuration source. So, let's say you
want to implement your own `Configuration` type, an `InMemoryConfiguration`, for example.
#### 1. Extend `Configuration`
The first thing you have to do is to let your `InMemoryConfiguration` class
extend `Configuration<InMemoryConfiguration>`.
```java
import de.exlll.configlib.Configuration;
import de.exlll.configlib.ConfigurationSource;
public class InMemoryConfiguration extends Configuration<InMemoryConfiguration> {}
```
#### 2. Create a `ConfigurationSource`
The second step is to create a class that implements
`ConfigurationSource<InMemoryConfiguration>`.
```java
final class InMemoryConfigurationSource
implements ConfigurationSource<InMemoryConfiguration> {}
```
#### 3. Implement `save-/loadConfiguration`
Next implement its methods.
```java
final class InMemoryConfigurationSource
implements ConfigurationSource<InMemoryConfiguration> {
private Map<String, Object> configAsMap = new HashMap<>();
/* The 'config' parameter is the Configuration instance that
* requested the save/load */
@Override
public void saveConfiguration(
InMemoryConfiguration config, Map<String, Object> map
) {
this.configAsMap = map;
}
@Override
public Map<String, Object> loadConfiguration(
InMemoryConfiguration config
) {
return configAsMap;
}
}
```
#### 4. Extend `Properties` and `Builder`
Within your `InMemoryConfiguration` class, create an `InMemoryProperties` class
that extends `Configuration.Properties`. Within the `InMemoryProperties` class,
create a `Builder` class that extends `Properties.Builder`.
```java
public class InMemoryConfiguration extends Configuration<InMemoryConfiguration> {
public static final class InMemoryProperties extends Properties {
protected InMemoryProperties(Builder<?> builder) {
super(builder);
}
public static Builder<?> builder() {
return new Builder() {
@Override
protected Builder getThis() {
return this;
}
};
}
public static abstract class Builder<B extends Builder<B>>
extends Properties.Builder<B> {
public InMemoryProperties build() {
return new InMemoryProperties(this);
}
}
}
}
```
#### 5. Add properties
Add properties to your `InMemoryProperties` class that can configure your
`InMemoryConfiguration` in some meaningful way. For example:
```java
public static final class InMemoryProperties extends Properties {
private final int minMemory;
private final int maxMemory;
protected InMemoryProperties(Builder<?> builder) {
super(builder);
this.minMemory = builder.minMemory;
this.maxMemory = builder.maxMemory;
}
public int getMinMemory() { return minMemory; }
public int getMaxMemory() { return maxMemory; }
public static Builder<?> builder() {
return new Builder() {
@Override protected Builder getThis() { return this; }
};
}
public static abstract class Builder<B extends Builder<B>>
extends Properties.Builder<B> {
private int minMemory = 0;
private int maxMemory = 1024;
public B setMinMemory(int minMemory) {
this.minMemory = minMemory;
return getThis();
}
public B setMaxMemory(int maxMemory) {
this.maxMemory = maxMemory;
return getThis();
}
public InMemoryProperties build() {
return new InMemoryProperties(this);
}
}
}
```
#### 6. Implement `getSource/getThis`
The last step is to implement the `getSource` and `getThis` methods and override the
constructor of your `InMemoryConfiguration`.
```java
public class InMemoryConfiguration extends Configuration<InMemoryConfiguration> {
private final InMemoryConfigurationSource source =
new InMemoryConfigurationSource();
protected InMemoryConfiguration(InMemoryProperties properties) {
super(properties);
}
@Override
protected ConfigurationSource<InMemoryConfiguration> getSource() {
return source;
}
@Override
protected InMemoryConfiguration getThis() {
return this;
}
}
```
#### 7. Use your new `InMemoryConfiguration`
```java
final class InMemoryDatabaseConfig extends InMemoryConfiguration {
private final String host = "localhost";
// ...
public InMemoryDatabaseConfig(InMemoryProperties properties) {
super(properties);
}
// ...
public static void main(String[] args) {
InMemoryProperties properties = InMemoryProperties.builder()
.setMinMemory(123)
.setMaxMemory(456)
.build();
InMemoryDatabaseConfig config = new InMemoryDatabaseConfig(properties);
config.save();
}
}
```

@ -0,0 +1,521 @@
## Tutorial
This tutorial is intended to show most of the features of this library, so let's say that
we want to create the following configuration file for some kind of game:
```yaml
# Valid color codes: &4, &c, &e
win_message: '&4YOU WON'
blocked_users:
- root
- john
team_members:
- - Pete
- Mary
- Alice
- Leo
- - Eli
- Eve
- Paul
- Patrick
moderator:
credentials:
username: alex
password: '123'
email: a@b.c
users_by_name:
Patrick:
credentials:
username: Patrick
password: '579'
email: patrick@example.com
Eli:
credentials:
username: Eli
password: '246'
email: eli@example.com
Pete:
credentials:
username: Pete
password: '123'
email: pete@example.com
Eve:
credentials:
username: Eve
password: '357'
email: eve@example.com
Alice:
credentials:
username: Alice
password: '789'
email: alice@example.com
Leo:
credentials:
username: Leo
password: '135'
email: leo@example.com
Paul:
credentials:
username: Paul
password: '468'
email: paul@example.com
Mary:
credentials:
username: Mary
password: '456'
email: mary@example.com
first_prize:
==: org.bukkit.inventory.ItemStack
type: DIAMOND_AXE
meta:
==: ItemMeta
meta-type: UNSPECIFIC
enchants:
DIG_SPEED: 5
DURABILITY: 3
MENDING: 1
consolation_prizes:
- ==: org.bukkit.inventory.ItemStack
type: STICK
amount: 2
- ==: org.bukkit.inventory.ItemStack
type: ROTTEN_FLESH
amount: 3
- ==: org.bukkit.inventory.ItemStack
type: CARROT
amount: 4
prohibited_items:
- BEDROCK
- AIR
- LAVA
# Configure the arena:
arena_height: 40
arena_center: world;0;128;0
# Remember to play fair!
```
#### 1. Extend `BukkitYamlConfiguration`
The first thing we have to do is to extend `BukkitYamlConfiguration`. We use
`BukkitYamlConfiguration` instead of `YamlConfiguration` because it can properly
(de-)serialize Bukkit classes like `ItemStack`s.
```java
public final class GameConfig extends BukkitYamlConfiguration {}
```
#### 2. Create constructor
Next we have to create a constructor that matches super. If we don't pass a
`BukkitYamlProperties` object to the super call, the `BukkitYamlProperties.DEFAULT`
instance is used. But because we want to format the field names of our configuration
(so that we can write 'winMessage' in Java which becomes 'win_message' in YAML), we
have to pass a `BukkitYamlProperties` object that uses a different
`FieldNameFormatter` (see [11.](https://github.com/Exlll/ConfigLib/wiki/Tutorial#11-use-gameconfig)).
```java
public final class GameConfig extends BukkitYamlConfiguration {
public GameConfig(Path path, BukkitYamlProperties properties) {
super(path, properties);
}
// uses BukkitYamlProperties.DEFAULT instance
// public GameConfig(Path path) {
// super(path);
// }
}
```
#### 3. Add `winMessage`
Because `winMessage` is a string with a comment, we add a field named `winMessage`
of type `String` to our configuration class and annotate it with the `@Comment`
annotation.
```java
public final class GameConfig extends BukkitYamlConfiguration {
@Comment("Valid color codes: &4, &c, &e")
private String winMessage = "&4YOU WON";
// ...
}
```
#### 4. Add `blockedUsers`
Because `blockedUsers` is a list of strings, we add a field named `blockedUsers`
of type `List<String>` to our configuration class.
```java
public final class GameConfig extends BukkitYamlConfiguration {
// ...
private List<String> blockedUsers = Arrays.asList("root", "john");
// ...
}
```
Remember that `null` values are not allowed. All non-primitive fields must be
assigned some non-`null` default value.
#### 5. Add `teamMembers`
The `teamMembers` field is of type `List<List<String>>`.
```java
public final class GameConfig extends BukkitYamlConfiguration {
// ...
private List<List<String>> teamMembers = Arrays.asList(
Arrays.asList("Pete", "Mary", "Alice", "Leo"),
Arrays.asList("Eli", "Eve", "Paul", "Patrick")
);
// ...
}
```
#### 6. Add `moderator`
Because our `moderator` represents a user that has credentials and an email address,
we create a `User` and a `Credentials` class and annotate them as
`ConfigurationElement`s:
```java
@ConfigurationElement
final class Credentials {
private String username;
private String password;
// ConfigurationElements must have a no-args constructor (can be private)
private Credentials() {}
public Credentials(String username, String password) {
this.username = username;
this.password = password;
}
// getter etc.
}
@ConfigurationElement
final class User {
private Credentials credentials;
private String email;
private User() {}
public User(String username, String password, String email) {
this.credentials = new Credentials(username, password);
this.email = email;
}
// getter etc.
}
```
Now we can use the `User` class for our `moderator` field:
```java
public final class GameConfig extends BukkitYamlConfiguration {
// ...
private User moderator = new User("alex", "a@b.c", "123");
// ...
}
```
`ConfigurationElement`s must have a no-args constructor which is to create
instances of a given element. If the no-args constructor is a valid constructor
for your program, it must initialize the fields to some non-`null` value.
#### 7. Add `usersByName`
The `usersByName` field is a map that maps user names to `User` instances.
That means we have to use the `@ElementType` annotation.
```java
public final class GameConfig extends BukkitYamlConfiguration {
// ...
@ElementType(User.class)
private Map<String, User> usersByName = initUsersByName();
// ...
private Map<String, User> initUsersByName() {
Map<String, User> usersByName = new HashMap<>();
usersByName.put("Pete", new User("Pete", "pete@example.com", "123"));
usersByName.put("Mary", new User("Mary", "mary@example.com", "456"));
// ...
return usersByName;
}
}
```
#### 8. Add `firstPrize` and `consolationPrizes`
The types of `firstPrize` and `consolationPrizes` are `ItemStack` and `List<ItemStack>`,
respectively. Because a `BukkitYamlConfiguration` knows how to serialize `ItemStack`
instances, we need to tell the library not to try to convert them. This can be done
by using the `@NoConvert` annotation.
```java
public final class GameConfig extends BukkitYamlConfiguration {
// ...
@NoConvert
private ItemStack firstPrize = initFirstPrize();
@NoConvert
private List<ItemStack> consolationPrizes = Arrays.asList(
new ItemStack(Material.STICK, 2),
new ItemStack(Material.ROTTEN_FLESH, 3),
new ItemStack(Material.CARROT, 4)
);
// ...
private ItemStack initFirstPrize() {
ItemStack stack = new ItemStack(Material.DIAMOND_AXE);
stack.addEnchantment(Enchantment.DURABILITY, 3);
stack.addEnchantment(Enchantment.DIG_SPEED, 5);
stack.addEnchantment(Enchantment.MENDING, 1);
return stack;
}
// ...
}
```
#### 9. Add `prohibitedItems`
The `prohibitedItems` field is a list of `Material`s. Since this library supports
converting enums, we just have to use the `@ElementType` annotation.
```java
public final class GameConfig extends BukkitYamlConfiguration {
// ...
@ElementType(Material.class)
private List<Material> prohibitedItems = Arrays.asList(
Material.BEDROCK, Material.AIR, Material.LAVA
);
// ...
}
```
#### 10. Add `arenaHeight` and `arenaCenter`
The `arenaHeight` can simply be represented by an `int` field. The `arenaCenter`
is of type `Location`. We could again use the `@NoConvert` annotation but this
would result in a different representation. Instead, we are going to implement our
`Converter`.
First we have to create a class that implements `Converter<Location, String>`:
```java
final class LocationStringConverter implements Converter<Location, String> {}
```
Then we must implement the `convertTo` and `convertFrom` methods:
```java
final class LocationStringConverter implements Converter<Location, String> {
@Override
public String convertTo(Location location, ConversionInfo conversionInfo) {
String worldName = location.getWorld().getName();
int blockX = location.getBlockX();
int blockY = location.getBlockY();
int blockZ = location.getBlockZ();
return worldName + ";" + blockX + ";" + blockY + ";" + blockZ;
}
@Override
public Location convertFrom(String s, ConversionInfo conversionInfo) {
String[] split = s.split(";");
World world = Bukkit.getWorld(split[0]);
int x = Integer.parseInt(split[1]);
int y = Integer.parseInt(split[2]);
int z = Integer.parseInt(split[3]);
return new Location(world, x, y, z);
}
}
```
Finally we have to tell our configuration to use this converter for the
`arenaCenter` field. This is done using the `@Convert` annotation:
```java
public final class GameConfig extends BukkitYamlConfiguration {
// ...
@Comment({"", "Configure the arena:"})
private int arenaHeight = 40;
@Convert(LocationStringConverter.class)
private Location arenaCenter = new Location(
Bukkit.getWorld("world"), 0, 128, 0
);
}
```
#### 11. Use `GameConfig`
Before we can use our new configuration, we have to instantiate it by passing
a `Path` and a `BukkitYamlProperties` object to its constructor. In this case
the `BukkitYamlProperties` is used to change the formatting and to append
additional text to the created configuration file.
```java
public final class GamePlugin extends JavaPlugin {
@Override
public void onEnable() {
Path configPath = new File(getDataFolder(), "config.yml").toPath();
BukkitYamlProperties properties = BukkitYamlProperties.builder()
.setFormatter(FieldNameFormatters.LOWER_UNDERSCORE)
.setAppendedComments(Arrays.asList(
"", "Remember to play fair!"
))
.build();
GameConfig config = new GameConfig(configPath, properties);
config.loadAndSave();
}
}
```
### Full example
```java
import de.exlll.configlib.Converter;
import de.exlll.configlib.annotation.*;
import de.exlll.configlib.configs.yaml.BukkitYamlConfiguration;
import de.exlll.configlib.configs.yaml.BukkitYamlConfiguration.BukkitYamlProperties;
import de.exlll.configlib.format.FieldNameFormatters;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public final class GamePlugin extends JavaPlugin {
@Override
public void onEnable() {
Path configPath = new File(getDataFolder(), "config.yml").toPath();
BukkitYamlProperties properties = BukkitYamlProperties.builder()
.setFormatter(FieldNameFormatters.LOWER_UNDERSCORE)
.setAppendedComments(Arrays.asList(
"", "Remember to play fair!"
))
.build();
GameConfig config = new GameConfig(configPath, properties);
config.loadAndSave();
}
}
final class GameConfig extends BukkitYamlConfiguration {
@Comment("Valid color codes: &4, &c, &e")
private String winMessage = "&4YOU WON";
private User moderator = new User("alex", "a@b.c", "123");
private List<String> blockedUsers = Arrays.asList("root", "john");
private List<List<String>> teamMembers = Arrays.asList(
Arrays.asList("Pete", "Mary", "Alice", "Leo"),
Arrays.asList("Eli", "Eve", "Paul", "Patrick")
);
@ElementType(User.class)
private Map<String, User> usersByName = initUsersByName();
@NoConvert
private ItemStack firstPrize = initFirstPrize();
@NoConvert
private List<ItemStack> consolationPrizes = Arrays.asList(
new ItemStack(Material.STICK, 2),
new ItemStack(Material.ROTTEN_FLESH, 3),
new ItemStack(Material.CARROT, 4)
);
@ElementType(Material.class)
private List<Material> prohibitedItems = Arrays.asList(
Material.BEDROCK, Material.AIR, Material.LAVA
);
@Comment({"", "Configure the arena:"})
private int arenaHeight = 40;
@Convert(LocationStringConverter.class)
private Location arenaCenter = new Location(
Bukkit.getWorld("world"), 0, 128, 0
);
public GameConfig(Path path, BukkitYamlProperties properties) {
super(path, properties);
}
private HashMap<String, User> initUsersByName() {
HashMap<String, User> usersByName = new HashMap<>();
usersByName.put("Pete", new User("Pete", "pete@example.com", "123"));
usersByName.put("Mary", new User("Mary", "mary@example.com", "456"));
usersByName.put("Alice", new User("Alice", "alice@example.com", "789"));
usersByName.put("Leo", new User("Leo", "leo@example.com", "135"));
usersByName.put("Eli", new User("Eli", "eli@example.com", "246"));
usersByName.put("Eve", new User("Eve", "eve@example.com", "357"));
usersByName.put("Paul", new User("Paul", "paul@example.com", "468"));
usersByName.put("Patrick", new User("Patrick", "patrick@example.com", "579"));
return usersByName;
}
private ItemStack initFirstPrize() {
ItemStack stack = new ItemStack(Material.DIAMOND_AXE);
stack.addEnchantment(Enchantment.DURABILITY, 3);
stack.addEnchantment(Enchantment.DIG_SPEED, 5);
stack.addEnchantment(Enchantment.MENDING, 1);
return stack;
}
private static final class LocationStringConverter
implements Converter<Location, String> {
@Override
public String convertTo(Location location, ConversionInfo conversionInfo) {
String worldName = location.getWorld().getName();
int blockX = location.getBlockX();
int blockY = location.getBlockY();
int blockZ = location.getBlockZ();
return worldName + ";" + blockX + ";" + blockY + ";" + blockZ;
}
@Override
public Location convertFrom(String s, ConversionInfo conversionInfo) {
String[] split = s.split(";");
World world = Bukkit.getWorld(split[0]);
int x = Integer.parseInt(split[1]);
int y = Integer.parseInt(split[2]);
int z = Integer.parseInt(split[3]);
return new Location(world, x, y, z);
}
}
}
@ConfigurationElement
final class User {
private Credentials credentials;
private String email;
private User() {}
public User(String username, String password, String email) {
this.credentials = new Credentials(username, password);
this.email = email;
}
}
@ConfigurationElement
final class Credentials {
private String username;
private String password;
// ConfigurationElements must have a no-args constructor (can be private)
private Credentials() {}
public Credentials(String username, String password) {
this.username = username;
this.password = password;
}
}
```
Loading…
Cancel
Save