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 Map
s, unlike List
s 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.