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 DataViews 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 ConfigurationNodes 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);
}