[OUTDATED] How to easily handle HOCON in Sponge

(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. :smile:

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…

  1. This is NOT YAML
  2. This is definately NOT YAML
  3. HOCON is alot like JSON (google it)
  4. Immutability is key
  5. 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!)…##

  1. File (java.io)
  2. ConfigurationLoader (ninja.leaping.configurate.loader)
  3. 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 :slight_smile: :slight_smile: :slight_smile:

7 Likes

Immutability = An object is immutable when it is once initialized and its values are unchangeable after that.
Serialization = The concept of writing an object to a file and reading it again.

1 Like

Simply means read-only. the object you get is unable to be mutated. If you want to change it’s value, you need to create a new one and replace it. If the object that contains that one is also immutable, you must create a new one of those, and so forth.

Sorry about that, thank you.

How do i submit content to the sponge docs? @TBotV63

Umm, github

looks a lot like YAML Kappa

In all seriousless, I was dreading having to learn this for plugin making. Nice tutorial!

1 Like

Additions to the docs are done through pull requests on github. Feel free to post a PR or an issue over there :smile:

I kinda prefer CSON over HOCON when it comes to JSON alternatives

Hocon isn’t really a JSON alternative (neither is CSON imo). They are ways of representing data in a human readable format and merely inherit syntax from JSON. JSON’s advantage is that it is very strict and there is mostly only one way of representing something. That allows for faster parsing (not implying that parsing JSON is always faster than parsing HOCON etc.), which makes it a great serialization format when you don’t want humans to handle the data anyway. CSON and especially HOCON have an advantage of being humanreadable, supporting comments and having way more liberal syntax.

Btw, CSON really reminds me of YAML…

EDIT: Yes, I rant :stuck_out_tongue:

But your point remains valid.

I meant I prefer CSON in terms of readability.

1 Like

Writting plugins is considerably harder in Sponge than Bukkit? I think you’ll find that’s quite the opposite. The only reason people think it’s harder is because they only know how to code plugins and have no Java experience.

In my opinion the complexity of writting plugins for Sponge is the EXACT same as written them for Bukkit. I’d love it if you could share with me why you personally think it’s harder.

2 Likes

It’s harder because it’s different for me. I got used to bukkit, but this is so different I gave up at commands

Well no offence, but ‘used to Bukkit’ shouldn’t be a thing if you want to develop. You should learn what it actually means to understand an API and how to use one.

I just can’t understand the commands at all. Maybe it’s because sponge makes more utilizaion in OOP, something I am clueless when it comes to? I have no professional training as a developer, and do most of my stuff in non-oop situations

OOP isn’t really all that complicated to understand.
Perhaps you should google OOP concepts, It will seriously benefit you in the long run.

Anyway the command structure if fairly easy to understand. You use the Builder class to create a Command instance. You trigger methods within the builder that returns the builder instance each time, this means you can chain methods like so:

builder.someMethod().someMethod().someMethod().someMethod().build();

Thank you! I will check this out when I am home. :slight_smile:

What he is saying is it’s a very new structure, focused more towards “professionalism” ex. Builders. I have never worked with builders before but I undersand why they are there, but it doesn’t make it any easier to understand.

1 Like

No, it’s really not a valid point.
If you knew how to code, then you’d know how to read the docs, and the API.

I’m sorry, but saying it’s harder than Bukkit doesn’t really make much sense at all.
You cannot compare it to languages in the real world. If Sponge was written in another language then I’d completely agree with you, but I really don’t see how it’s any harder to learn.

In fact I think Sponge is easier to learn, I never saw Bukkit officially represent themselves to these standards in regards to documentation.

2 Likes

To each is own. I’m not here to argue.