Every time I make a DataManipulator, I am struck by how long it takes. It’s the same boilerplate, time after time after time, and it takes FOREVER. And if it takes me this long to write one, imagine how long it takes someone to figure out how to write one if they’ve never done it before. So I decided to just automatically generate them.
I shall anticipate the inevitable comments: This currently works for API 5/6. Completely untested on 7.
DataManipulatorGenerator is a standalone program, which converts a very small HOCON file into a full DataManipulator class and key class. This is the smallest possible configuration:
fields = [
{
type = Z
name = bool
}
]
plugin-id = "my_plugin_id"
This will generate a full DataManipulator, with methods getBool(), setBool(), bool(), the proper implementation of the entire manipulator, an immutable version, a builder for it, Keys.BOOL, and more.
Here’s the configuration:
The root of the file must have a list of objects called fields[]. Aside from that, there are a few other options.
class sets the name of the class; if absent, the name of the file is used.
key-class sets the name of the key class; if absent, the main class minus “Data” plus “Keys” is used.
package sets the package name.
plugin-id sets the plugin ID; it doesn’t directly modify the output, but it’s used in inferencing key IDs (i.e. <plugin-id>:<fieldname>).
Lastly, there’s the imports[] list, which contains a list of qualified class names to import. This is only necessary for any inner types of generics (e.g. List<ItemType> requires you to import org.spongepowered.api.item.ItemType).
Within a field object:
The two required fields are type, the type of the field, and name, the internal name of the field. It’s encouraged for name to be camelCase, to facilitate name inferencing. As for type, this must be the fully qualified type of the object (e.g. java.lang.String, java.util.UUID, etc.) if it is a class. If it is a primitive, the internal type name is used (the capital first letter of the type, except for boolean which is Z). There are other optional values:
full-type is used for generics. This is how the field should appear in type definition (e.g. List<String>, Map<UUID, Integer>. Non-generics and primitives do not need this field.
transient sets whether or not this field should be excluded from serialization. It won’t show up in toContainer() or from(DataView).
optional sets whether or not the field is optional. This should be used instead of using the literal type Optional for optional values.
default sets the default value for this field. I don’t expect you’ll need to use this unless you have something really convoluted, since the program knows the defaults for a bunch of Java types, Sponge types, CatalogTypes, etc. Note also that any field marked transient won’t have a default set for it in the no-arg constructor.
key{} contains the key-specific settings. Again, this doesn’t need to be present.
Within key{}:
name: The name of the static final Key. Ideally should be UNDERSCORE_CAPS. If not present, generated from the field name.
data-query: The path to use for the DataQuery. Dots are the separator. Ideally should be TitleCase. If not present, generated from the field name.
id: The ID of the Key. Ideally should be lowercase, with a <plugin id>: prefix. If not present, generated from the field name and (if present) plugin-id.
display-name: The display name of the Key. Ideally should be Spaced Title Case. If not present, generated from the field name.
Once you’ve got your configuration set, run DataManipulatorGenerator.jar however you like. If you double-click it, it’ll pop up a file select; if you run it from command-line, you can supply a list of files to it either as program arguments or through stdin.
For each config file, it will generate two Java source files. One is the keys file, which has a static final Key for every field. The other is the manipulator class, which has a class correctly implementing DataManipulator as well as static inner classes Immutable and Builder implementing ImmutableDataManipulator and DataManipulatorBuilder respectively.
The classes won’t contain any errors if the configuration doesn’t, so the generated files should be completely ready to import into whatever project you’re using, and the only remaining setup step is to register it during pre-initialization. Happy data-ing!
I lied, there might be one more step. DataContainer doesn’t have any methods to deserialize arbitrary objects inside Maps, unlike Lists and raw values. Any lines marked //TODO in the generated source need to be implemented manually. Aside from that, though, the generated files should be feature-complete.

