自定義 DataManipulators

自定義資料的核心是 DataManipulator 。在實作之前,你必須先決定是否需要為自定義資料建立一個分離的 API。一般而言最好將 API 與實作分離(如同 SpongeAPI),但是如果不會被其他開發者使用你也可以將兩者放在同一個類別。

您需要為每個 「單元」 資料定義一個 API 方法, 如 「字串」、」int」、:javadoc: 「ItemStack」 或自訂類型 (如 「Home」)。這些單位將被包裹在一個:javadoc: 的 『 價值 『, 這將允許它被訪問與 :javadoc: 『 鍵 『 s。有各種 「值」 的擴展, 取決於將代表哪個物件, 例如提供標準地圖操作的 :javadoc: 「MapValue」, 或者 :javadoc: 「BoundedComparableValue」, 它可以對一個:javadoc:`Comparable`物件的上限和下限設置限制,如整數。

现在我们考虑你需要继承实现的 AbstractData 类型。虽然你可以从零开始,但这些类型已经为你减少了 很多 诸如实现需要的方法的工作。你可以在 org.spongepowered.api.data.manipulator.mutable.common 里找到一个完整的列表。请参阅 單一型態 或者 複合型態 以了解关于每一种类型的实现细节。

你需要創建兩個不同的類別, 一個是可變的版本,實作 DataManipulator 和你的抽象型態;以及不可變的版本,實作 ImmutableDataManipulator 和你的 不可變 抽象型態。

備註

全部 資料都必須有可變與不可變版本,你必須兩個都實作

對於所有型態,你都需要定義 DataManipulator#asImmutable()asMutable() 方法,只需要簡單的複製目前物件到另一個版本的建構子即可。

数据值

你的相应 Getter 需要返回一个数据值,不过如下所示,我们为你提供了 ValueFactory 帮你代劳一部分工作。因为 Sponge 已经实现了一些 Value 对象,所以说这可以大大减少不必要的数据值类型。产生不同类型的数据值取决于使用不同的方法,如 createMapValuecreateBoundedComparableValue 等。

程式碼範例:實作 Value Getter

import org.spongepowered.api.Sponge;
import org.spongepowered.api.data.value.ValueFactory;
import org.spongepowered.api.data.value.mutable.Value;

import org.spongepowered.cookbook.myhomes.data.home.Home;
import org.spongepowered.cookbook.myhomes.data.Keys;

@Override
protected Value<Home> defaultHome() {
    return Sponge.getRegistry().getValueFactory()
            .createValue(Keys.DEFAULT_HOME, getValue(), null);
}

請注意 ImmutableDataManipulator 必須回傳一個 ImmutableValue,可以對回傳的 Value 呼叫 asImmutable()。我們建議你對不可變版本的值做快取(例如類別的資料成員)。

每個 Value 都需要一個 Key 來識別它,像是在範例中的 Keys.DEFAULT_HOME 。與 value 相似,你可以使用 KeyFactorymakeXKey() 方法來為你的 value 創建一個 Key

你需要传入一个 TypeToken 用于代表你的数据值包装的 原生 类型,以及另一个 TypeToken 代表数据值类型本身。你同时需要提供一个 DataQuery ——这通常用于序列化你的数据值。还要为你的数据类型提供一个唯一的标识 ID 及名称。把这些放到一起你就可以得到一个可用于你的 ValueKey 了。

程式碼範例:創建 Key

import org.spongepowered.api.data.DataQuery;
import org.spongepowered.api.data.key.Key;
import org.spongepowered.api.data.key.KeyFactory;
import org.spongepowered.api.data.value.mutable.Value;
import org.spongepowered.api.data.value.mutable.Value;

import com.google.common.reflect.TypeToken;

import org.spongepowered.cookbook.myhomes.data.home.Home;

public static final Key<Value<Home>> DEFAULT_HOME = KeyFactory.makeSingleKey(
        TypeToken.of(Home.class),
        new TypeToken<Value<Home>>() {},
        DataQuery.of("DefaultHome"), "myhomes:default_home", "Default Home");

備註

伺服器實作利用 TypeToken來保存你的值的泛型型別。它們是以兩種方式的其中一種來建立:

  • 對於非泛型型態,使用 TypeToken.of(MyType.class)

  • 對於泛型型態,可以建立匿名類別 TypeToken<MyGenericType<String>>() {}

序列化

如果你希望你的数据可 serializableDataHolder 或者配置文件中,你必须还要实现 DataSerializable#toContainer() 方法。我们十分建议你调用父类的 super.toContainer() ,这样子就会包含 DataSerializable#getContentVersion() 提供的版本号信息。每次数据信息的结构发生变化时你应该提升你的版本号信息,并使用 DataContentUpdater 以保证向前兼容。

備註

這對於簡易單一型態是不需要的,因為它已經定義了 toContainer()

程式碼範例: 實作 toContainer

import org.spongepowered.api.data.DataContainer;

import org.spongepowered.cookbook.myhomes.data.Keys;

@Override
public DataContainer toContainer() {
    DataContainer container = super.toContainer();
    // This is the simplest, but use whatever structure you want!
    container.set(Keys.DEFAULT_HOME.getQuery(), this.defaultHome);
    container.set(Keys.HOMES, this.homes);

    return container;
}

