Nesneleri Serileştirme

Uyarı

These docs were written for SpongeAPI 7 and are likely out of date. If you feel like you can help update them, please submit a PR!

The Configurate library also provides the means to tweak automatic serialization and deserialization of objects. Per default, a set of data types can be (de)serialized:

  • Strings, the most commonly used primitive types and their wrappers

  • Lists and Sets of serializable values (not including specific implementations)

  • Maps where both keys and values are serializable (not including specific implementations)

  • The types UUID, URL, URI and (regex) Pattern

  • Any enum or CatalogType (FIXME)

  • The text type Component (See also here)

Not

If you need special constraints or rules for your serialization (such as sorting the elements in a Set), then you should consider using your own TypeSerializer implementations.

But if you want to write your custom data structures to a config file, this will not be enough.

Bir oyuncunun kaç elmas kazandığını izleyen bir veri yapısı düşünün. Biraz benzeyebilir:

public class DiamondCounter {
    private UUID playerUUID;
    private int diamonds;

    [...]
}

Ayrıca bu alanlara erişmek için bazı yöntemler varsayalım, bu vb her ikisi de güzel bir yapıcı ayarı.

Creating a Custom TypeSerializer

Bu tür bir veri yapısı yazma ve yükleme çok basit, özel bir :javadoc:`TypeSerializer`sağlar. ``TypeSerializer`` arabirimi, verileri bir nesneden bir yapılandırma düğümüne yazmak için iki yöntem ve belirli bir yapılandırma düğümünden bir nesne oluşturmak için arabirim sağlar.

import com.google.common.reflect.TypeToken;
import org.spongepowered.configurate.objectmapping.ObjectMappingException;
import org.spongepowered.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());
    }
}

Daha sonra bu``TypeSerializer`` yapılandırılarak kaydedilir. Bu genellikle, varsayılan TypeSerializerCollection veya yerel olarak, yapılandırmanızı yüklerken ConfigurationOptions içinde belirtilerek yapılabilir.

Not

ConfigurationOptions are immutable. Every time you try to modify the original instance a new instance is created; so you either have to use the (chained) result directly or update your variable accordingly.

** Kod örneği: Bir TypeSerializer’ın genel olarak kaydedilmesi **

import org.spongepowered.configurate.objectmapping.serialize.TypeSerializers;

TypeSerializers.getDefaultSerializers().registerType(TypeToken.of(DiamondCounter.class), new DiamondCounterSerializer());

** Kod örneği: Bir TypeSerializer’ın yerel olarak kaydedilmesi **

import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.ConfigurationOptions;
import org.spongepowered.configurate.objectmapping.serialize.TypeSerializerCollection;
import org.spongepowered.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);

Uyarı

Eğer kendi eklentiniz tarafından kullanılmayan türler için özel bir TypeSerializer sağladıysanız, TypeSerializer’ın üzerine yazılmasından kaynaklanan diğer eklentiler veya Sponge ile uyumlu olmayanlar eklentiler için yerel olarak kaydetmelisiniz.

Tüyo

If you need the TypeToken.of(DiamondCounter.class) in multiple places, then you should consider creating a constant for it. You can do it in a similar fashion as Sponge does in the TypeTokens class, or just define the constant inside of your data or serializer class.

ObjectMappers kullanımı

Çoğu durumda serileştirme, harita alanlarını yapılandırma düğümlerine çevirir, böyle bir TypeSerializer yazmak oldukça sıkıcı bir durum ve yapılandırmada kendi başına yapılmasını istediğimiz bir durumdur. Bunu :javadoc: ConfigSerializable ve :javadoc:` Setting` bölümlerinde açıklayalım.

import org.spongepowered.configurate.objectmapping.Setting;
import org.spongepowered.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;

    [...]
}

Yukarıdaki örnek şimdi daha fazla kayıt olmadan yapılandırma düğümlerinden seri hale getirilebilir. @Setting açıklamaları, bir biçim düğümünü açıklama yapılan alanla eşleştirir. İki isteğe bağlı parametre, value ve comment kabul eder. Eğer value parametresi mevcutsa, alanın kaydedileceği düğümün adını tanımlar. Eğer mevcut değilse alanın adı kullanılır. Yukarıdaki örnekte, açıklama, playerUUID alanının içeriğinin “playerUUID” ile yorumlandığı ‘’player’’ düğümüne kaydedilmesini sağlar. Bununla birlikte, diamonds alanı, tam açıklamanın sadece bir yorum belirttiği için bu tam ad altında kaydedilecektir. Uygulama, yorumlanan yapılandırma düğümlerini destekliyorsa, bu yorum yapılandırmaya yazılır, yoksa atılır.

