Configuration Nodes
In memory, the configuration is represented using ConfigurationNodes. A ConfigurationNode
either holds
a value (like a number, a string or a list) or has child nodes, a tree-like configuration structure. When using a
ConfigurationLoader to load or create new configurations, it will return the root node. It is
recommended that you always keep a reference to that root node stored somewhere.
Nota
Depending on the ConfigurationLoader
used, you might even get a CommentedConfigurationNode, which in
addition to normal ConfigurationNode
behavior is able to retain a comment that will persist on the saved config
file.
Values
Basic Values
Basic value types like int
, double
, boolean
or String
each have their own convenience getter method
which will return the value or a default if the node does not contain a value of that type. Let’s check if the server
administrator wants our plugin to enable its blockCheats module by checking the value at the
modules.blockCheats.enabled
path.
boolean shouldEnable = rootNode.getNode("modules", "blockCheats", "enabled").getBoolean();
Yes, it’s really as simple as that. Similar to the above example, methods like ConfigurationNode#getInt(), ConfigurationNode#getDouble() or ConfigurationNode#getString() exist that allow you to conveniently grab a value of that type.
To set a basic value to a node, just use the ConfigurationNode#setValue(Object) method. Don’t be confused
that it accepts an Object
- this means that it can take anything and will determine how to proceed from there by
itself.
Imagine the blockCheats module is deactivated by a user command. This change will need to be reflected in the config and can be done as follows:
rootNode.getNode("modules", "blockCheats", "enabled").setValue(false);
Aviso
Anything other than basic value types cannot be handled by those basic functions, and must instead be read and
written using the (de)serializing Methods described below. Basic types are those that are natively handled by the
underlying implementation of the file format used by the ConfigurationLoader
, but generally include the
primitive data types, String
s as well as List
s and Map
s of basic types.
(De)Serialization
If you attempt to read or write an object that is not one of the basic types mentioned above, you will need to pass it
through deserialization first. In the ConfigurationOptions
used to create your root ConfigurationNode
, there
is a collection of TypeSerializers that Configurate uses to convert your objects to a
ConfigurationNode
and vice versa.
In order to tell Configurate what type it is dealing with, we have to provide a guava TypeToken
. Imagine we want
to read a player UUID
from the config node towns.aFLARDia.mayor
. To do so, we need to call the getValue()
method while providing a TypeToken
representing the UUID
class.
import java.util.UUID;
UUID mayor = rootNode.getNode("towns", "aFLARDia", "mayor").get(TypeToken.of(UUID.class));
This prompts Configurate to locate the proper TypeSerializer
for UUID
s and then use it to convert the stored
value into a UUID
. The TypeSerializer
(and by extension the above method) may throw an ObjectMappingException
if it encounters incomplete or invalid data.
Now if we we want to write a new UUID
to that config node, the syntax is very similar. Use the setValue()
method with a TypeToken
and the object you want to serialize.
rootNode.getNode("towns","aFLARDia", "mayor").setValue(TypeToken.of(UUID.class), newUuid);
Nota
Serializing a value will throw an ObjectMappingException
if no TypeSerializer
for the given TypeToken
can be found.
For simple classes like UUID
, you can just create a TypeToken
using the static TypeToken.of()
method.
But when the class you want to use has type parameters of its own (like Map<String,UUID>
) the syntax gets a
little more complicated. In most cases you will know exactly what the type parameters will be at compile time, so
you can just create the TypeToken
as an anonymous class: new TypeToken<Map<String,UUID>>() {}
. That way,
even generic types can conveniently be written and read.
Veja também
For more information about TypeToken
s, refer to the guava documentation
The types serializable using those methods are:
Any basic value (see above)
Any
List
orMap
of serializable typesThe types
java.util.UUID
,java.net.URL
,java.net.URI
andjava.util.regex.Pattern
Any type that has been made serializable as described on the config serialization page
Defaults
Unlike the Sponge API, the Configurate library does not use Optional
for values that might not be present but null.
While the getters for primitive methods (like getBoolean()
or getInt()
) might return false
or 0
, those
that would return an object (like getString()
) will return null
if no value is present. If you do not want to
manually handle those special cases, you can use default values. Every getXXX()
method discussed above has an
overloaded form accepting an additional parameter as a default value.
Let us take a look at the example for reading a boolean value again.
boolean shouldEnable = rootNode.getNode("modules", "blockCheats", "enabled").getBoolean();
This call will return false
if either the value false
is saved in the config or the value is not present in the
config. Since those two cases are indistinguishable we have no simple way of setting our variable to false
only if
that is the value specified on the config. Unless we specify true
as the default value.
boolean shouldEnable = rootNode.getNode("modules", "blockCheats", "enabled").getBoolean(true);
Similarly, you can specify defaults on any value you get from the config, thus avoiding null
returns or
ObjectMappingException
caused by the absence of the whole value. It also works on the deserializing getValue()
method. Some examples:
String greeting = rootNode.getNode("messages", "greeting").getString("FLARD be with you good man!");
UUID mayor = rootNode.getNode("towns", "aFLARDia", "mayor")
.getValue(TypeToken.of(UUID.class), somePlayer.getUniqueId());
Another useful application of those defaults is that they can be copied to your configuration if needed. Upon creation
of your root configuration node, you can create your ConfigurationOptions
with setShouldCopyDefaults(true)
.
Subsequently, whenever you provide a default value, Configurate will first check if the value you’re trying to get is
present, and if it is not, it will first write your default value to the node before returning the default value.
Let’s assume your plugin is running for the first time and the config file does not exist yet. You try to load it
with ConfigurationOptions
that enable copying of default values and get an empty config node. Now you run the
line rootNode.getNode("modules", "blockCheats", "enabled").getBoolean(true)
. As the node does not yet exist,
configurate creates it and writes the value true
to it as per the ConfigurationOptions
before returning it.
When the config is then finished, the value true
will persist on the node without ever being explicitly set.