單一型態

实现单一数据类型(Single Type)需要的工作要少些,因为很多方法都已经被你需要继承实现的 AbstractSingleData 类实现了。

「簡易」抽象型態最容易實作,但它僅限於下列型態:

  • Boolean

  • Comparable

  • Integer

  • List

  • Map

  • CatalogType

  • Enum

对于所有其他类型,你都需要通过继承实现 AbstractSingleData 类来实现它们。这允许你定义你自己的单一数据类型,而不受数据类型的约束,不过你就需要做较多的工作了。

小訣竅

默认的实现类已经在构造方法中把你的对象准备好了。你可以通过 getValue() 或者 getValueGetter() 方法获取到它。

簡易單一型態

幾乎所有工作都以經完成了。你需要做的只有:

  • 繼承對應的抽象型態

  • 對應 Key 到你的資料、物件本身、以及建構子的預設物件(如果物件為 null)

AbstractBoundedComparableData (及對應的不可變版本)還需要用於比較的最大及最小值,以及對應的 Comparator

備註

ListMapped 單一型態必須使用 ListDataMappedData(或對應的不可變版本)。這會為 DataManipulator 增加類似於操作 Map 或 List 的方法。

下列三個方法必須定義在可變的 manipluators 中:

fill(DataHolder, MergeFunction) 應該使用 MergeFunction#merge() 將提供的 DataHolder 取代你物件中的資料。

import org.spongepowered.api.data.DataHolder;
import org.spongepowered.api.data.merge.MergeFunction;

import org.spongepowered.cookbook.myhomes.data.friends.FriendsData;

import java.util.Optional;

@Override
public Optional<FriendsData> fill(DataHolder dataHolder, MergeFunction overlap) {
    FriendsData merged = overlap.merge(this, dataHolder.get(FriendsData.class).orElse(null));
    setValue(merged.friends().get());

    return Optional.of(this);
}

from(DataContainer) 應該從 container 中取得並覆蓋 value ,如果不存在則回傳 Optional.empty()

import org.spongepowered.api.data.DataContainer;
import org.spongepowered.api.data.DataQuery;

import org.spongepowered.cookbook.myhomes.data.Keys;
import org.spongepowered.cookbook.myhomes.data.friends.FriendsData;
import org.spongepowered.cookbook.myhomes.data.friends.ImmutableFriendsData;

import com.google.common.collect.Maps;

import java.util.Optional;
import java.util.UUID;

@Override
public Optional<FriendsData> from(DataContainer container) {
    if(container.contains(Keys.FRIENDS)) {
        List<UUID> friends = container.getObjectList(Keys.FRIENDS.getQuery(), UUID.class).get();
        return Optional.of(setValue(friends));
    }

    return Optional.empty();
}

copy() 顧名思義,應該回傳相同資料的副本。

import org.spongepowered.cookbook.myhomes.data.friends.FriendsData;

@Override
public FriendsData copy() {
    return new FriendsDataImpl(getValue());
}

自定義單一型別

除此之外,你還需要重新定義以下方法:

getValueGetter() 應該會傳遞代表你資料的 Value (見上文)。

toContainer() 應該會回傳含有你資料的 DataContainer (見上文)。

複合型態

和只能提供一种数据值的单一数据类型相比,复合数据类型可以提供多个“复合”的数据值。如果多个数据值是强关联的,像 FurnaceData 一样,那么复合数据类型便可大显身手,不过,实现复合数据类型,也就会变得更复杂一些。

首先请定义你的数据操纵器的所有数据值的 Getter。对于每一个数据值,请再创建相应的 裸露在外的 对象的 Getter 和 Setter。你马上就会用到它们。对于不可变数据操纵器来说,只有 Getter 是需要实现的。

註冊 Values

然后你需要执行注册操作,这样基于 数据键 的系统才能在其上工作。如欲注册,请分别针对可变和不可变数据操纵器实现 DataManipulator#registerGettersAndSetters()ImmutableDataManipulator#registerGetters() 方法。

對所有 value 你必須呼叫:

  • registerKeyValue(Key, Supplier) 傳入對應 key 的 Value getter

  • registerFieldGetter(Key, Supplier) 傳入上面定義的原始物件 getter 方法

  • registerFieldSetter(Key, Consumer) 傳入 setter 方法,如果你正在實作可變版本

我們建議使用 Java 8 的 :: 語法來簡化 SupplierConsumer 函數

程式碼範例:實作 Getters 與 Setters

import org.spongepowered.cookbook.myhomes.data.Keys

// registerGetters() for immutable implementation
@Override
protected void registerGettersAndSetters() {
    registerKeyValue(Keys.DEFAULT_HOME, this::defaultHome);
    registerKeyValue(Keys.HOMES, this::homes);

    registerFieldGetter(Keys.DEFAULT_HOME, this::getDefaultHome);
    registerFieldGetter(Keys.HOMES, this::getHomes);

    // Only on mutable implementation
    registerFieldSetter(Keys.DEFAULT_HOME, this::setDefaultHome);
    registerFieldSetter(Keys.HOMES, this::setHomes);
}

fill(DataHolder, MergeFunction)from(DataContainer) 的實作和單一型態類似,但必須處理所有的 values