Serialización de Datos

Mientras un ImmutableDataManipulator es una buena manera de almacenar datos mientras el servidor está ejecutándose, no persistirá durante el reinicio. Sin embargo, cada implemento DataManipulator de la interfaz :javadoc:`DataSerializable`y así puede ser serializado a un :javadoc:`DataContainer` y deserailizado a un DataBuilder.

Después de esta conversación inicial del DataManipulator especializado para una estructura más general, el DataContainer se puede seguir procesando.

DataContainer y DataView

Un DataView es una estructura de propósito general para almacenar cualquier tipo de datos. Soporta múltiples valores e incluso anida DataViews como un valor, así permite una estructura de árbol. Cada valor es identificado por un DataQuery. Un DataContainer es una raíz DataView.

Cada DataSerializable proporciona un método toContainer() que creará y devolverá un DataContainer. Como un ejemplo, llamando toContainer() en una instancia HealthData producirá un DataContainer que contiene dos valores, uno para el estado actual y uno para la salud máxima, cada uno identificado por el DataQuery de la respectiva Llave.

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 este contenedor de vuelta a una instancia HealthData se realiza por el``DataBuilder`` correspondiente. Esos son registrados y administrados por el DataManager. También puede ser obtenido por una instancia válida de :javadoc:`Juego` o utilizando la clase de utilidad Sponge. El DataManager``proporciona un método para obtener el apropiado ``DataBuilder para deserializar una clase dada y adicionalmente un método abreviado para obtener el DataBuilder y hacer que realice la deserialización en un solo paso. Los siguientes dos ejemplos de código son funcionalmente equivalentes.

Ejemplo de Código: Deserialización, la manera larga

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();
}

Ejemplo de Código: Deserialización, la manera corta

import org.spongepowered.api.data.manipulator.mutable.entity.HealthData;

public Optional<HealthData> deserializeHealth(DataView container) {
    return Sponge.getDataManager().deserialize(HealthData.class, container);
}

La función deserializeHealth devolverá Optional.empty()` si no hay ``DataBuilder` registrado para ``HealthData o el DataContainer suministrado está vacío. Si datos inválidos están presentes en el DataContainer, una InvalidDataException se producirá.

DataTranslator

En Sponge, generalmente las implementaciones MemoryDataView y MemoryDataContainer son utilizadas, las cuales residen solo en la memoria y así no persistirán después de reiniciar el servidor. Con el fin de almacenar persistentemente un DataContainer, primero tiene que ser convertido en una representación almacenable.

Utilizando la implementación DataTranslators#CONFIGURATION_NODE para DataTranslator, podemos convertir un DataView en un ConfigurationNode y viceversa. ConfigurationNodes puede entonces ser escrito y leído por un archivo persistente utilizando la Biblioteca de Configuración.

Ejemplo de Código: Serializar una instancia HealthData para Configuración

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

Ejemplo de Código: Deserializar una instancia HealthData desde Configuración

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

Una alternativa para utilizar un DataTranslator es usar DataFormat, que le permite almacenar un DataContainer en formato HOCON, JSON o NBT. Puede también recrear DataContainers utilizando DataFormats. Sponge proporciona implementaciones DataFormat que están disponibles en la clase DataFormats.

Por ejemplo, podemos utilizar el DataFormats#JSON DataFormat que nos permite crear una representación de JSON de un DataContainer. La salida JSON puede entonces fácilmente ser almacenada en un base de datos. Podemos luego usar el mismo DataFormat para recrear el DataContainer original de este JSON cuando es requerido.

Importaciones para ejemplos de código

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;

Ejemplo de Código: Serializar un ItemStackSnapshot para formato JSON

String json = DataFormats.JSON.write(itemStack.toContainer());

Ejemplo de Código: Deserializar un ItemStackSnapshot del formato JSON

DataContainer container = DataFormats.JSON.read(json);

Ejemplo de Código: Escribir un ItemStackSnapshot para un archivo utilizando 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();
    }
}

Ejemplo de Código: Leer un ItemStackSnapshot desde un archivo utilizando 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();
}