ManipuladoresDeDatos Personalizados

La parte principal de los datos personalizados es DataManipulator. Para implementarlo, primero debes decidir si quieres crear una API separada para tus datos personalizados. Generalmente hablando es mejor separar la API de la implementación (como lo hace la API de Sponge), pero si no va a ser vista por otros desabolladores entonces puedes simplemente poner ambas en la misma clase.

Querrás definir un método API para cada «unidad» de tus datos, como un String, int, ItemStack o un tipo personalizado como Home. Estas unidades seran envueltas en un Value, lo que permitirá que se puedan acceder con Keys. Hay varias extensiones de Value dependiendo de que objeto será representado, como MapValue que provee el mapa estándar de operaciones, o BoundedValue que puede establecer limites superiores o inferiores del valor (como enteros). Los limites de los valores son verificados usando un Comparator.

Ahora, elige de cual de los tipos de AbstractData extenderás. Aunque podrias implementar desde cero, estos tipos de abstracto quitan mucho del trabajo que necesita hacerse implementando los métodos requeridos. Una lista completa puede ser encontrada en org.spongepowered.api.data.manipulator.mutable.common. Ve también Tipos Únicos or Tipos Compuestos a continuación para detalles de implementación de cada tipo.

Debes crear dos clases diferentes - una que es alterable e implementa DataManipulator y tu tipo de abstracto, y una versión inalterable que implementa ImmutableDataManipulator y tu tipo de abstracto inmutable.

Nota

Todos los datos deben tener versiones mutables e inmutables, debes implementar ambas.

Para todos los tipos, necesitaras definir los metodos DataManipulator#asImmutable()/ asMutable() - esto es tan simple como copiar los objetos existentes a constructores para las versiones alternativas.

Valores

Tu(s) getter(s) de valor deben devolver un valor. En el ejemplo a continuación, obtenemos el ValueFactory. Esto nos ahorra un montón de tipeo al usar los objetos Value ya implementados de Sponge. Dependiendo de que valor estes creando hay diferentes metodos que llamar tales como createMapValue, createBoundedComparableValue, etc.

Ejemplo de Código: Implementando un Getter de Valor

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

Nota que un ImmutableDataManipulator devolvería en su lugar un ImmutableValue, al llamar asImmutable() en el Value devuelto. Recomendamos que cachees esto (al igual que con una campo de clase) en la versión inmutable.

Cada Value también necesita un Key para identificarlo, visto en el siguiente ejemplo como Keys.DEFAULT_HOME. Similar a los valores, usas uno de los métodos makeXKey() en KeyFactory para crear un Key para tu valor.

Necesitar pasar un TypeToken representando el tipo puro de tu valor, y un TypeToken representando el Value. También necesitas proporcionar una ruta DataQuery - esto es mas comúnmente usado para serializar el Value. Al igual que con cualquier tipo de catalogo debes también proporcionar un ID y nombre únicos. Reúne todo esto y tendrás un Key que pudes usar en tus Values.

Ejemplo de Código: Creando una Clave

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

Nota

Los TypeTokens son usados por la implementación para conservar el tipo genérico de tus valores. Sponge proporciona una larga lista de símbolos pre-construidos para la API en TypeTokens.

Si necesitas crear el tuyo propio, puedes hacerlo de dos maneras:

  • Para tipos no genéricos, utiliza TypeToken.of(MyType.class)

  • Para tipos genéricos, crea una clase anónima con TypeToken<MyGenericType<String>>() {}

Serialización

Para hacer sus datos serializable <../serialization>`a :javadoc:`DataHolders o archivos de configuración, Ud debería también implementar DataSerializable#toContainer().Nosotros recomendamos llamar a``super.toContainer()``ya que este incluirá la versión de DataSerializable#getContentVersion(). Usted debería incrementar la versión cada vez que se hace un cambio al formato de los datos serializados, y usar :ref:`content-updaters`para permitir la compatibilidad con las versiones anteriores.

Nota

Esto no es requerido para tipos únicos simples, ya que ya implementan toContainer()

Ejemplo de Código: Implementando 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;
}

Registro

Registrando tu DataManipulator permite que sea accesible por Sponge y por otras extensiones en una forma generica. El juego/extensión puede crear copias de tus datos y serializar/deserializar tus datos sin referenciar ninguna de tus clases directamente.

Para registrar un DataManipulator Sponge tiene la guía DataRegistration#builder(). Esto construirá un DataRegistration y lo registrará automáticamente.

Nota

Debido a la naturaleza de los datos, debs registrar tu``DataManipulator`` durante la inicialización - generalmente escuchando a :javadoc: “GameInitializationEvent” tal como en el ejemplo siguiente. Si intentas registrar un DataManipulator una vez completada la inicialización se producirá una excepción.

