-
Notifications
You must be signed in to change notification settings - Fork 0
Creating custom SavableObjects
When working on larger projects and on various variables, it becomes a necessity to save some data in YAML files.
Think about a clan plugin, that has to create a YAML file for each clan created, holding the name, the members, the leader and more.
This is nothing new or complicated, once you get the hang of it, but it gets pretty annoying and repetitive, especially if required for multiple projects.
Not only that, but it also allows to create more bugs in your code that could have been avoided in the first place.
Thanks to BearCommands, you will not have to worry about saving manually anything ever again: welcome to SavableObjects!
Contents |
---|
Savable |
YamlField |
PreventSaving |
YamlObjects |
YamlPairs |
A Practical Example |
With Savable Objects we refer to all custom classes that extend the Savable class.
This class introduces a bunch of new methods to load and dump fields to a file and from an object.
The plugin will handle everything for you, all you have to do is pass the destination file in the constructor and call the save()
method.
To create this kind of behaviour, Savable converts every field into a YamlField object.
This class is basically a wrapper for the real field, waiting to be retrieved from or saved to a determined configuration section of the file.
When calling the reload()
method, every field is converted into a YamlField and setObject()
is called.
On the contrary, when saving a Savable object, every field is again converted and the YamlField method save()
is called.
Fear not because the plugin also provides a custom annotation: PreventSaving. Simply use it on any field you do not want to be saved. More examples later.
YamlField are not only wrappers for the real fields, but they also have an important role: to find the correct YamlObject to associate to the field type they are holding.
A YamlObject
is an abstract class capable of loading and saving an object.
public abstract class YamlObject<O> {
/**
* Constructor used for loading.
* @param yamlPairs: the YAML pairs.
*/
public YamlObject(YamlPair<?>[] yamlPairs);
/**
* Constructor used for saving.
* @param object: the object to save.
* @param yamlPairs: the YAML pairs.
*/
public YamlObject(O object, YamlPair<?>[] yamlPairs);
/**
* @return the stored object.
*/
public O getObject();
/**
* Loads the object from the configuration section from the given path.
* @param configurationSection: the configuration section (can be a Configuration file);
* @param path: the path where to find the object.
* @return the loaded object, null if some errors occur.
*/
public abstract O load(Configuration configurationSection, String path) throws Exception;
/**
* Dumps the object to the configuration section at the given path.
* @param configurationSection: the configuration section (can be a Configuration file);
* @param path: the path where to save the object.
*/
public abstract void dump(Configuration configurationSection, String path) throws Exception;
}
BearCommands offers several predefined YamlObjects, but more can be created:
- UUIDYamlObject;
- DateYamlObject;
- BearPlayerYamlObject (works for every BearPlayer implementation);
- MapYamlObject;
- CollectionYamlObject;
- EnumYamlObject.
As you might have noticed from the constructors of YamlObject, YamlPairs are very important for this system to work. The predefined YamlObjects we discussed earlier do not have a direct correlation to the classes they represent. It is thanks to YamlPairs that this bond is formed:
YamlPair<?>[] defaultPairs = new YamlPair[]{
new YamlPair<>(UUID.class, UUIDYamlObject.class),
new YamlPair<>(Date.class, DateYamlObject.class),
new YamlPair<>(ABearPlayer.class, BearPlayerYamlObject.class),
new YamlPair<>(Map.class, MapYamlObject.class),
new YamlPair<>(Collection.class, CollectionYamlObject.class),
new YamlPair<>(Enum.class, EnumYamlObject.class)
};
YamlPair accepts two classes: the first class is the destination class, the second is the corresponding YamlObject class.
Well, glad you asked!
As I anticipated, you can use this system to automate your own custom classes loading and dumping.
All you have to do is create your own YamlObject and pair it with your class,
then simply call the save()
method on your object to save it and reload()
to load it from file.
All of this might seem daunting and complicated, but once you fully understand, you truly see the power of Savable object.
Let's use an example to demonstrate it:
First thing first, we will need an object to save. Let's use the Clan example I used earlier:
package it.fulminazzo.testplugin.Objects;
import it.angrybear.Annotations.PreventSaving;
import it.angrybear.Objects.Savable;
import it.fulminazzo.testplugin.TestPlugin;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class Clan extends Savable {
private String name;
@PreventSaving
private final List<String> invites = new ArrayList<>();
private final List<TestPlayer> members = new ArrayList<>();
public Clan(TestPlugin plugin, String name, TestPlayer leader) {
super(plugin, new File(plugin.getDataFolder(), name + ".yml"));
this.name = name;
members.add(leader);
}
public Clan(TestPlugin plugin, File clanFile) {
super(plugin, clanFile);
}
public String getName() {
return name;
}
public List<String> getInvites() {
return invites;
}
public List<TestPlayer> getMembers() {
return members;
}
}
As you can see, nothing too complicated. Just some things to keep in mind:
- Remember to extend the Savable class and call
super()
for its constructor; - Every field should NOT be final, since it will be later loaded by the plugin; However, if the field is of type List or Map (in general, Iterable), it should be initiated as an empty one;
- Note how in this case we do not want to save the invites, therefore we use the PreventSaving annotation to prevent it;
- The first constructor will only be called if the Clan object is newly created, while the second one only when retrieving.
You might have also noticed that we used a TestPlayer class to identify the members. We already used this class when talking about CustomPlayers but let's check it out again:
public class TestPlayer extends BearPlayer {
private String data;
public TestPlayer(TestPlugin plugin, File playersFolder, OfflinePlayer player, String data) throws Exception {
super(plugin, playersFolder, player);
this.data = data;
}
@Override
protected void createNew(UtilPlayer utilPlayer) {
data = ((Player) utilPlayer.getPlayer()).getDisplayName();
}
public String getData() {
return data;
}
}
Since TestPlayer is a custom class, we will need to create a custom YamlObject for it. Let's call it TestPlayerYamlObject, for the sake of uniformity:
package it.fulminazzo.testplugin.Objects.YamlElements;
import it.angrybear.Objects.Configurations.Configuration;
import it.angrybear.Objects.YamlElements.UUIDYamlObject;
import it.angrybear.Objects.YamlElements.YamlObject;
import it.angrybear.Objects.YamlPair;
import it.fulminazzo.testplugin.Objects.TestPlayer;
import it.fulminazzo.testplugin.TestPlugin;
import org.bukkit.Bukkit;
import java.util.UUID;
public class TestPlayerYamlObject extends YamlObject<TestPlayer> {
public TestPlayerYamlObject(YamlPair<?>... yamlPairs) {
super(yamlPairs);
}
public TestPlayerYamlObject(TestPlayer object, YamlPair<?>... yamlPairs) {
super(object, yamlPairs);
}
@Override
public TestPlayer load(Configuration configurationSection, String path) throws Exception {
// Get the section at the given path.
Configuration playerSection = configurationSection.getConfiguration(path);
// If the section is not found, return a null object.
if (playerSection == null) return null;
// Prepare to load a UUID type object from file using YamlObject.newObject(Class<?>, YamlPairs[]).
// Since we know we want to load a UUID, we can pass the UUID.class and
// assume it will return a UUIDYamlObject.
UUIDYamlObject uuidYamlObject = YamlObject.newObject(UUID.class, yamlPairs);
UUID uuid = uuidYamlObject.load(playerSection, "uuid");
// Load primitive-type object "data".
String data = playerSection.getString("data");
// Create the new object.
TestPlugin plugin = TestPlugin.getPlugin();
object = new TestPlayer(plugin, plugin.getDataFolder(), Bukkit.getOfflinePlayer(uuid), data);
// Return the newly created object.
return object;
}
@Override
public void dump(Configuration configurationSection, String path) throws Exception {
// Delete every previous information.
configurationSection.set(path, null);
// If the object is null, stop.
if (object == null) return;
// Create a section to the given path to host the new object.
Configuration playerSection = configurationSection.createSection(path);
// Calling YamlObject.newObject(Object, YamlPairs[]) to dump non-primitive type object object.getUuid().
// Since we know this is a UUID, we can assume it will return a UUIDYamlObject.
UUIDYamlObject uuidYamlObject = YamlObject.newObject(object.getUuid(), yamlPairs);
uuidYamlObject.dump(playerSection, "uuid");
// Dump primitive-type object object.getData().
playerSection.set("data", object.getData());
}
}
A couple of notes before we continue:
- the two given constructors are required and should not be altered (do not add any more parameter);
- when loading and dumping a UUID we used the
YamlObject.newObject()
static methods. This functions will return the correct YamlObject according to the given yamlPairs and object/class given (custom ones included).
Now that we have created our YamlObject, we will need to specify it to the plugin in the form of a YamlPair.
In the plugin main class, we can call the addAdditionalYamlPairs()
to achieve that:
import it.angrybear.Bukkit.SimpleBearPlugin;
import it.angrybear.Objects.YamlPair;
import it.fulminazzo.testplugin.Objects.YamlElements.TestPlayerYamlObject;
import it.rrberto.testplugin.TestPlayer;
public class TestPlugin extends SimpleBearPlugin {
@Override
public void onEnable() {
addAdditionalYamlPairs(new YamlPair<>(TestPlayer.class, TestPlayerYamlObject.class));
super.onEnable();
}
}
That's it!
You now have a fully functional Savable object of type Clan that will automatically load or save whenever required!
You can maybe create a manager that loads every clan on start and saves it on shutdown:
public class ClansManager {
private final List<Clan> clans;
public ClansManager(TestPlugin plugin) {
this.clans = new ArrayList<>();
File[] clanFiles = plugin.getDataFolder().listFiles();
if (clanFiles == null) return;
for (File clanFile : clanFiles) {
Clan clan = new Clan(plugin, clanFile);
clan.reload();
this.clans.add(clan);
}
}
public List<Clan> getClans() {
return clans;
}
public void saveAll() {
clans.forEach(c -> {
try {
c.save();
} catch (IOException e) {
TestPlugin.logError(BearLoggingMessage.GENERAL_ERROR_OCCURRED,
"%task%", "saving clan object " + c.getName(),
"%error%", e.getMessage());
}
});
}
}
- Home
- How to start a plugin
- Work with Configuration Files
- Work with Enums
- Work with Commands
- Work with Custom Players
- Work with Messaging Channels
- Creating custom SavableObjects
- Timers
- General Utils
- Placeholders
- Bukkit Utils
- Velocity Utils