DataManipulators Customisés

La partie principale des données customisés est le DataManipulator. Pour l’implémenter, vous devez d’abord décider de si vous voulez créer une API séparée pour vos données personnalisées. De manière générale, il est préférable de séparer l’API de l’implémentation (comme SpongeAPI le fait), mais il ne sera pas visible par les autres développeurs donc vous pouvez simplement mettre les deux dans la même classe.

Vous voudrez définir une méthode d’API pour chaque « unité » de vos données, comme les String, int, ItemStack ou un type personnalisé comme Home. Ces unités seront encapsulées dans une Value, ce qui lui permettra d’être accédé avec des Keys. Il existe diverses extensions de Value selon quel objet est représenté, tel que MapValue qui fournit les opérations standards de maps, ou BoundedComparableValue qui peut définir la limite la plus haute et la plus basse d’objets Comparable tels que des entiers.

Maintenant, choisissez quel type d”AbstractData vous allez hériter. Alors que vous pourriez l’implémenter à partide zéro, ces types abstraits enlèvent beaucoup de travail qui est nécessaire pour faire l’implémentation des méthodes requises. Une liste complète peut être trouvé dans org.spongepowered.api.data.manipulator.mutable.common. Voir Types Uniques ou Types Composés ci-dessous pour les détailes d’implémentation de chaque type.

Vous devrez créer deux classes différentes - une qui est mutable et qui implémente DataManipulator et votre type abstrait, et une version immuable qui implémente ImmutableDataManipulator et votre type abstrait immuable.

Note

Toutes les données doivent avoir des versions mutables et immuables, vous devez implémenter les deux.

Pour tous les types, vous devrez définir les méthodes DataManipulator#asImmutable() / asMutable() - c’est aussi simple que de copier les objets existants dans un constructeur pour la version alternative.

Valeurs

Votre/vos getter(s) ont besoin de retourner une valeur. Dans l’exemple ci-dessous, nous récupérer la ValueFactory. Cela nous permet d’économiser beaucoup de type en utiliser les objets Value déjà implémentés par Sponge. Selon la valeur que vous créez il y a différentes méthodes à appeler comme createMapValue, createBoundedComparableValue, etc.

Exemple de Code : Implémenter un getter de valeur

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

Notez qu’un ImmutableDataManipulator retourne une ImmutableValue, en appelant asImmutable() sur la Value retournée. Nous recommandons que vous mettez en cache cela (comme avec une variable de classe) dans la version immuable.

Chaque Value a aussi besoin d’une Key pour l’identifier, comme vu dans l’exemple avec Keys.DEFAULT_HOME. Similairement aux valeurs, vous utilisez une des méthodes makeXKey() dans le KeyFactory pour créer une Key pour votre valeur.

Vous avez besoin de passer un TypeToken représentant le type brut de votre valeur, et un TypeToken représentant la Value. Vous aurez également besoin de fournir un chemin de DataQuery - c’est le plus souvent utilisé pour sérialisé la Value. Comme avec n’importe quel type de catalogue, vous devez aussi fournir un ID unique et un nom. Mettez tout cela ensemble et vous avez une Key que vous pouvez utiliser dans vos Values.

Exemple de Code : Créer une 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");

Note

Les TypeTokens sont utilisés par l’implémentation du serveur pour préserver le type générique de vos valeurs. Ils sont créés dans l’une des deux façons suivantes :

  • Pour les types non génériques, utiliez TypeToken.of(MyType.class)

  • Pour les types génériques, créez une classe anonyme avec TypeToken<MyGenericType<String>>() {}

Sérialisation

Pour rendre vos données sérialisables pour les DataHolders ou les fichiers de configuration, vous devez aussi implémenter DataSerializable#toContainer(). Nous recommandons d’appeler super.toContainer() puisqu’il inclue la version depuis le DataSerializable#getContentVersion(). Vous devez augmenter la version à chaque fois qu’un changement est fait au format de vos données sérialisées, et utiliser les Les DataContentUpdaters pour permettre la rétrocompatibilité.