import org.spongepowered.api.event.game.state.GameInitializationEvent;
import org.spongepowered.api.data.DataRegistration;

import org.example.MyCustomData;
import org.example.ImmutableCustomData;
import org.example.CustomDataBuilder;

@Listener
public void onInit(GameInitializationEvent event) {
  DataRegistration.builder()
      .dataClass(MyCustomData.class)
      .immutableClass(ImmutableCustomData.class)
      .builder(new CustomDataBuilder())
      .manipulatorId("my-custom")
      .dataName("My Custom")
      .buildAndRegister(myPluginContainer);
}

Advertencia

Los datos que fueron serializados antes de 6.0.0, o los datos en los que has cambiado el ID, no serán reconocidos a menos que sean registrados con DataManager#registerLegacyManipulatorIds(String, DataRegistration). Si se registra un DataManipulator pre-6.0.0 el ID es tomado de Class.getName(), al igual que con com.example.MyCustomData.

Tipos Únicos

Los tipos únicos requieren poca implementación por que mucho del trabajo ya se ha hecho en el tipo AbstractSingleData del cual extendiste.

Los tipos de abstractos «simples» son los mas fáciles de implementas, pero se limitan solo a los siguientes tipos:

  • Boolean

  • Comparable

  • Integer

  • List

  • Map

  • CatalogType

  • Enum

Para todos los otros tipos debes implementar un tipo único personalizado extendiendo AbstractSingleData. Esto te permite definir tus propios datos con cualquier tipo que quieras, mientras sigue haciendo la mayor parte del trabajo por ti.

Truco

Las implementaciones abstractas guardan el objeto para ti en el constructor. Puedes acceder a él en su ejecución llamando a los métodos getValue() and getValueGetter().

Tipos Únicos Simples

Casi todo el trabajo es hecho por ti con tipos abstractos simples. Todo lo que necesitas hacer es:

  • Extiende el tipo abstracto relevante

  • pasa la Key para tus datos, el objeto en si mismo, y el objeto por defecto (si el objeto es nulo) en el constructor

AbstractBoundedComparableData (y el equivalente inmutable) adicionalmente requiere valores mínimos y máximos que serán comprobados, al igual que un Comparator.

Nota

Los tipos simples List y Mapped deben implementar en su lugar ListData / MappedData``(o el equivalente inmutable). Esto añade métodos adicionales para permitir el comportamiento similar a Mapa/Lista directamente en el ``DataManipulator.

Los 3 metodos siguientes deben ser definidos en manipuladores mutables:

fill(DataHolder, MergeFunction) deberia remplazar los datos en tu objeto con ese del DataHolder dado, usando el resultado 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) debería sobrescribir su valor con el del contenedor y devolverse a si mismo, de otra forma devolver 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() deberia, como el nombre sugiere, devolver una copia de si mismo con los mismos datos.

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

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

Tipos Unicos Personalizados

Ademas, debes remplazar los siguientes métodos:

getValueGetter() deberia pasar el Value representando tus datos (ver arriba).

toContainer() deberia devolver un DataContainer representando tus datos (see above).

Tipos Compuestos

Mientras que los tipos únicos admiten sólo un valor, los tipos «compuesto» soportan cuantos valores quieras. Esto es útil cuando se agrupan varios objetos, tales como :javadoc: “FurnaceData”. El lado negativo, sin embargo, es que son más complejos de implementar.

Para comenzar, cree todos los getters “Valor” que tendrá la información. Para cada valor, cree un método para acceder y configurar el objeto en crudo, el cual será usado luego. Para los datos que sean invariables, solamente se necesitarán los getters.

Registrando Valores

Luego, querrás registrarlos para que el sistema basado en Keys pueda hacer referencia a ellos. Para hacer esto, implementa DataManipulator#registerGettersAndSetters() o ImmutableDataManipulator#registerGetters() dependiendo de si los datos son mutables o no.

Para cada valor debes llamar:

  • registerKeyValue(Key, Supplier) referenciando el getter de ``Value` para la clave dada

  • registerFieldGetter(Key, Supplier) haciendo referencia al método getter para el objeto sin procesar definido anteriormente

  • registerFieldSetter(Key, Consumer) haciendo referencia al método setter anterior si está implementando la versión mutable

Nosotros recomendamos usar las funciones Java 8´s :: syntax for easy Supplier y Consumer.

Ejemplo de Código: Implementando Getters y 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) y from(DataContainer) son similares a las implementaciones para datos individuales, pero cargando todos tu valores.