(Moderation Edit) ** PLEASE NOTE: **
This guide is no longer updated and therefore lacking new information plus may contain (now) false information. Please head over to the SpongeDocs to get the latest information including updates.
Hello.
I am here to show you what has worked for me when it comes to loading this obscure (yet very brilliant) new form of configuration. If you are coming here directly from bukkit, you will find this very new and different. The fact is that there are huge differences between a language like YAML and a less strict language like HOCON. Hopefully i can point out a few reasons to learn the new API for i/o. Lets begin.
Lets start with some background info:
Sponge has chosen HOCON for a concept called immutability. Now why did it change, do you ask? In my opinion, I think the developers have decided on a new format of configuration for performance and ease-of-access, but also because of the type of API that sponge is becoming. As you can tell, building plugins has become quite a bit different than Bukkit had handed to us in the past, but this is not because it is not well written or extremely thought out. In fact, its quite the opposite. The reason it is advanced and more complex is because it is trying to flush out some of the problems that Bukkit had faced from day one, while also increasing power and efficiency by incorporating the Forge API (and inheriting HOCON from it). So for them to take on the challenge of teaching people to make great plugins with a great library, they are subtly increasing the quality of the plugins that will be released to the public.
Now for the tutorialâŚ
HOCON Example ( this is what we will build ):
Root {
# Boolean?
"Boolean_Node"=false
# Integer?
"Integer_Node"=1735267
# String?
"String_Node"=i-am-a-string
}
Now, as you can probably tell, this is very foreign to how Bukkit handled âconfig.ymlâ's. It looks ugly, it smells ugly, but it works wonders like you would not believe. I have grown to love this language because of how versatile and easy to code it is. But there are quite a few key concepts you have to grasp before you can start making your new permissions/economy plugins. Here are a couple thoughts to get you startedâŚ
- This is NOT YAML
- This is definately NOT YAML
- HOCON is alot like JSON (google it)
- Immutability is key
- It sorts your configurations alphabetically!
Now for the fun partâŚ
A Basic Configuration Class:
To begin, create a new class in your maven project.
Mine will be named âHoconExamplePlugin.javaâ.
Here is your empty class:
package com.github.snumbers.example;
import org.slf4j.Logger;
import org.spongepowered.api.event.Subscribe;
import org.spongepowered.api.event.state.ServerStartingEvent;
import org.spongepowered.api.plugin.Plugin;
import com.google.inject.Inject;
@Plugin(name = "HoconExample", id = "HoconExample", version = "0.0A")
public class HoconExamplePlugin {
@Inject
public Logger logger;
@Subscribe
public void serverEvent(ServerStartingEvent event) {
}
}
From here, we will add some required fields (these are important!)âŚ##
- File (java.io)
- ConfigurationLoader (ninja.leaping.configurate.loader)
- CommentedConfigurationNode (ninja.leaping.configurate.commented)
here is what we added:
package com.github.snumbers.example;
import org.slf4j.Logger;
import org.spongepowered.api.event.Subscribe;
import org.spongepowered.api.event.state.ServerStartingEvent;
import org.spongepowered.api.plugin.Plugin;
import com.google.inject.Inject;
@Plugin(name = "HoconExample", id = "HoconExample", version = "0.0A")
public class HoconExamplePlugin {
@Inject
public Logger logger;
@Inject
@DefaultConfiguration(sharedRoot = true)
private File configuration = null;
@Inject
@DefaultConfiguration(sharedRoot = true)
private ConfigurationLoader<ComentedConfigurationNode> configurationLoader = null;
private CommentedConfigurationNode configurationNode = null;
@Subscribe
public void serverEvent(ServerStartingEvent event) {
}
}
Now that we have our variables ready, what does this mean?
-
File is the flat-file storage of all our configuration settings.
-
ConfigurationLoader is the object that hosts the configuration changes going on (read/write). We are not using it explicitly, except for loading our config nodes into memory.
-
CommentedConfigurationNode. This is the field we will use to read and write data to and from the HOCON configuration. It allows per node comments, which you can see above in the Hocon Example
From here we will be able to set some default values in our ServerStartingEvent subscription.
Adding some default values:
Here, we will be adding a try and if inside our server start event. inside of which we will check if the file exists already, if it does we will move on, but if it doesnât we will execute the default configuration setup. This includes loading the CommentedConfigurationNode from the ConfigurationLoader. After which we will set the values of the configuration with .getNode(root, path).setValue(value).setcomment(âcommentâ); and then we finish it off with saving our changed values, exiting the if, then proceeding to load the changed values back into the configurationNodeâs memory.
If you are not sure about the way getNode(root, path) handles HOCON configuration, i suggest reading it on the sponge website because it is hard to explain: https://docs.spongepowered.org/en/
package com.github.snumbers.example;
import org.slf4j.Logger;
import org.spongepowered.api.event.Subscribe;
import org.spongepowered.api.event.state.ServerStartingEvent;
import org.spongepowered.api.plugin.Plugin;
import com.google.inject.Inject;
@Plugin(name = "HoconExample", id = "HoconExample", version = "0.0A")
public class HoconExamplePlugin {
@Inject
public Logger logger;
@Inject
@DefaultConfiguration(sharedRoot = true)
private File configuration = null;
@Inject
@DefaultConfiguration(sharedRoot = true)
private ConfigurationLoader<ComentedConfigurationNode> configurationLoader = null;
private CommentedConfigurationNode configurationNode = null;
@Subscribe
public void serverEvent(ServerStartingEvent event) {
try {
if(!configuration.exists()){
configuration.createNewFile();
configurationNode = confgurationLoader.load();
configurationNode("Root", "BooleanNode)
.setValue(false)
.setComment("Boolean?");
configurationNode("Root", "IntegerNode)
.setValue(1735267)
.setComment("Integer?");
configurationNode("Root", "StringNode)
.setValue("i-am-a-string")
.setComment("String?");
configurationLoader.save(configurationNode);
}
configurationNode = configurationLoader.load();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
And there you have it! You have successfully written to the serverâs config folder under ".conf
Check your imports, save your project, and export your plugin to your forge server under the âmodsâ folder. After starting up your server, you will find your config inside the âconfigâ folder in the server directory.
Reading from your new config:
For example, we will write out to our console the values of our config after they have been loaded (so within the same event). This is accomplished by using the configurationNode field to use .getNode(root, path).get/TYPE/(); where /TYPE/ is the kind of Object you wish to fetch.
We will add three logging methods into our code below our catch statement:
package com.github.snumbers.example;
import org.slf4j.Logger;
import org.spongepowered.api.event.Subscribe;
import org.spongepowered.api.event.state.ServerStartingEvent;
import org.spongepowered.api.plugin.Plugin;
import com.google.inject.Inject;
@Plugin(name = "HoconExample", id = "HoconExample", version = "0.0A")
public class HoconExamplePlugin {
@Inject
public Logger logger;
@Inject
@DefaultConfiguration(sharedRoot = true)
private File configuration = null;
@Inject
@DefaultConfiguration(sharedRoot = true)
private ConfigurationLoader<ComentedConfigurationNode> configurationLoader = null;
private CommentedConfigurationNode configurationNode = null;
@Subscribe
public void serverEvent(ServerStartingEvent event) {
try {
if(!configuration.exists()){
configuration.createNewFile();
configurationNode = confgurationLoader.load();
configurationNode("Root", "BooleanNode)
.setValue(false)
.setComment("Boolean?");
configurationNode("Root", "IntegerNode)
.setValue(1735267)
.setComment("Integer?");
configurationNode("Root", "StringNode)
.setValue("i-am-a-string")
.setComment("String?");
configurationLoader.save(configurationNode);
}
configurationNode = configurationLoader.load();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
logger.info("Boolean Node: " + configurationNode.getNode("Root", "BooleanNode").getBoolean();
logger.info("Integer Node: " + configurationNode.getNode("Root", "IntegerNode").getInteger();
logger.info("String Node: " + configurationNode.getNode("Root", "StringNode").getString();
}
Please notice the .getBoolean(), .getInteger(), getString() methods and how they are fetching their respective values. If you mix these up chances are you will not notice because it wont throw errors, it will just default to a â0â or something else that isnât the value you have written in the config. Iâve had problems with this that is why i am bringing it up, its just something to pay attention to.
Making this more friendly:
These snippets will allow you to more easily fetch these values.
public String getString(String root, String node) {
String result = "null";
if(configurationNode.getNode(root, node) != null) {
result = configurationNode
.getNode(root,node).getString();
return result;
}
return result;
}
That pretty much sums up the basics of configuration files.
if you have questions, PM me or better yet, reply to this thread.
Hope this helps