SirYwell

Moderator
Donator
Nov. 2, 2017
8
2
Warum gibt es dieses Tutorial?
Ich habe mich entschlossen, dieses Tutorial zu erstellen, da ich oft sehe, dass Bukkit/Spigot-Plugins irgendwo eine Location in eine YAML-Konfigurationsdatei schreiben oder herauslesen. Meist wird dabei wie folgt vorgegangen:
Java:
public Location loadLocation(String path) {
    World world = Bukkit.getWorld(getConfig().getString(path + ".world"));
    int x = getConfig().getInt(path + ".x");
    int y = getConfig().getInt(path + ".y");
    int z = getConfig().getInt(path + ".z");
    return new Location(world, x, y, z);
}

public void saveLocation(Location location, String path) {
    getConfig().set(path + ".world", location.getWorld().getName());
    getConfig().set(path + ".x", location.getBlockX());
    getConfig().set(path + ".y", location.getBlockY());
    getConfig().set(path + ".z", location.getBlockZ());
    saveConfig();
}

Sieht soweit richtig aus, funktioniert auch gut. Allerdings stellt uns Bukkit etwas bereit, was uns hier eine Menge Arbeit abnimmt: Das Interface ConfigurationSerializable. Klassen, die dieses Interface implementieren, kann man nämlich direkt in die Konfiguration schreiben bzw. auslesen.

Aber was ist die Alternative?
Mehrere Klassen von Bukkit sind Subklassen oder Subinterfaces von ConfigurationSerializable. Eine Liste findet ihr in den oben verlinkten Javadocs zu dem Interface. Wie ihr mit diesen Klassen umgeht, zeige ich euch hier:
Java:
public Location loadLocation(String path) {
    return (Location) getConfig().get(path);
}

public void saveLocation(Location location, String path) {
    getConfig().set(path, location);
    saveConfig();
}

Diese Methoden erfüllen die gleiche Aufgabe. Zugegeben, im ersten Codeabschnitt speichern wir nur Integer-Werte für x, y und z und mit der zweiten Variante speichern wir diese als Double-Werte und außerdem zwei Float-Werte für pitch und yaw. Hierbei kommt es aber auch vollkommen darauf an, wie man natürlich das Location-Objekt an die Konfiguration übergibt.
Beispielsweise könnten die Unterschiede so in der gepeicherten YAML-Datei aussehen:
YAML:
serialized:
  ==: org.bukkit.Location
  world: world
  x: -123.48872766645316
  y: 90.0
  z: 567.1446789517951
  pitch: 22.50058
  yaw: 82.34863
not-serialized:
  world: world
  x: -124
  y: 90
  z: 567

Abgesehen davon lassen sich aber auch Objekte wie ItemStacks und vieles mehr auf diese einfache Art speichern. Für ItemStacks stellt die Konfiguration sogar eine Methode bereit, damit man nicht casten muss:
Java:
ItemStack itemStack = getConfig().getItemStack(path);
(Fragt mich nicht, warum es das nicht für Locations gibt.)

Was kann man damit noch so anstellen?
Nun wollen wir einen Schritt weitergehen und unsere eigene Klasse erstellen, die genauso praktisch verwendet werden kann:

Java:
@SerializableAs("example")
public class MySerializableObject implements ConfigurationSerializable {

    private final int number;
    private final String name;
    private final List<String> list;

    public MySerializableObject(int number, String name, List<String> list) {
        this.number = number;
        this.name = name;
        this.list = list;
    }

    @Override
    public Map<String, Object> serialize() {
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("number", this.number);
        result.put("name", this.name);
        result.put("list", this.list);
        return result;
    }
}

Die Annotation @SerializableAs definiert ein Alias, unter welchem diese Klasse in der Konfiguration gespeichert werden soll. Später mehr noch dazu.

Die Methode, die wir überschreiben müssen, gibt eine Map zurück, die alle Werte mit einem von uns zugegebenen Key beinhaltet, die abgespeichert werden sollen.

Theoretisch könnten wir ein solches Objekt nun schon speichern, allerdings würde das bei einem erneuten Laden der Konfigurationsdatei eine Fehlermeldung generieren. Bukkit muss nämlich erst wissen, wie es dieses Objekt wieder aus der Datei auslesen soll. Hierbei gibt es zwei mögliche Vorgehensweisen.
Konstruktor:
Java:
public MySerializableObject(Map<String, Object> result) {
    this.number = (int) result.get("number");
    this.name = (String) result.get("name");
    this.list = (List<String>) result.get("list");
}

Wenn die Klasse einen Konstruktor enthält, der nur den Parameter Map<String, Object> enthält, kann Bukkit diesen verwenden.
Methode:
Java:
public static MySerializableObject deserialize(Map<String, Object> result) {
    int number = (int) result.get("number");
    String name = (String) result.get("name");
    List<String> list = (List<String>) result.get("list");
    return new MySerializableObject(number, name, list);
}


Alternativ kann Bukkit auch die Methode public static YourObject valueOf(Map<String, Object> result)
verwenden, um ein Objekt aus der Datei zu lesen. Die Funktionsweise bleibt dabei gleich.
Ein letzter Schritt ist jedoch noch notwendig, damit Bukkit wirklich etwas mit der Klasse anfangen kann:
Beim Aktivieren des Plugins, bzw vor dem ersten Zugriff auf die Datei, muss die Klasse noch registriert werden. Dies geht wie folgt:
Java:
ConfigurationSerialization.registerClass(MySerializableObject.class, "example");
Hier taucht auch wieder unser Alias auf, welches Bukkit benötigt, um von diesem aus wieder die dazugehörige Klasse zu finden.
Beachte: ConfigurationSerialization ist nicht das Interface, das wir zuvor verwendet haben ;)

Endergebnis
Wenn wir nun ein beispielhaftes Objekt erstellen und in eine YAML-Datei speichern, sieht das wie folgt aus:
YAML:
my-own-object:
  ==: example
  number: 1337
  name: This is CONFIGURATIONSERIALIZABLE
  list:
  - One
  - Two
  - Three

Ich hoffe, ich konnte dem ein oder anderen damit weiterhelfen und viel Arbeit ersparen.
 

Users who are viewing this thema