Best Practices in separating plugin algorithms from config access? [Much answered]

Hey all,

I’ve been reading “Effective Java” and listening to the Coding Blocks podcasts fairly regularly now, and they’ve proved an amazing resource. I’m finally thinking in terms of design patterns instead of spaghetti. With that, it has made using the debugger and rapidly developing plugin resources much faster.

That being said, I’m not quite sure the best way to adapt these practices to config files. I assume there are important relations to factory patterns here, but it does seem like hard coding in getNode(“Some Vaguely Human Readable Node”) is not the best way to go about it.

So for projects where you guys have a fairly large number of constants/user-changable constants, how do you integrate ConfigurationNode-based constants without your code turning into spaghetti?

1 Like

You can create a class annotated with ConfigSerializable, and then declare fields with the @Setting annotation. (See here to read more about it) That way you can just have a Config class which holds all values you need as variables.

A good example of what @Wundero mentioned can be seen in my plugin Consensus and its config.

1 Like

Like @Wundero I would move all configurable variables into a separated class and have this class handle loading and parsing of the configuration file (via ConfigSerializable or anything else). You can than inject this class into every class that depends on configurable values.

I would not, however, use public fields to store the configurable values. Unless static and final, public fields are (nearly) always a very bad idea, because you cannot control any external changes made to these variables - plus you cannot validate the state of a certain field before handling its value out to other classes. Instead, I would recommend using private fields together with getters to be used by other classes.

If you are using getters it is also a good idea to introduce an interface that actually defines all these getters and have a class implementing this interface that connects it with Sponge’s configuration logic. This way, classes that depend on your configuration can be programmed against the interface.
You can swap out the configuration implementation at any time and unit testing becomes much easier because you can easily mock the interface.

@pie_flavor
Can you adapt a @Configserializable/@Setting method of holding node values and use it to set default values? It seems like this would be a great way to generate a config file.

Another snag I ran into whilst trying your approach is that static classes in Config.java don’t allow for instantiation (obviously). My use case:

Region1:
-a private primitive value
-a private SubRegion value1
// what I’d like to do:
-a private SubRegion value1 = new SubRegion(specifics to Region1)

Region2:
-a private primitive value
-a private SubRegion SubRegion value2
// what I’d like to do:
-a private SubRegion value2 = new SubRegion(specific defaults to Region2)

SubRegion:

  • a constructor that sets the primitive default value?
    -a primitive value

=====================================================

@TheE
That is excellent advice! I feel this would be a very appropriate thing to put in the docs.

Back in days when sponge did not have ConfigSerializable I wrote my own annotation based config parsing.

-it can generate default config based on field value (see examples) (static init. or double brace )

  • works only on static fields.

  • can generate comments

  • For more complex structures (Objects) you should use Gson which is one dependency of sponge api if im not mistaken

how d’you mean?

@ConfigSerializable public class Foo {
    @Setting Bar bar = new Bar();
    @ConfigSerializable public static class Bar {
        @Setting int test = 3;
    }
}

@pie_flavor
TIL whilst taking bart you can instantiate static classes. Back to the docs it is!

Ok so thats how you set the field of test to three, but can you set it to three via a constructor?
Ex:

If anyone is willing to cobble together improved documentation for this and throw a PR our way, SpongeDocs editorial staff will definitely give it fair consideration. Clarity and ease-of-use are important features.
Trial by fire (aka actual live testing) is definitely spurring the evolution of Sponge and it’s Docs :wink:

… why wouldn’t you? A static class is just a normal class with a weird namespace.

So to clarify, @Setting Val val = 2 IS setting the defaults to equal 2, but can be overridden if there is a value in the config file.

EDIT: also how do I adapt your (pie_flavor’s) config to auto-write a config file based on coded defaults? So far its just creating a blank config file

Yes, if you set it like that 2 will be the default value. If you didn’t set it to anything, is will use the normal default value for that type (0 for int, false for boolean, 0D for double and so on). Watch out for what the default value is, as it’s a nice player for bugs to find their way in.

As for how to write the config, you’ll have to do that yourself.

I guess I’m a bit confused as the docs say “Since in many cases the (de)serialization boils down to mapping fields to configuration nodes, writing such a TypeSerializer is a rather dull affair and something we’d like Configurate to do on its own.”

I guess whomever wrote this meant “deserialization” without the (de) parenthesis, which would mean if you wanted to serialize your object you would need to provide custom TypeSerializers. Mk I can do that, I was just wondering if there wasn’t a shortcut as was lightly implied by the docs.

I think you should be able to save defaults. You can load a root node from a file and then copy it; use root.setValue(TypeToken.of(Config.class), config); on one of the roots, then merge values such that the root that you did NOT set the value of takes priority. Then you can save the merged node back to the file in order to properly load defaults. Note that I haven’t tested this but I believe it should work.

EDIT: Forgot object in setValue(...) method.

You can write the config back to the config node as simple as you can read it, but the process of actually saving the config node itself is something you still have to do.

1 Like

Tried to do that here, but it seems as though Configuration does not accept com.google.common.reflect.TypeToken$SimpleTypeToken:

Heres the batholith defaults class btw:

That’s because when you use setValue(...), you need to use both the TypeToken AND the object which holds the defaults. Sorry about that, will edit original post.

wew! ok so no more errors, but the testconfig.conf file also doesn’t show any nodes after the .save() method:

Does it show anything? Because if BatholithDefaults is a properly annotated class, then the code you posted should work.

Nope. Completely empty config file. Here’s BatholithDefaults http://pastebin.com/9bWY9Awc