Serializando Objetos
La libreria Configurate también proporciona los medios para cambiar a serialización y deserialización automática de objetos. Por defecto, un conjunto de tipos de datos puede ser (de)serializado: entre otras cadenas, enteros, dobles, UUIDs, listas (de valores serializables) y mapas (donde las claves y valores son serializables). Pero si quieres escribir tus estructuras de datos personalizadas en un archivo de configuración, esto no será suficiente.
Imagina una estructura de datos rastreando cuantos diamantes ha minado un jugador. Se puede ver un poco como esto:
public class DiamondCounter {
private UUID playerUUID;
private int diamonds;
...
}
También adopta algunos métodos para acceder a esos archivos, un buen constructor configurándolos a ambos, etc.
Creando un TypeSerializer personalizado
Una manera muy sencilla de escribir y cargar una estructura de datos es proporcionando un :javadoc: “TypeSerializer” personalizado. La interfaz de ``TypeSerializer` proporciona dos métodos, uno para escribir los datos de un objeto en un nodo de configuración y otro para crear un objeto desde un nodo de configuración dado.
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());
}
}
Este ``TypeSerializer` debe ser registrado con Configurate. Esto puede hacerse ya sea a nivel global, registrando el TypeSerializerCollection por defecto o localmente, especificandolo en el ConfigurationOptions al cargar tu configuración.
Ejemplo de Código: Registrando un TypeSerializer globalmente
import ninja.leaping.configurate.objectmapping.serialize.TypeSerializers;
TypeSerializers.getDefaultSerializers().registerType(TypeToken.of(DiamondCounter.class), new DiamondCounterSerializer());
Ejemplo de Codigo: Registrando un TypeSerializer localmente
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);
Advertencia
Si proporcionas un TypeSerializer
personalizado para tipos que no son introducidos por la propia extension, solo deberías registrarlos localmente para evitar conflictos con otros extensiones o Sponge, causados por un TypeSerializer
siendo sobrescrito.
Utilizando ObjectMappers
Ya que en muchos casos la (de)serialización se limita a asignar campos a los nodos de configuración, escribiendo algo como un TypeSerializer
es un asunto más bien aburrido que nos gustaría que Configurate lo haga por su propia cuenta. Así que anotemos nuestra clase con las anotaciones :javadoc: “ConfigSerializable” y :javadoc: “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;
...
}
El ejemplo anterior ahora puede ser serializado y deserializado desde los nodos de configuración sin mayor registro. Las anotaciones``@Setting`` mapean un nodo de configuración en el campo que fue anotado. Acepta dos parámetros opcionales, value
y``comment``. Si existe el parámetro value
, define el nombre del nodo que se guardará en el campo. Si no está presente, se utilizará en su lugar el nombre del campo. Así en nuestro ejemplo anterior, la anotación asegura que el contenido del campo playerUUID
” se guarda en el nodo «player», comentado con «Player UUID». El campo diamonds
sin embargo se guardará bajo ese nombre exacto ya que su anotación sólo especifica un comentario. Ese comentario se escribirá en la configuración si la aplicación es compatible con los nodos de configuración comentados, de lo contrario se descartará.
Truco
También puedes usar la abreviación @Setting("someNode")
en lugar de @Setting(value="someNode")
La anotación @ConfigSerializable
elimina la necesidad de cualquier registro, ya que le permite a Configurate generar solo un ObjectMapper para la clase. La única limitación es que Configurate necesita un constructor vacío para instanciar un nuevo objeto antes de llenar los campos anotados.
Proporcionando un ObjectMapperFactory personalizado
Esa restricción, sin embargo, puede ser superada si usamos un ObjectMapperFactory diferente, por ejemplo un GuiceObjectMapperFactory. En vez de requerir un constructor vació, trabajará en cualquier clase que Guice pueda crear mediante una inyección de dependencia. Esto también permite una mezcla de los campos anotados @Inject
y @Setting
.
Su plug-in puede adquirir sólo un GuiceObjectMapperFactory
simplemente por inyección de dependencia (consulte Inyección de Dependencia) y luego pasarlo a 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));
}
}
Nota
El código que me muestra más arriba es un ejemplo, y por su brevedad, no contiene un manejo apropiado de las excepciones.