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
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:
@SerializableAs
annotation just above the class, as follows:@SerializableAs("WarperLocation") public class SerializableLocation implements ConfigurationSerializable {
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.
3.145.74.54