Tüyo

@Setting(value ="someNode") yerine @Setting("someNode") kısaltmasını da kullanabilirsiniz

@ConfigSerializable annotation’ı class için Configurate’in ObjectMapper oluşturmasına olanak sağladığından dolayı başka kayıtlara muhtaç kalınmasını engeller. Tek sınırlama Configurate’in belirlenmiş yerleri doldurabilmek için boş bir constructor’a ihtiyaç duymasıdır.

Not

You can also have fields that are not are not annotated with @Setting in your @ConfigSerializable classes. These fields won’t be persisted to config files and can be used to store temporary references for your plugin.

Using Default Values in ConfigSerializable Types

It is also possible to use default values inside of @ConfigSerializable types. You just have to use Java’s field initializers (or getters) to set some default values. As long as the entry is not present in the config file the value won’t be overwritten.

@ConfigSerializable
public class DiamondCounter {

    @Setting(value="player", comment="Player UUID")
    private UUID playerUUID;

    @Setting(comment="Number of diamonds mined")
    private int diamonds = 0;

    @Setting(comment="The time the player found a diamond last.")
    private LocalDateTime diamonds = LocalDateTime.now();

    [...]
}

Example: Loading a ConfigSerializable Config with Default Values

Instead of loading a default config from the plugin jar itself, it is also possible to just ask Configurate to create it if it is missing.

try {
    this.config = this.configManager.load().<Configuration>getValue(Configuration.TYPE, Configuration::generateDefault);
} catch (ObjectMappingException | IOException e) {
    this.logger.error("Failed to load the config - Using a default", e);
    this.config = Configuration.generateErrorDefault();
}

In this case you load the entire configuration into a Configuration object that contains all of your plugins configuration. Using such a class has the following benefits:

  • Type safety is guaranteed

  • No need to update the configuration file shipped in your plugin

  • You don’t need to store lots of references for each of your configuration options

  • You can pass this config (or its parts) into methods or reference it from other classes

  • It is easy to write comments for each attribute in a place that also helps you during development

Not

In this case Configuration.generateDefault() is called when the config file is missing or empty. If you still want to load the shipped default config asset you can load it inside of that method. Configuration.generateErrorDefault() is called when there is an error reading or parsing the config. It is not necessary to use separate methods for those cases; you can also use the no-arg constructor, or use an entirely custom solution.

Example: Saving a ConfigSerializable Config

Saving a @ConfigSerializable config is also very simple, as shown by the following example:

try {
    this.configManager.save(this.configManager.createEmptyNode().setValue(Configuration.TYPE, this.config));
} catch (IOException | ObjectMappingException e) {
    this.logger.error("Failed to save the config", e);
}

Özel bir ObjectMapperFactory sağlanıyor

Kısıtlama, farklı bir ObjectMapperFactory kullandığımızda, örneğin GuiceObjectMapperFactory, ortadan kaldırılabilir. Bu durumda boş bir constuctor’a ihtiyaç duyulması yerine guice’un dependency injenction ile oluşturabileceği tüm class’larda çalışır. Bu ayrıca @Inject ve @Setting gibi belirlenmiş yerlerin karışımına da olanak sağlar.

Eklentiniz, dependency injection (bkz Bağımlılık Enjeksiyonu) kullanıp bunu ConfigurationOptions``a okutarak``GuiceObjectMapperFactory kullanabilir.

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 org.spongepowered.configurate.commented.CommentedConfigurationNode;
import org.spongepowered.configurate.loader.ConfigurationLoader;
import org.spongepowered.configurate.objectmapping.GuiceObjectMapperFactory;

@Plugin(name="IStoleThisFromZml", id="shamelesslystolen", version="0.8.15", description = "Stolen")
public class StolenCodeExample {

    @Inject private GuiceObjectMapperFactory factory;
    @Inject private ConfigurationLoader<CommentedConfigurationNode> loader;

    @Listener
    public void enable(GamePreInitializationEvent event) throws IOException, ObjectMappingException {
        CommentedConfigurationNode node =
          loader.load(ConfigurationOptions.defaults().setObjectMapperFactory(factory));
        DiamondCounter myDiamonds = node.getValue(TypeToken.of(DiamondCounter.class));
    }
}

Not

Yukarıda ki kod bir örnektir ve özlük için uygun bir istisna işlemesinden yoksundur.