Sérialisation d’objets
La librairie Configurate fournit aussi les moyens d’alérer la sérialisation et la désérialisation automatique des objets. Par défaut, un ensemble de types de données peut être (dé)sérialisé : entre autres les Strings, ints, doubles, UUIDs, Lists (de valeurs sérialisables) et les Maps (où les clés et les valeurs sont sérialisables). Mais si vous voulez écrire vos structures de données personnalisés dans un fichier de configuration, ça ne sera pas suffisant.
Imaginez une structure de données suivant le nombre de diamants que le joueur a miné. Ça pourrait ressembler à ceci :
public class DiamondCounter {
private UUID playerUUID;
private int diamonds;
...
}
Mettez également quelques méthodes pour accéder à ces variables, un joli constructeur les définissant, etc.
Créer un TypeSerializer customisé
Un moyen très simple d’écrire et de charger une telle structure de données est de fournir un TypeSerializer cusomisé. L’interface TypeSerializer
fournit deux méthodes, une pour écrire les données depuis un objet dans une node de configuration, et une pour créer un objet depuis une node de configuration donnée.
import com.google.common.reflect.TypeToken;
import ninja.leaping.configurate.objectmapping.ObjectMappingException;
import ninja.leaping.configurate.objectmapping.serialize.TypeSerializer;
public class DiamondCounterSerializer implements TypeSerializer<DiamondCounter> {
@Override
public DiamondCounter deserialize(TypeToken<?> type, ConfigurationNode value)
throws ObjectMappingException {
UUID player = value.getNode("player").getValue(TypeToken.of(UUID.class));
int diamonds = value.getNode("diamonds").getInt();
return new DiamondCounter(player, diamonds);
}
@Override
public void serialize(TypeToken<?> type, DiamondCounter obj, ConfigurationNode value)
throws ObjectMappingException {
value.getNode("player").setValue(obj.getPlayerUUID());
value.getNode("diamonds").setValue(obj.getDiamonds());
}
}
Ce TypeSerializer
doit ensuite être enregistré avec Configurate. C’est possible de le faire soit globalement, en l’enregistrant au TypeSerializerCollection par défaut, soit localement, en le spécifiant dans le ConfigurationOptions au chargement de la configuration.
Exemple de Code : Enregistrer un TypeSerializer globalement
import ninja.leaping.configurate.objectmapping.serialize.TypeSerializers;
TypeSerializers.getDefaultSerializers().registerType(TypeToken.of(DiamondCounter.class), new DiamondCounterSerializer());
Exemple de Code : Enregistrer un TypeSerializer localement
import ninja.leaping.configurate.ConfigurationNode;
import ninja.leaping.configurate.ConfigurationOptions;
import ninja.leaping.configurate.objectmapping.serialize.TypeSerializerCollection;
import ninja.leaping.configurate.objectmapping.serialize.TypeSerializers;
TypeSerializerCollection serializers = TypeSerializers.getDefaultSerializers().newChild();
serializers.registerType(TypeToken.of(DiamondCounter.class), new DiamondCounterSerializer());
ConfigurationOptions options = ConfigurationOptions.defaults().setSerializers(serializers);
ConfigurationNode rootNode = someConfigurationLoader.load(options);
Avertissement
Si vous fournissez un TypeSerializer
customisé pour les types qui ne sont pas introduits par votre propre plugin, vous devriez jamais les enregistrer localement afin d’éviter les conflits avec les autres plugins ou Sponge, causé par un TypeSerializer
se faisant écraser.
Utiliser les ObjectMappers
Étant donné que dans de nombreux cas, la (dé)sérialisation se résume au mappage de variables aux nodes de configuration, écrire un tel TypeSerializer
est une affaire assez ennuyeuse et c’est quelque chose que nous aimerions que Configurate fasse lui-même. Donc annotons notre classe avec les annotations ConfigSerializable et Setting.
import ninja.leaping.configurate.objectmapping.Setting;
import ninja.leaping.configurate.objectmapping.serialize.ConfigSerializable;
@ConfigSerializable
public class DiamondCounter {
@Setting(value="player", comment="Player UUID")
private UUID playerUUID;
@Setting(comment="Number of diamonds mined")
private int diamonds;
...
}
L’exemple ci-dessus peut maintenant être sérialisé et désérialisé depuis les nodes de configuration sans plus d’enregistrement. L’annotation @Setting
map une node de configuration à la variable qui a été annotée. Il accepte deux paramètres optionnels, value
et comment
. Si le paramètre value
existe, il définit le nom de la node dans laquelle la variable sera sauvegardée. Si il n’est pas présent, le nom de la variable sera utilisé à la place. Donc dans notre exemple ci-dessus, l’annotation garantit que le contenu de la variable playerUUID
est sauvegardé à la node « player », commentée avec « Player UUID ». La variable diamonds
sera sauvegardé sous ce nom exact puisque l’annotation spécifie seulement un commentaire. Ce commentaire sera écrit dans la config si l’implémentation supporte les nodes de configuration commentées, sinon il sera rejeté.
Astuce
Vous pouvez aussi utiliser le raccourci @Setting("someNode")
au lieu de @Setting(value="someNode")
L’annotation @ConfigSerializable
élimine le besoin de faire des enregistrements puisqu’il permet à Configurate de seulement génrer un ObjectMapper pour la classe. La seule restriction est que Configurate a besoin d’un constructeur vide pour instancier un nouvel objet avant de le remplir par les variables annotées.
Fournir un ObjectMapperFactory customisé
Cette restriction, toutefois, peut être levée si nous utilisons un autre ObjectMapperFactory, par exemple un GuiceObjectMapperFactory. Au lieu d’avoir besoin d’un constructeur vide, il va fonctionner sur n’aimporte quel classe que guice peut créer via l’injection de dépendances. Cela permet également un mélange des variables annotées @Inject
et @Setting
.
Votre plugin peut acquérir un GuiceObjectMapperFactor
simplement par l’injection de dépendances (voir Injection de Dépendances) et le passer ensuite au ConfigurationOptions
.
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.game.state.GamePreInitializationEvent;
import org.spongepowered.api.plugin.Plugin;
import com.google.inject.Inject;
import ninja.leaping.configurate.commented.CommentedConfigurationNode;
import ninja.leaping.configurate.loader.ConfigurationLoader;
import ninja.leaping.configurate.objectmapping.GuiceObjectMapperFactory;
@Plugin(name="IStoleThisFromZml", id="shamelesslystolen", version="0.8.15")
public class StolenCodeExample {
@Inject private GuiceObjectMapperFactory factory;
@Inject private ConfigurationLoader<CommentedConfigurationNode> loader;
@Listener
public void enable(GamePreInitializationEvent event) {
CommentedConfigurationNode node =
loader.load(ConfigurationOptions.defaults().setObjectMapperFactory(factory));
DiamondCounter myDiamonds = node.getValue(TypeToken.of(DiamondCounter.class));
}
}
Note
Le code ci-dessus est un exemple et, par souci de brièveté, ne gère pas correctement les exceptions.