序列化資料

虽然 ImmutableDataManipulator 存储数据是十分美妙的,然而对于重启服务器这一操作,数据的存储就束手无策了。然而,每一个 DataManipulator 都实现了 DataSerializable 接口,这意味着我们就可以使用 DataContainer 序列化(Serialize)数据,同时使用 DataBuilder 反序列化(Deserialize)数据。

在通过初步地从特定的 DataManipulator 转换为较为普遍的结构之后, DataContainer 还可以起进行进一步的转换。

DataContainer 與 DataView

作为一个可以表示任何数据的通用结构, DataView 支持多个值,甚至包括嵌套的 DataView 作为值,这使得其看起来像一个树形的结构。每一个索引都使用 DataQuery 去表示。 DataContainerDataView 的根。

每个 DataSerializable 都提供一个名为 toContainer() 的方法将其转换为 DataContainer 。作为示例,通过对一个 HealthData 的实例的 toContainer() 方法调用将会返回一个包含有两个数据的 DataContainer ,一个表示当前生命值,一个表示最大生命值。每个 DataQuery 都通过对应的 Key 确定。

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

现在就是 DataBuilder 大显身手的时候了,它可以把一个 DataContainer 反序列化成一个 HealthData 的实例。 DataManager 负责注册和管理这些事情。它的获取方式包括一个 Game 的实例,或者 Sponge 类等。 DataManager 提供了一个方法用于获取合适的 DataBuilder 用于反序列化指定的 Class 和 额外的 DataBuilder 从而一步完成反序列化操作。下面的代码示例是等价的。

代码示例:较长代码的反序列化操作

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

代码示例:较短代码的反序列化操作

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

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

如果没有用于 HealthDataDataBuilder 被注册,或者指定的 DataContainer 为空,方法 deserializeHealth 将会返回一个 Optional.empty() 。如果 DataContainer 中含有非法数据,那么将抛出一个 InvalidDataException 异常。

DataTranslator

在 Sponge 中,一般的实现有 MemoryDataViewMemoryDataContainer ,它们只存储在内存中,因此在服务器重新启动后便不复存在。为了持久存储一个 DataContainer ,我们首先必须将其转换为可存储形式。

通过使用 DataTranslators#CONFIGURATION_NODE 实现 DataTranslator ,我们可以将一个 DataView 转换为一个 ConfigurationNode ,反之亦然。 ConfigurationNode 可以使用 Configurate Library 来写入和读取可被持久保存的文件。

代码示例:把 HealthData 序列化为配置文件数据

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

代码示例:把配置文件数据反序列化为 HealthData

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

另外一种使用 DataTranslator 的方式是使用 DataFormat 。你可以通过这种方式,把一个 DataContainer 以 HOCON、JSON、或者 NBT的方式存储。你也可以通过 DataFormats 重新创建一个 DataContainer 。你可以在 DataFormats 类中看到 Sponge 对 DataFormat 的实现。

比如说,我们可以通过使用 DataFormats#JSON 这一 DataFormat ,把一个 DataContainer 转换成 JSON 的形式。输出的 JSON便可以方便地存储在数据库等地方。我们还可以使用相同的 DataFormat 把产生的 JSON 转换回原来的 DataContainer

代码示例所引用的类

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;

代码示例:把一个 ItemStackSnapshot 序列化为 JSON 格式

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

代码示例:反序列化一个 JSON 格式的文本以得到一个 ItemStackSnapshot

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

代码示例:把一个 ItemStackSnapshot 序列化为 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();
    }
}

代码示例:反序列化一个 NBT 格式的文件以得到一个 ItemStackSnapshot

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