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.1
You 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}, …] .) |