Sérialisation de données
Alors qu’un ImmutableDataManipulator est une bonne façon de stocker des données pendant que le serveur est allumé, il ne va pas persister après un redémarrage. Toutefois, chaque DataManipulator implémente l’interface DataSerializable et peut donc être sérialisé en un DataContainer et désérialiser par un DataBuilder.
Après cette conversion initiale depuis le DataManipulator
spécialisé vers une structure plus générale, le DataContainer
peut être traité ultérieurement.
DataContainer et DataView
Un DataView est une structure polyvalente pour tenir tout type de données. Il supporte plusieurs valeurs et même imbriquer DataView
s comme valeur, ce qui permet une structure arborescente. Chaque valeur est identifée par un DataQuery. Un DataContainer
est un DataView
racine.
Chaque DataSerializable
fournit une méthode toContainer()
qui va créé et retourner un DataContainer
. Par exemple, appeler toContainer()
sur une instance de HealthData va produire un DataContainer
contenant deux valeurs, une pour la santé actuelle et une pour la santé maximale, chacune étant identifée par le DataQuery
de la Key
respective.
import org.spongepowered.api.data.DataContainer;
import org.spongepowered.api.data.key.Keys;
DataContainer serializedHealth = healthData.toContainer();
double currentHealth = serializedHealth.getDouble(Keys.HEALTH.getQuery()).get();
currentHealth == healthData.health().get(); // true
Convertir ce conteneur en une instance d”HealthData
se fait par le DataBuilder
correspondant. Ils sont enregistrés et gérés par le DataManager. Ils peuvent être soit obtenus depuis une instance valide de Game ou en utilisant la classe utilitaire Sponge. Le DataManager
fournit une méthode pour récupérer le DataBuilder
approprié pour désérialiser une classe donnée et, en outre, une méthode plus courte pour récupérer le DataBuilder
et lui faire faire la désérialisation en une étape. Les deux exemples de code suivants sont fonctionnellement équivalents.
Exemple de Code : Désérialisation, la façon longue
import org.spongepowered.api.data.DataView;
import org.spongepowered.api.data.manipulator.mutable.entity.HealthData;
import org.spongepowered.api.util.persistence.DataBuilder;
import java.util.Optional;
public Optional<HealthData> deserializeHealth(DataView container) {
final Optional<DataBuilder<HealthData>> builder = Sponge.getDataManager().getBuilder(HealthData.class);
if (builder.isPresent()) {
return builder.get().build(container);
}
return Optional.empty();
}
Exemple de Code : Désérialisation, la façon courte
import org.spongepowered.api.data.manipulator.mutable.entity.HealthData;
public Optional<HealthData> deserializeHealth(DataView container) {
return Sponge.getDataManager().deserialize(HealthData.class, container);
}
La fonction ` deserializeHealth`` va retourner Optional.empty()
sil il n’y a pas de DataBuilder
enregistré pour HealthData
ou si le DataContainer
fournit est vide. Si des données invalides sont présentes dans le DataContainer
, une InvalidDataException sera levée.
DataTranslator
Dans Sponge, généralement les implémentations MemoryDataView et MemoryDataContainer sont utilisés, qui résident en mémoire uniquement et ne persistent donc pas après un redémarrage. Afin de constamment stocker un DataContainer
, il doit d’abord être converti en une représentation stockable.
En utilisant l’implémentation DataTranslators#CONFIGURATION_NODE de DataTranslator, nous pouvons convertir un DataView
en ConfigurationNode et vice versa. Les ConfigurationNode
s peuvent ensuite être écrites et lues depuis des fichiers persistants à l’aide de la Librairie Configurate.
Exemple de Code : Sérialiser une instance d’HealthData pour Configurate
import ninja.leaping.configurate.ConfigurationNode;
import org.spongepowered.api.data.persistence.DataTranslator;
import org.spongepowered.api.data.persistence.DataTranslators;
public ConfigurationNode translateToConfig(HealthData data) {
final DataTranslator<ConfigurationNode> translator = DataTranslators.CONFIGURATION_NODE;
final DataView container = data.toContainer();
return translator.translate(container);
}
Exemple de Code : Désérialiser une instance d’HealthData depuis Configurate
import java.util.Optional;
public Optional<HealthData> translateFromConfig(ConfigurationNode node) {
final DataTranslator<ConfigurationNode> translator = DataTranslators.CONFIGURATION_NODE;
final DataView container = translator.translate(node);
return deserializeHealth(container);
}
DataFormat
Une alternative au DataTranslator
est le DataFormat, qui vous permet de stocker un DataContainer
en format HOCON, JSON ou NBT. Vous pouvez également recréer des DataContainers en utilisant les DataFormats
. Les implémentations de DataFormat
fournies par Sponge sont disponibles dans la classe DataFormats.
Par exemple, nous pouvons utiliser le DataFormat
DataFormats#JSON qui nous permet de créer une représentation JSON d’un DataContainer
. Le JSON en sortie peut ensuite être facilemeent stocké dans une base de données. Nous pouvons alors utiliser le même DataFormat
pour recréer le DataContainer
original à partir de ce JSON lorsque c’est nécessaire.
Imports pour les exemples de code
import org.spongepowered.api.Sponge;
import org.spongepowered.api.data.DataContainer;
import org.spongepowered.api.data.persistence.DataFormats;
import org.spongepowered.api.item.inventory.ItemStackSnapshot;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
Exemple de Code : Sérialiser un ItemStackSnapshot au format JSON
String json = DataFormats.JSON.write(itemStack.toContainer());
Exemple de Code : Désérialiser un ItemStackSnapshot depuis le format JSON
DataContainer container = DataFormats.JSON.read(json);
Exemple de Code : Écrire un ItemStackSnapshot dans un fichier en utilisant NBT
public void writeItemStackSnapshotToFile(ItemStackSnapshot itemStackSnapshot, Path path) {
DataContainer dataContainer = itemStackSnapshot.toContainer();
try (OutputStream outputStream = Files.newOutputStream(path)) {
DataFormats.NBT.writeTo(outputStream, dataContainer);
} catch (IOException e) {
// For the purposes of this example, we just print the error to the console. However,
// as this exception indicates the file didn't save, you should handle this in a way
// more suitable for your plugin.
e.printStackTrace();
}
}
Exemple de Code : Lire un ItemStackSnapshot depuis un fichier en utilisant NBT
public Optional<ItemStackSnapshot> readItemStackSnapshotFromFile(Path path) {
try (InputStream inputStream = Files.newInputStream(path)) {
DataContainer dataContainer = DataFormats.NBT.readFrom(inputStream);
return Sponge.getDataManager().deserialize(ItemStackSnapshot.class, dataContainer);
} catch (IOException e) {
e.printStackTrace();
}
return Optional.empty();
}