Skip to content

Config Manager

Redempt edited this page Dec 15, 2020 · 24 revisions

Handling config is always a pain. It seems there's no way to do it in a way that doesn't feel annoying - at least not with the Spigot API alone. With RedLib, all your config problems will go away.

Similarly to Command Manager, Config Manager uses annotations to hook into your code. Rather than methods, though, it hooks into fields.

In your main plugin class, you can put the following code:

private static ConfigManager config;

@Override
public void onEnable() {
	config = new ConfigManager(this).register(this).saveDefaults().load();
}

On its own, this really won't do anything. But it's important to note what all of these method calls mean, because they'll become relevant the moment we add a config hook. Instantiating the ConfigManager with a plugin as an argument just instantiates it for the default config, config.yml in your plugin's data folder. Calling register(this) registers your plugin's config hooks, meaning that's where the data will be loaded to and saved from. Calling saveDefaults() means that any values initialized in the hooked variables will be saved into the config. load() then loads all the data from config into the variables.

The variables in question should be annotated with @ConfigHook with the value being the path to the config value they represent.

@ConfigValue("delay")
int delay = 5;

When register() is called, it will find this variable. The "delay" in the ConfigHook annotation specifies that the path to the value in the config is delay. Notice that it has an initialized value of 5, meaning that when saveDefaults() is called, if there isn't a value with the path delay in the config, it will be set to 5 and saved. When load() is called, it will load whatever value is in the config at the path delay into your variable. It works for all types supported by Spigot's YAML parser, including string lists and ItemStacks.

If you call save() on the ConfigManager, it will save all the values currently in your variables to config.

But sometimes you need to load configuration sections, too. ConfigManager can do that as well. All you have to do is write a class with a variable for each value you want to load from each entry in the config section. One example would be if you want to make a grouping system:

@ConfigMappable
public class Group {
	
	@ConfigValue("owner)
	private UUID owner;
	@ConfigValue("members")
	private List<UUID> members = ConfigManager.list(UUID.class);
	
	//A constructor with no arguments is required
	protected Group() {}
	
	public Group(UUID leader) {
		owner = leader;
		members = new ArrayList<>();
	}
	
}

Note the default constructor - that is needed for Config Manager to initialize the variable without passing anything to a constructor. Without it, instantiation is inaccessible. Additionally, the @ConfigMappable annotation is required for this object to be considered mappable by the ConfigManager.

And that's all we need, there's just one last step. In your main file again, you can add:

@ConfigHook("groups")
Map<String, Group> groups = ConfigManager.map(Group.class);

@Override
public void onEnable() {
	new ConfigManager(this).addConverter(UUID.class, UUID::fromString, UUID::toString)
		.register(this).saveDefaults().load();
}

Calling ConfigManager.map creates a ConfigMap, which extends HashMap<String, T> with T being the type of the class you give it. This will tell ConfigManager how to load data into the objects stored in the map.

Putting .* at the end is required, as it specifies that this will use the whole config section. When you call load() on your ConfigManager, it will load all the entries from the group section into the Map. The section would look like this:

groups:
	a:
		owner: UUID-here
		members:
		- member1-UUID
		- member2-UUID

You may also notice that a converter for UUID was added. This is because the Group class stores the owner's and members' UUIDs, which isn't a data type YAML lets you store directly. All that's needed is to give it the class, as well as a method to convert it from a string, then back to a string. This can work for any data type as long as you give it the converter. It also works for lists of types other than those supported, in a manner similar to config maps. You may notice that, in the Group class, the members variable is instantiated with ConfigManager.list(UUID.class). Similarly toConfigManager.map, this will create a special type of List that tells ConfigManager how to load data into it and allows it to use custom types, including config-mappable objects.

Another thing that's often needed is for some extra steps to be run after all the data has been inserted into a config-mappable object. This can be achieved with the @ConfigPostInit flag, which goes over a method which will be run after all the values for that object are loaded from config:

@ConfigPostInit
private void postInit() {
	//Perform post-initialization tasks here
}```

This will be run immediately after values are populated, before anything else happens.

For config-mappable objects being stored in a map created through `ConfigManager.map`, you can also use an annotation to get the key the object is under:


@ConfigMappable public class Group {

@ConfigPath
private String name;
@ConfigValue("owner")
private UUID owner;
@ConfigValue("members")
private List<UUID> members = ConfigManager.list(UUID.class);

private Group() {}

public Group(UUID owner) {
	this.owner = owner;
}

@ConfigPostInit
private void postInit() {
	//Post-initialization 
}

public String getName() {
	return name;
}

public UUID getOwner() {
	return owner;
}

public List<UUID> getMembers() {
	return members;
}

}```

Here's what a simple, complete config-mappable object might look like. When it is loaded from config, or when an instance is inserted into a map created through the ConfigManager, the name field will be set to the key it's under in the configuration. Alternatively, instead of using a String, you can use a ConfigurationSection to get the section it is under. It has a post-init method, which will be called after initialization of all values is complete, and the rest is a fairly standard Java POJO. One thing that's worth noting is that there is no need for the name to be passed to the constructor. When you put this object into the map, the name field will be updated automatically.

Clone this wiki locally