Note

Ce n’est pas requis pour les types uniques simples, comme il implémente déjà toContainer()

Exemple de Code : Implémenter 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;
}

Types Uniques

Les types uniques nécessitent peu d’implémentation puisqu’une grande partie du travail a déjà été faite dans le type AbstractSingleData que vous héritez.

Les types abstraits « simples » sont les plus faciles à implémenter, mais sont limités aux types ci-dessous :

  • Boolean

  • Comparable

  • Integer

  • List

  • Map

  • CatalogType

  • Enum

Pour tous les autres types vous devez implémenter un type unique customisé en héritant AbstractSingleData. Cela vous permet de définir vos propres données uniques avec les types que vous souhaitez, tout en faisant toujours l’essentials du travail pour vous.

Astuce

Les implémentations abstraites enregistrent l’objet pour vous dans le constructeur. Vous pouvez y accéder dans votre implémentation en appelant les méthodes getValue() et getValueGetter().

Types Uniques Simples

Presque tout le travail est fait pour vous avec les types abstraits simples. Tout ce que vous devez faire est :

  • Héritez le type abstrait pertinent

  • passez la Key pour vos données, l’objet lui-même, et l’objet par défaut (si l’objet est null) dans le constructeur

AbstractBoundedComparableData (et l’équivalent immuable) exige également des valeurs minimum et maximum qui seront vérifies, ainsi qu’un Comparator.

Note

Les types uniques List et Mapped doivent plutôt implémenter ListData / MappedData (ou l’équivalent immuable). Cela rajoute des méthodes additionnelles pour permettre des comportements similaires à ceux des Maps/Lists directement sur le DataManipulator.

Les 3 méthodes suivantes doivent être définies sur les manipulators mutables :

fill(DataHolder, MergeFunction) devrait remplacer les données de votre objet avec celles du DataHolder donné, en utilisant le résultat de MergeFunction#merge().

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) devrait remplacer sa valeur avec celle dans le conteneur et se retourner, sinon retourner 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() devrait, comme son nom l’indique, retourner une copie de lui-même avec les mêmes données.

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

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

Types Uniques Customisés

En plus du , vous devrez override les méthodes suivantes :

getValueGetter() devrait passer la Value représentant vos donnes (voir ci-dessus).

toContainer() devrait retourner un DataContainer représentant vos données (voir ci-dessus).

Types Composés

En considérant que les types uniques ne supportent qu’une seule valeur, les types « composés » supportent autant de valeurs que vous voulez. C’est utile lorsque plusieurs objets sont groupés, comme le FurnaceData. L’inconviénient, cependant, est qu’ils sont plus complexes à implémenter.

Pour commencer, créez tous les getters de Value que vos données vont avoir. Pour chaque valeur, créez une méthode pour obtenir et définir l’objet brut, que vous utiliserez plus tard. Pour les données immuables, seulement les getters sont nécessaires.

Enregistrer les Valeurs

Ensuite, vous voudrez les enregistrer afin que le système basé sur les Keys puisse les référencer. Pour faire ceci, implémentez soit DataManipulator#registerGettersAndSetters(), soit ImmutableDataManipulator#registerGetters() selon si les données sont mutables ou non.

Pour chaque valeur vous devez appeler :

  • registerKeyValue(Key, Supplier) référançant le getter de Value pour la clé donnée

  • registerFieldGetter(Key, Supplier) référançant le getter pour l’objet brut définit plus haut

  • registerFieldSetter(Key, Consumer) référançant le setter ci-dessus si vous implémentez la version mutable

Nous recommandez d’utilisez la syntax :: de Java 8 pour des fonctions Supplier et Consumer simples.

Exemple de Code : Implémenter des Getters et des 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) et from(DataContainer) sont semblables à des implémentations pour des données uniques, mais en chargeant toutes nos valeurs.