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/docs/tutorial.md

16 KiB

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:

# 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 ItemStacks.

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.).

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.

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.

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>>.

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 ConfigurationElements:

@ConfigurationElement
final class Credentials {
    private String username;
    private String password;

    // ConfigurationElements must have a no-args constructor (can be private)
    private Credentials() { this("", ""); }

    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() { this("", "", ""); }

    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:

public final class GameConfig extends BukkitYamlConfiguration {
    // ...
    private User moderator = new User("alex", "123", "a@b.c");
    // ...
}

ConfigurationElements must have a no-args constructor which is used to create instances of a given element.

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.

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", "123", "pete@example.com"));
        usersByName.put("Mary", new User("Mary", "456", "mary@example.com"));
        // ...
        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.

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 Materials. Since this library supports converting enums, we just have to use the @ElementType annotation.

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 own Converter.

First we have to create a class that implements Converter<Location, String>:

final class LocationStringConverter implements Converter<Location, String> {}

Then we must implement the convertTo and convertFrom methods:

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:

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 of field names, to append text to the configuration file and to add an additional field filter.

public final class GamePlugin extends JavaPlugin {

    @Override
    public void onEnable() {
        Path configPath = new File(getDataFolder(), "config.yml").toPath();

        BukkitYamlProperties properties = BukkitYamlProperties.builder()
                .addFilter(field -> !field.getName().startsWith("ignore"))
                .setFormatter(FieldNameFormatters.LOWER_UNDERSCORE)
                .setAppendedComments(Arrays.asList(
                        "", "Remember to play fair!"
                ))
                .build();
        GameConfig config = new GameConfig(configPath, properties);
        config.loadAndSave();
    }
}

Full example

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()
                .addFilter(field -> !field.getName().startsWith("ignore"))
                .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", "123", "a@b.c");
    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
    );
    private String ignoreMe = "1";
    private String ignoreMeToo = "2";

    public GameConfig(Path path, BukkitYamlProperties properties) {
        super(path, properties);
    }

    private Map<String, User> initUsersByName() {
        Map<String, User> usersByName = new HashMap<>();
        usersByName.put("Pete", new User("Pete", "123", "pete@example.com"));
        usersByName.put("Mary", new User("Mary", "456", "mary@example.com"));
        usersByName.put("Alice", new User("Alice", "789", "alice@example.com"));
        usersByName.put("Leo", new User("Leo", "135", "leo@example.com"));
        usersByName.put("Eli", new User("Eli", "246", "eli@example.com"));
        usersByName.put("Eve", new User("Eve", "357", "eve@example.com"));
        usersByName.put("Paul", new User("Paul", "468", "paul@example.com"));
        usersByName.put("Patrick", new User("Patrick", "579", "patrick@example.com"));
        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() { this("", "", ""); }

    public User(String username, String password, String email) {
        this.credentials = new Credentials(username, password);
        this.email = email;
    }

    public Credentials getCredentials() {
        return credentials;
    }

    public String getEmail() {
        return email;
    }
}

@ConfigurationElement
final class Credentials {
    private String username;
    private String password;

    // ConfigurationElements must have a no-args constructor (can be private)
    private Credentials() { this("", ""); }

    public Credentials(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }
}