datastorage/Configuration API

Introduction

Let's be honest here...YAML configuration is a pain to work with. The defaults system requires additional files to be added, it is impossible to automatically cast types, and the support for headers is terrible. The Configuration API makes working with configuration a breeze.

Configuration node and value paths are delimited by '.'-characters. To obtain the value of setting in the node settings, you would use path settings.setting.

Usage

Configuration works using stored nodes in a tree of data, as this is how YAML operates.

Loading and saving configuration

We will mainly cover configuration reading from file and saving to file. The API is built around the principle to read and write files, so there is little support for anything else. If you wish to read or write to another storage system, you can use the loadFromStream and saveToStream methods found in BasicConfiguration.

To start a new configuration for a file in, for example, plugin enable, you can do the following:

FileConfiguration config = new FileConfiguration(this, "config.yml");
if (config.exists()) {
    config.load(); // Load the configuration from the file
} else {
    config.set("config1", 12);
    config.set("config2", 14);
    config.save(); // Saves the new data to file
}
System.out.println(config.get("config1", Integer.class));
System.out.println(config.get("config2", Integer.class));

Getting values and their defaults

There is a major difference in getting values between this API and the default Bukkit implementation. In the Configuration API, the defaults specified are automatically set in the configuration if the value was not found. This makes it tonnes easier to start a (new) configuration. In the example above it first checked if the file exists, but this can be done a lot easier:

FileConfiguration config = new FileConfiguration(this, "config.yml");
config.load();
System.out.println(config.get("config1", 12));
System.out.println(config.get("config2", 14));
config.save();

Loading is allowed if the file does not exist, it will simply not load anything in that case. When getting the two values, it will automatically assign '12' to 'config1' if such a node was not loaded. When saving, it will automatically update these values.

Various stored types are supported, including all enumeration types (like Material and GameMode). To perform the same as the above but with Lists or Arrays, you can do the following:

List<Integer> ints = config.getList("blockIds", Integer.class);

It will automatically create an empty list in this case if it wasn't set. If you wish to specify defaults as well: this is possible. Non-integer convertable values are automatically filtered from the list, so no worries of having a String in there.

Nodes

YAML is a tree-based storage system, so support for Configuration Nodes is there as well. You do not have to use node objects to create nodes, but they can make it easier to pass around configuration in your plugin to several components. You can use getNode(path) to get a node. If the node does not yet exist, it is made and returned. For that reason, this method never returns null.

ConfigurationNode entities = config.getNode("entities");
maxCount = entities.get("maxCount", 225);
message = entities.get("message", "You have reached the maximum amount of entities");
EntityLogic.initialize(entities);

There is no difference between ConfigurationNode functionality and Configuration functionality - File/Basic Configuration is a node as well. The only difference is that a ConfigurationNode does not store information such as file, indent and other settings.

Headers

The Configuration API adds support for configuration headers, to give useful descriptions of configuration nodes and values. It produces output such as:

#> This is the configuration of MyAwesomePlugin

# Sets the awesomeness of this plugin
awesomeness: 85

# Controls what levels of awesomeness are enabled
enabledLevels:
  # Whether awesome looks are enabled
  looks: true
  # Whether awesome usage is enabled
  usage: false

As you can see, the first header (the 'main' header) has a slightly different syntax. This is to prevent the first node header (awesomeness) from conflicting with the main header. Setting node headers is very easy:

config.setHeader("This is the configuration of MyAwesomePlugin");
config.setHeader("awesomeness", "\nSets the awesomeness of this plugin");
config.setHeader("enabledLevels", "\nControls what levels of awesomeness are enabled");
config.setHeader("enabledLevels.looks", "Whether awesome looks are enabled");
config.setHeader("enabledLevels.usage", "Whether awesome usage is enabled");

You can use the newline character '\n' to have multiple header lines, or to create empty lines (as seen in the example). There is also an addHeader method, which basically appends more text to the current header on a new line. This is somewhat equivalent to:

config.setHeader("awesomeness", config.getHeader("awesomeness") + "\n" + "Something added on a new line");

Conclusion

By setting defaults while getting data from a configuration, it is a lot easier to perform loading and saving. The addition of per-node headers allows configuration files full of user guidelines, which allows you to maintain a documentation without updating additional plugin pages.


Comments

Posts Quoted:
Reply
Clear All Quotes