Writing a ConfigurationSerializable class

Serialization is the process of translating data or objects into a form that can be written to a file. In the plugin Warper, we will need to save the Bukkit locations. Locations cannot be serialized themselves. Therefore, we will make our own class that holds the Bukkit Location object data and is able to convert it to and from a map. If you are new to maps, they are a very useful type of collection that we will use throughout this project. Maps have keys and values. Each key points to a specific value. The Warper plugin is a good example of how maps can be used. When teleporting, a player will choose a specific location to warp to by name. If all the warp locations were in a list, we would have to iterate through the list until the warp location with the correct name was found. With a map, we can pass the key (the name of the warp) to the map, and it will return the value (the warp location).

Create a new class called SerializableLocation, which contains a private variable that holds the Bukkit Location object. The first constructor will require a Location object. We will also include a getLocation method. The following code is how the beginning of the new class looks:

package com.codisimus.warper;

import org.bukkit.Location;

/**
 * A SerializableLocation represents a Bukkit Location object
 * This class is configuration serializable so that it may be
 * stored using Bukkit's configuration API
 */
public class SerializableLocation {
    private Location loc;

    public SerializableLocation(Location loc) {
        this.loc = loc;
    }

    /**
     * Returns the Location object in its full form
     *
     * @return The location of this object
     */
    public Location getLocation() {
        return loc;
    }
}

Once you add implements ConfigurationSerializable, your IDE should warn you about implementing all the abstract methods. The method that you must override is serialize. This will return a map representation of your object. We already mentioned each piece of data that we will need. Now, we just have to assign each piece of data a name and put it in a map. To add data to a map, you can call the put method. This method requires two parameters, namely a key and a value of the key. A key is simply a name for the piece of data that allows us to reference it later. The value is the serializable data. To find out more about maps, you can read the Javadoc at https://docs.oracle.com/javase/8/docs/api/java/util/Map.html. For the serialize method, we will need to get all the data that we mentioned earlier and put it in a map, as follows:

/**
 * Returns a map representation of this object for use of serialization
 *
 * @return This location as a map of Strings to Objects
 */
@Override
public Map<String, Object> serialize() {
    Map map = new TreeMap();
    map.put("world", loc.getWorld().getUID().toString());
    map.put("x", loc.getX());
    map.put("y", loc.getY());
    map.put("z", loc.getZ());
    map.put("yaw", loc.getYaw());
    map.put("pitch", loc.getPitch());
    return map;
}

This handles the saving portion, but we still have to handle the loading. The simplest way to do this is by adding a constructor that takes the map as a parameter, as follows:

/**
 * This constructor is used by Bukkit to create this object
 *
 * @param map The map which matches the return value of the serialize() method
 */
public SerializableLocation(Map<String, Object> map) {
    //Check if the world for this location is loaded
    UUID uuid = UUID.fromString((String) map.get("world"));
    World world = Bukkit.getWorld(uuid);
    if (world != null) {
        //Each coordinate we cast to it's original type
        double x = (double) map.get("x");
        double y = (double) map.get("y");
        double z = (double) map.get("z");

        //Both yaw and pitch are loaded as type Double and then converted to float
        float yaw = ((Double) map.get("yaw")).floatValue();
        float pitch = ((Double) map.get("pitch")).floatValue();

        loc = new Location(world, x, y, z, yaw, pitch);
    } else {
        Warper.plugin.getLogger().severe("Invalid location, most likely due to missing world");
    }
}

Loading is essentially the opposite of saving. We pull each value from the map and then use it to create the Bukkit Location object. As a safeguard, we will first verify that the world is actually loaded. If the world is not loaded, the location will not exist. We do not want the plugin to crash because of this. There is also no reason why you need to try loading the location of a nonexistent world, because no one will be able to teleport to it anyway.

Each object that you get from the map will have to be cast to its original type, which was done in the previous code. The float values are an exceptional case. Each of the float values will be read as a double value. The double value is similar to float, but it is more precise. Therefore, loading the float values as the double values and then converting them will not cause data loss.

Both of these methods will be used by Bukkit. As a programmer, you will only have to store this object in the YAML configuration. This can be done by simply using the following line of code:

config.set("location", serializableLoc);

Then, you can retrieve the data later by using the following code:

SerializableLocation loc = (SerializableLocation)config.get("location");

Bukkit uses the serialize method and the constructor to handle the rest.

The class name and path are used to reference this class. To see an example of this, take a look at the ItemStack object in the config.yml file for the MobEnhancer plugin. An example of this class has also been provided:

==: com.codisimus.warper.SerializableLocation

Tip

The path will of course have your own namespace, not com.codisimus.

This works fine, but it may cause confusion, especially with long pathnames. However, there is a way to ask Bukkit to reference this class by using an alias. Perform the following two steps to complete this:

  1. Add the @SerializableAs annotation just above the class, as follows:
    @SerializableAs("WarperLocation")
    public class SerializableLocation implements ConfigurationSerializable {
  2. Register your class within the ConfigurationSerialization class:
    ConfigurationSerialization.registerClass(SerializableLocation.class, "WarperLocation");

This can be done in the onEnable method. Just ensure that it is executed before you attempt loading the data.

Tip

The serializable name must be unique. Therefore, it is better to include your plugin name rather than simply Location. That way, you can have a serializable location for another plugin without them conflicting.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.74.54