This component centralizes the configuration of your application, providing simple access and automatic parsing.
- Documentation
- Javadoc
- Loader class (for
loadComponents) - Your configuration class (see below)
The concept🔗
The old way to manage configuration is pretty annoying. You have to access the main plugin class everytime, the data types are limited, the configuration paths are hard-coded...
Using QuartzLib, you transform this...
final EntityType entity;
try
{
entity = EntityType.valueOf(MainPluginClass.getInstance().getConfig().getString("my_section.entity", "ZOGLIN"));
}
catch (IllegalArgumentException e)
{
entity = EntityType.ZOGLIN;
MainPluginClass.getInstance().getLogger().warning("Invalid entity set in config; using default value.");
}
...into this:
final EntityType entity = Config.MY_SECTION.ENTITY.get();
Better, eh?
Writing the configuration class🔗
Behind the scenes, there is a class storing the configuration scheme.
Here is the only small inconvenient of this configuration management: you have to duplicate the config in both the config.yml file and this class. But it's a small task, plus a tool exist to generate the configuration class from the config.yml (we'll talk about this later).
You'll have to write a class extending the Configuration class. Let's call this class Config.
import fr.zcraft.quartzlib.components.configuration.Configuration;
public final class Config extends Configuration
{
}
For everything to work, load this class in your main plugin class:
loadComponents(Config.class);
Configuration entries🔗
Then you add the configuration entries. The idea is to add a public and static field per entry, the data type being a ConfigurationItem<?>.
import fr.zcraft.quartzlib.components.configuration.Configuration;
import fr.zcraft.quartzlib.components.configuration.ConfigurationItem;
// I highly recommend to import this statically
import static fr.zcraft.quartzlib.components.configuration.ConfigurationItem.item;
public final class Config extends Configuration
{
public static ConfigurationItem<String> MY_CONFIG = item("my_config", "default value");
}
Here I created a String configuration item, related to the key my_config in the config.yml file. Using it like this will return the value as a String from the configuration file, or "default value" if the key is missing.
final String value = Config.MY_CONFIG.get();
Referencing sub-keys🔗
Let's say you have this configuration file:
title: My title
display-as:
scoreboard: true
action-bar: true
You can write the Config class in two different ways. The most intuitive one is to reference the sub-keys with the dot notation, just like Bukkit:
public final class Config extends Configuration
{
public static ConfigurationItem<String> TITLE = item("title", "My title");
public static ConfigurationItem<Boolean> DISPLAY_AS_SCOREBOARD = item("display-as.scoreboard", true);
public static ConfigurationItem<Boolean> DISPLAY_AS_ACTION_BAR = item("display-as.action-bar", true);
}
But you can also use sub-classes, like this:
import fr.zcraft.quartzlib.components.configuration.Configuration;
import fr.zcraft.quartzlib.components.configuration.ConfigurationItem;
import fr.zcraft.quartzlib.components.configuration.ConfigurationSection;
import static fr.zcraft.quartzlib.components.configuration.ConfigurationItem.item;
import static fr.zcraft.quartzlib.components.configuration.ConfigurationItem.section;
public final class Config extends Configuration
{
public static final ConfigurationItem<String> TITLE = item("title", "My title");
public static final DisplayAsSection DISPLAY_AS = section("display-as", DisplayAsSection.class);
public static class DisplayAsSection extends ConfigurationSection
{
public ConfigurationItem<Boolean> SCOREBOARD = item("scoreboard", true);
public ConfigurationItem<Boolean> ACTION_BAR = item("action-bar", true);
}
}
The section method is used to identify the DISPLAY_AS attribute as a section. The string is the section key in the config.yml file, and the class is a reference to the sub-class defining the configuration in this section.
The sub-classes have to extend ConfigurationSection, and its members are not static; excepted that, it's the same as before. You also don't have to mention the parent section in a configuration class (I wrote item("scoreboard", true); instead of item("display-as.scoreboard", true);).
To access fields defined like this, it's really simple (and readable):
final Boolean displayAsScoreboard = Config.DISPLAY_AS.SCOREBOARD.get();
Advantages of sub-keys references using sub-classes🔗
This kind of class is a bit harder to write, but it comes with some advantages.
-
You can reuse the configuration sections. See this configuration file (inspired by BelovedBlocks):
oak: name: "Oak" craftable: true itemGlow: true birch: name: "Oak" craftable: true itemGlow: true # ...You can of course repeat the sections, but you can also write something like this:
public final class Config extends Configuration { public final BlockSection OAK = section("oak", BlockSection.class); public final BlockSection BIRCH = section("birch", BlockSection.class); // And 10 more if you want static public class BlockSection extends ConfigurationSection { public final ConfigurationItem<String> NAME = item("name", String.class); public final ConfigurationItem<Boolean> CRAFTABLE = item("craftable", true); public final ConfigurationItem<Boolean> GLOW = item("itemGlow", true); } } -
You can extend configuration sections. Now we will use this
config.yml(inspired by BelovedBlocks too):oak: name: "Oak" craftable: true itemGlow: true amountCrafted: 4 birch: name: "Birch" craftable: true itemGlow: true amountCrafted: 4 # ... stonecutter: name: "Stonecutter" craftable: true itemGlow: true usageInLore: true percentageBreaking: 0.1 saw: name: "Saw" craftable: true itemGlow: true usageInLore: true percentageBreaking: 0.1You can use inheritance mechanisms to write less code and avoid duplicates:
public final class Config extends Configuration { public final BlockSection OAK = section("oak", BlockSection.class); public final BlockSection BIRCH = section("birch", BlockSection.class); // And 10 more if you want public final ToolSection STONE_CUTTER = section("stonecutter", ToolSection.class); public final ToolSection SAW = section("saw", ToolSection.class); // Same here static public class ItemSection extends ConfigurationSection { public final ConfigurationItem<String> NAME = item("name", String.class); public final ConfigurationItem<Boolean> CRAFTABLE = item("craftable", true); public final ConfigurationItem<Boolean> GLOW = item("itemGlow", true); } static public class BlockSection extends ItemSection { public final ConfigurationItem<String> AMOUNT_CRAFTED = item("amountCrafted", String.class); } static public class ToolSection extends ItemSection { public final ConfigurationItem<Boolean> USAGE_IN_LORE = item("usageInLore", true); public final ConfigurationItem<Float> PERCENTAGE_BREAKING = item("percentageBreaking", 0.1f); }And from the user point of view, it's still the same.
final float sawPercentage = Config.SAW.PERCENTAGE_BREAKING.get();
Lists in the configuration🔗
To retrieve configurations like this:
my-list:
- Item 1
- Item 2
...create an entry like this. Note the ConfigurationList instead of ConfigurationItem, and the list method after.
import static fr.zcraft.quartzlib.components.configuration.ConfigurationItem.list;
public final class Config extends Configuration
{
public static ConfigurationList<String> MY_LIST = list("my-list", String.class);
}
The type parameter of ConfigurationList<?> is the data type of the items in the list (here, strings). You also have to give the data type again in the list method, due to Java limitations.
The ConfigurationList<?> class implements the List<?> and Iterable<?> interfaces, so you can use them like this:
for (String item : Config.MY_LIST)
// Do something...
Maps in the configuration🔗
Maps are for YAML structures like this:
map:
key: 1.5
key2: 45.2
where the keys and values can be changed by the user. The keys can be a string or anything that can be extracted from a string. The value can be either a simple value (like the ones of item), or a section.
Translation in the code:
public static ConfigurationMap<String, Float> MAP = map("map", String.class, Float.class);
Or with a section instead:
map:
key:
item1: value
item2: value
key2:
item1: value
item2: value
public static ConfigurationMap<String, MapSection> MAP = map("map", String.class, MapSection.class);
public static class MapSection extends ConfigurationSection
{
public static ConfigurationItem<String> ITEM_1 = item("item1", "value");
public static ConfigurationItem<String> ITEM_2 = item("item2", "value");
}
The ConfigurationMap<?, ?> objects implements the Map<?,?> and Iterable<Map.Entry<?,?>> interfaces, so you can list the values stored like this:
for (Map.Entry<String, MapSection> entry : Config.MAP.entrySet())
// Do something...
The great and hidden powers of the Value Handlers🔗
So you have a class to handle your configuration. Nice. You put data types inside. You probably noticed you can use the basic data types just like Bukkit:
public static ConfigurationItem<String> MY_STRING = item("my-string", "default value");
public static ConfigurationItem<Boolean> MY_BOOLEAN = item("my-boolean", true);
public static ConfigurationItem<Double> MY_DOUBLE = item("my-double", 42.69d);
But in fact, you can also write this:
public static ConfigurationItem<Material> MY_MATERIAL = item("my-material", Material.NETHER_STAR);
public static ConfigurationItem<Achievement> MY_ACHIEVEMENT = item("my-achievement", Achievement.OPEN_INVENTORY);
Or this:
public static ConfigurationItem<ItemStack> MY_ITEM = item("my-item", new ItemStack(Material.STONE, 2));
Or even this:
public static ConfigurationItem<MyCustomObject> MY_OBJECT = item("my-object", new MyCustomObject("some default"));
This is where things get interesting.
A Configuration Value Handler?🔗
Behind the scenes, a Configuration Value Handler is what is used to transform a raw value retrieved from the Bukkit configuration accessor to an exploitable, validated value.
It's a simple static method taking a single parameter (retrieved from the config through Bukkit) and returning a parsed and transformed object of the right type (either simple like a Double or more complex like an ItemStack).
The built-in handlers🔗
We created a few handlers for usual data types, avoiding you to reimplement them.
| Data type | Configuration example |
|---|---|
All standard data types.
|
Extracted using the [Type].parse[Type](Object) methods, or toString for strings-related types. |
Any Enum.Using Enum.valueOf(String) after dash and spaces replacement by underscores, and upper-casing.
|
material: DIRT
entity: vex
your-custom-enum: a value |
A Locale.Using Locale.Builder.setLanguageTag(String)
|
lang: fr-FR |
A Bukkit Vector.Use them to retrieve a location. |
Either:
vector: 15,65,-128
or:
vector:
x: 15
y: 65
z: -128
(Fields are optional and default to 0 in the second format.) |
An Enchantment.As they are not technically an Enum.
|
enchantment: damage_undead |
An ItemStack.
|
item:
type: cake block # Only required field
amount: 12 # Defaults to 0
data: 1
title: "The Lie"
lore:
- Line 1 of the lore
- Line 2 of the lore
glow: true # Adds a fake enchant to have a glowing item
hideAttributes: true # Hides all the item attributes in the tooltip
enchantments:
damage_all: 2
depth_strider: 3
# Below: only for the Potion type
effect: fire resistance # Required
level: 2 # Defaults to 1
splash: true # Defaults to false
extended: true # Defaults to false
(Enchantment names — Potion effect names) |
A Potion.
|
potion:
effect: fire resistance # Required
level: 2 # Defaults to 1
splash: true # Defaults to false
extended: true # Defaults to false
(Potion effect names) |
A DyeColor.It's an enum so you can just use the name, but a support for old numeric codes was added. |
dye: yellow
dye: 12
(Dye names) |
A BannerMeta.
|
banner:
color: black # A DyeColor, see above
patterns:
- {Pattern: hh,Color: 0}
- {Pattern: cs,Color: 15}
- {Pattern: hhb,Color: 1}
- {Pattern: ms,Color: 14}
- {Pattern: ts,Color: 15}
- {Pattern: bs,Color: 15}
- {Pattern: bo,Color: 15}
(Banner generator; extract the values from the commands. You can also write the JSON value directly: [{Pattern: hh}, …].) |
zDevelopers