org.diorite.config.serialization.Serialization.java Source code

Java tutorial

Introduction

Here is the source code for org.diorite.config.serialization.Serialization.java

Source

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2016. Diorite (by Bartomiej Mazur (aka GotoFinal))
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package org.diorite.config.serialization;

import javax.annotation.Nullable;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.Writer;
import java.lang.invoke.MethodHandle;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonIOException;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;

import org.yaml.snakeyaml.DumperOptions.FlowStyle;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.Tag;
import org.yaml.snakeyaml.resolver.Resolver;

import org.diorite.commons.reflections.ConstructorInvoker;
import org.diorite.commons.reflections.DioriteReflectionUtils;
import org.diorite.commons.reflections.MethodInvoker;
import org.diorite.commons.reflections.ReflectMethod;
import org.diorite.commons.threads.DioriteThreadUtils;
import org.diorite.config.Config;
import org.diorite.config.ConfigTemplate;
import org.diorite.config.annotations.DelegateSerializable;
import org.diorite.config.annotations.SerializableAs;
import org.diorite.config.annotations.StringSerializable;
import org.diorite.config.serialization.comments.CommentsManager;
import org.diorite.config.serialization.comments.DocumentComments;
import org.diorite.config.serialization.snakeyaml.DumperOptions;
import org.diorite.config.serialization.snakeyaml.Representer;
import org.diorite.config.serialization.snakeyaml.Yaml;
import org.diorite.config.serialization.snakeyaml.YamlConstructor;

/**
 * Serialization manager, it allows to serialize and deserialize all registered types. <br>
 * It is used to serialize and deserialize both json and yaml.
 */
public final class Serialization {
    private static final Serialization GLOBAL = new Serialization((Void) null);
    private static final int BEST_WIDTH = 180;

    // gson section
    private final GsonBuilder gsonBuilder = new GsonBuilder().setPrettyPrinting().serializeNulls()
            .serializeSpecialFloatingPointValues().enableComplexMapKeySerialization();
    private ThreadLocal<Gson> cachedGson = ThreadLocal.withInitial(this.gsonBuilder::create);

    // yaml section
    private Collection<Class<?>> yamlIgnoredClasses = new ConcurrentLinkedQueue<>();
    private Collection<BiFunction<YamlConstructor, Representer, YamlStringSerializerImpl<?>>> stringRepresenters = new ConcurrentLinkedQueue<>();
    private Collection<BiFunction<YamlConstructor, Representer, YamlSerializerImpl<?>>> objectRepresenters = new ConcurrentLinkedQueue<>();
    private ThreadLocal<Yaml> cachedYaml = ThreadLocal.withInitial(this::createYaml);
    private ThreadLocal<AtomicInteger> localCounter = ThreadLocal.withInitial(AtomicInteger::new);

    private final CommentsManager commentsManager = new CommentsManager();

    /**
     * Returns instance of comments manager.
     *
     * @return instance of comments manager.
     */
    public CommentsManager getCommentsManager() {
        return this.commentsManager;
    }

    private Yaml createYaml() {
        Representer representer = new Representer();
        YamlConstructor constructor = new YamlConstructor();

        // register types
        for (Class<?> ignoredClass : this.yamlIgnoredClasses) {
            representer.addClassTag(ignoredClass, Tag.MAP);
        }
        for (BiFunction<YamlConstructor, Representer, YamlStringSerializerImpl<?>> serializerCreator : this.stringRepresenters) {
            YamlStringSerializerImpl<?> yamlSerializer = serializerCreator.apply(constructor, representer);
            Class<?> type = yamlSerializer.getType();
            representer.addClassTag(type, Tag.MAP);
            representer.addRepresenter(type, yamlSerializer);
            constructor.addConstruct(type, yamlSerializer);
        }
        for (BiFunction<YamlConstructor, Representer, YamlSerializerImpl<?>> serializerCreator : this.objectRepresenters) {
            YamlSerializerImpl<?> yamlSerializer = serializerCreator.apply(constructor, representer);
            Class<?> type = yamlSerializer.getType();
            representer.addClassTag(type, Tag.MAP);
            representer.addRepresenter(type, yamlSerializer);
            constructor.addConstruct(type, yamlSerializer);
        }

        DumperOptions dumperOptions = new DumperOptions();
        dumperOptions.setAllowReadOnlyProperties(true);
        dumperOptions.setAllowUnicode(true);
        dumperOptions.setDefaultFlowStyle(FlowStyle.BLOCK);
        dumperOptions.setIndent(2);
        dumperOptions.setPrettyFlow(false);
        dumperOptions.setWidth(BEST_WIDTH);

        Resolver resolver = new Resolver();

        Yaml yaml = new Yaml(this, constructor, representer, dumperOptions, resolver);

        yaml.setName("DioriteYaml[" + this.localCounter.get().getAndIncrement() + "]:"
                + DioriteThreadUtils.getFullThreadName(Thread.currentThread(), true));
        return yaml;
    }

    private Gson gson() {
        return this.cachedGson.get();
    }

    private Yaml yaml() {
        return this.cachedYaml.get();
    }

    private final Map<Class<?>, StringSerializer<?>> stringSerializerMap = new ConcurrentHashMap<>(10);
    private final Map<Class<?>, Serializer<?>> serializerMap = new ConcurrentHashMap<>(10);

    private final Set<String> trueValues = new HashSet<>(
            Set.of("true", "enabled", "enable", "yes", "y", "e", "t", "on"));
    private final Set<String> falseValues = new HashSet<>(
            Set.of("false", "disabled", "disable", "no", "n", "d", "f", "off"));

    /**
     * Add string values that can be interpreted as {@link Boolean#TRUE} value when reading from config.
     *
     * @param strings
     *         string representations of {@link Boolean#TRUE} value.
     */
    public void addTrueValues(String... strings) {
        Collections.addAll(this.trueValues, strings);
    }

    /**
     * Add string values that can be interpreted as {@link Boolean#FALSE} value when reading from config.
     *
     * @param strings
     *         string representations of {@link Boolean#FALSE} value.
     */
    public void addFalseValues(String... strings) {
        Collections.addAll(this.falseValues, strings);
    }

    @Nullable
    Boolean toBool(String str) {
        if (this.trueValues.contains(str)) {
            return true;
        }
        if (this.falseValues.contains(str)) {
            return false;
        }
        return null;
    }

    public static Serialization getGlobal() {
        return GLOBAL;
    }

    private void refreshCache() {
        this.cachedGson = ThreadLocal.withInitial(this.gsonBuilder::create);
        this.cachedYaml = ThreadLocal.withInitial(this::createYaml);
    }

    /**
     * Remove all cached values.
     */
    public void cleanup() {
        this.refreshCache();
    }

    /**
     * Remove cached values for current thread.
     */
    public void cleanupThread() {
        this.cachedGson.remove();
        this.cachedYaml.remove();
    }

    private Serialization(@Nullable Void v) {
        this.registerStringSerializer(StringSerializer.of(UUID.class, UUID::toString, UUID::fromString));
    }

    /**
     * Register given string serializable class to this serialization manager.
     *
     * @param clazz
     *         serializable clazz to register.
     * @param <T>
     *         type of serializable class, must implement {@link org.diorite.config.serialization.StringSerializable} or contain {@link StringSerializable}
     *         annotations.
     *
     * @return old serializer if exists.
     */
    @Nullable
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public <T> StringSerializer<T> registerStringSerializable(Class<T> clazz) {
        Class target = getDelegatedLookupClass(clazz);
        if (clazz.isAnnotationPresent(StringSerializable.class)) {
            return this.registerStringSerializableByAnnotations(target);
        }
        return this.registerStringSerializableByType(target);
    }

    /**
     * Register given string serializer to this serialization manager.
     *
     * @param stringSerializer
     *         string serializer to register.
     * @param <T>
     *         serializer value type.
     */
    @SuppressWarnings("unchecked")
    public <T> void registerStringSerializer(StringSerializer<T> stringSerializer) {
        this.gsonBuilder.registerTypeAdapter(stringSerializer.getType(),
                new JsonStringSerializerImpl<>(stringSerializer));
        this.stringRepresenters.add((c, r) -> new YamlStringSerializerImpl<>(r, stringSerializer));
        this.refreshCache();
        this.stringSerializerMap.put(stringSerializer.getType(), stringSerializer);
    }

    /**
     * Register given serializable class to this serialization manager.
     *
     * @param clazz
     *         serializable clazz to register.
     * @param <T>
     *         type of serializable class, must implement {@link Serializable} or contain {@link org.diorite.config.annotations.Serializable} annotations.
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public <T> void registerSerializable(Class<T> clazz) {
        Class target = getDelegatedLookupClass(clazz);
        Serializer serializer;
        if (clazz.isAnnotationPresent(org.diorite.config.annotations.Serializable.class)) {
            serializer = this.registerSerializableByAnnotations(target);
        } else {
            serializer = this.registerSerializableByType(target);
        }
        if (clazz != target) {
            this.registerSerializer(
                    Serializer.of(clazz, serializer.getSerializerFunction(), serializer.getDeserializerFunction()));
        }
    }

    /**
     * Register given serializer to this serialization manager.
     *
     * @param serializer
     *         serializer to register.
     * @param <T>
     *         serializer value type.
     *
     * @return old serializer if exists.
     */
    @Nullable
    @SuppressWarnings("unchecked")
    public <T> Serializer<T> registerSerializer(Serializer<T> serializer) {
        this.gsonBuilder.registerTypeAdapter(serializer.getType(), new JsonSerializerImpl<>(serializer, this));
        this.objectRepresenters.add((c, r) -> new YamlSerializerImpl<>(r, c, serializer, this));
        this.refreshCache();
        return (Serializer<T>) this.serializerMap.put(serializer.getType(), serializer);
    }

    /**
     * Returns true if given object type is serializable to simple string.
     *
     * @param object
     *         object type to check.
     *
     * @return true if given object type is serializable to simple string.
     */
    public boolean isStringSerializable(Object object) {
        return this.isStringSerializable(object.getClass());
    }

    /**
     * Returns true if given type is serializable to simple string.
     *
     * @param clazz
     *         type to check.
     *
     * @return true if given type is serializable to simple string.
     */
    public boolean isStringSerializable(Class<?> clazz) {
        return this.stringSerializerMap.containsKey(clazz);
    }

    /**
     * Returns true if given object type is serializable.
     *
     * @param object
     *         object type to check.
     *
     * @return true if given object type is serializable.
     */
    public boolean isSerializable(Object object) {
        return this.isSerializable(object.getClass());
    }

    /**
     * Returns true if given type is serializable.
     *
     * @param clazz
     *         type to check.
     *
     * @return true if given type is serializable.
     */
    public boolean isSerializable(Class<?> clazz) {
        return this.serializerMap.containsKey(clazz);
    }

    private final Set<Class<?>> canBeSerializedChecked = new ConcurrentSkipListSet<>();

    boolean canBeSerialized(Class<?> clazz) {
        try {
            TypeAdapter<?> adapter = this.gson().getAdapter(clazz);
            //            this.yamlIgnoredClasses.add(clazz);
            return adapter != null;
        } catch (Exception e) {
            // print error but still return false to caller.
            e.printStackTrace();
            return false;
        }
    }

    /**
     * Serialize given object to simple string.
     *
     * @param type
     *         type of object to serialize.
     * @param object
     *         object to serialize.
     * @param <T>
     *         type of object to serialize.
     *
     * @return serialized object as string.
     *
     * @throws IllegalArgumentException
     *         if given object isn't string serializable.
     */
    @SuppressWarnings("unchecked")
    public <T> String serializeToString(Class<T> type, T object) {
        if (!this.isStringSerializable(type)) {
            throw new IllegalArgumentException(
                    "Given object isn't string serializable: (" + type.getName() + ") -> " + object);
        }
        return ((StringSerializer<T>) this.stringSerializerMap.get(type)).serialize(object);
    }

    /**
     * Deserialize given string to object.
     *
     * @param type
     *         type of object to deserialize.
     * @param str
     *         string to deserialize.
     * @param <T>
     *         type of object to deserialize.
     *
     * @return deserialized object from string.
     *
     * @throws IllegalArgumentException
     *         if given type isn't string serializable.
     */
    @SuppressWarnings("unchecked")
    public <T> T deserializeFromString(Class<T> type, String str) {
        if (!this.isStringSerializable(type)) {
            throw new IllegalArgumentException(
                    "Given type isn't string serializable: (" + type.getName() + ") -> " + str);
        }
        return ((StringSerializer<T>) this.stringSerializerMap.get(type)).deserialize(str);
    }

    /**
     * Serialize given object to simple string.
     *
     * @param object
     *         object to serialize.
     *
     * @return serialized object as string.
     *
     * @throws IllegalArgumentException
     *         if given object isn't string serializable.
     */
    @SuppressWarnings("unchecked")
    public String serializeToString(Object object) {
        if (!this.isStringSerializable(object)) {
            throw new IllegalArgumentException("Given object isn't string serializable: " + object);
        }
        return ((StringSerializer<Object>) this.stringSerializerMap.get(object.getClass())).serialize(object);
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    Object serialize(Object object, SerializationType serializationType, @Nullable DocumentComments comments) {
        if (isSimple(object)) {
            return object;
        }
        return this.serialize((Class) object.getClass(), object, serializationType, comments);
    }

    @SuppressWarnings("unchecked")
    <T> Object serialize(Class<T> type, T object, SerializationType serializationType,
            @Nullable DocumentComments comments) {
        if (isSimple(object)) {
            return object;
        }
        if (!this.isSerializable(type)) {
            if (this.canBeSerialized(type)) {
                if (serializationType == SerializationType.YAML) {
                    return this.fromYaml(this.toYaml(object), Map.class);
                } else {
                    TypeAdapter<T> typeAdapter = this.gson().getAdapter(type);
                    return this.fromJson(typeAdapter.toJsonTree(object), Map.class);
                }
            }
            throw new IllegalArgumentException(
                    "Given object isn't serializable: (" + type.getName() + ") -> " + object);
        }
        SimpleSerializationData serializationData = (SimpleSerializationData) SerializationData
                .create(serializationType, this, type);
        if (comments != null) {
            serializationData.setComments(comments);
        }

        ((Serializer<T>) this.serializerMap.get(type)).serialize(object, serializationData);
        return serializationData.rawValue();
    }

    // private

    @Nullable
    @SuppressWarnings("unchecked")
    private <T> StringSerializer<T> registerStringSerializableByAnnotations(Class<T> clazz) {
        Function<T, String> serialization = null;
        Function<String, T> deserialization = null;
        for (Method method : clazz.getDeclaredMethods()) {
            if (method.isAnnotationPresent(StringSerializable.class)) {
                Class<?> returnType = method.getReturnType();
                Class<?>[] params = method.getParameterTypes();
                if (Modifier.isStatic(method.getModifiers())) {
                    if (returnType.equals(String.class) && (params.length == 1)
                            && (params[0].equals(clazz) || params[0].equals(Object.class))) {
                        MethodInvoker methodInvoker = new MethodInvoker(method);
                        methodInvoker.ensureAccessible();
                        serialization = obj -> (String) methodInvoker.invoke(null, obj);
                        continue;
                    }
                    if (returnType.equals(clazz) && (params.length == 1) && params[0].equals(String.class)) {
                        MethodInvoker methodInvoker = new MethodInvoker(method);
                        methodInvoker.ensureAccessible();
                        deserialization = str -> (T) methodInvoker.invoke(null, str);
                        continue;
                    }
                    throw stringAnnotationError(clazz);
                }
                if (returnType.equals(String.class) && (params.length == 0)) {
                    MethodInvoker methodInvoker = new MethodInvoker(method);
                    methodInvoker.ensureAccessible();
                    serialization = obj -> (String) methodInvoker.invoke(obj);
                    continue;
                }
                throw stringAnnotationError(clazz);
            }
        }
        if (serialization == null) {
            throw stringAnnotationError(clazz);
        }
        if (deserialization == null) {
            for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
                if (constructor.isAnnotationPresent(StringSerializable.class)) {
                    Class<?>[] params = constructor.getParameterTypes();
                    if ((params.length == 1) && params[0].equals(String.class)) {
                        ConstructorInvoker<T> constructorInvoker = new ConstructorInvoker<>(constructor);
                        //noinspection Convert2MethodRef Java 9 conpiler bug: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8171993 // FIXME
                        deserialization = (arguments) -> constructorInvoker.invokeWith(arguments);
                    } else {
                        throw stringAnnotationError(clazz);
                    }
                }
            }
            if (deserialization == null) {
                throw stringAnnotationError(clazz);
            }
        }
        StringSerializer<T> stringSerializer = StringSerializer.of(getRedirectionClass(clazz), serialization,
                deserialization);
        this.registerStringSerializer(stringSerializer);
        return stringSerializer;
    }

    @Nullable
    @SuppressWarnings("unchecked")
    private <T extends org.diorite.config.serialization.StringSerializable> StringSerializer<T> registerStringSerializableByType(
            Class<T> clazz) {

        ReflectMethod deserialize = DioriteReflectionUtils.getTypedMethod(clazz, "deserializeFromString", clazz,
                false, String.class);
        if ((deserialize == null) || !deserialize.isStatic()) {
            deserialize = DioriteReflectionUtils.getTypedMethod(clazz, "valueOf", clazz, false, String.class);
            if ((deserialize == null) || !deserialize.isStatic()) {
                deserialize = DioriteReflectionUtils.getConstructor(clazz, false, String.class);
                if (deserialize == null) {
                    String simpleName = clazz.getSimpleName();
                    throw new IllegalArgumentException("Given class (" + clazz.getName()
                            + ") does not contains deserialization method! Make sure to create one of this methods:\n"
                            + "    static " + simpleName + " deserializeFromString(String)\n" + "    static "
                            + simpleName + " valueOf(String)\n" + "   constructor(String)");
                }
            }
        }
        MethodHandle handle = deserialize.getHandle();
        StringSerializer<T> serializer = StringSerializer.of(getRedirectionClass(clazz),
                org.diorite.config.serialization.StringSerializable::serializeToString, str -> {
                    try {
                        return (T) handle.invokeExact(str);
                    } catch (Throwable throwable) {
                        throw new DeserializationException(clazz, str, throwable);
                    }
                });
        this.registerStringSerializer(serializer);
        return serializer;
    }

    @SuppressWarnings("unchecked")
    private <T extends Serializable> Serializer<T> registerSerializableByType(Class<T> clazz) {
        ReflectMethod deserialize = DioriteReflectionUtils.getTypedMethod(clazz, "deserialize", clazz, false,
                DeserializationData.class);
        if ((deserialize == null) || !deserialize.isStatic() || !deserialize.isPublic()) {
            deserialize = DioriteReflectionUtils.getTypedMethod(clazz, "valueOf", clazz, false,
                    DeserializationData.class);
            if ((deserialize == null) || !deserialize.isStatic() || !deserialize.isPublic()) {
                deserialize = DioriteReflectionUtils.getConstructor(clazz, false, DeserializationData.class);
                if ((deserialize == null) || !deserialize.isPublic()) {
                    String simpleName = clazz.getSimpleName();
                    throw new IllegalArgumentException("Given class (" + clazz.getName()
                            + ") does not contains deserialization method! Make sure to create one of this methods:\n"
                            + "    static " + simpleName + " deserialize(DeserializationData)\n" + "    static "
                            + simpleName + " valueOf(DeserializationData)\n"
                            + "    constructor(DeserializationData)");
                }
            }
        }
        MethodHandle handle = deserialize.getHandle();
        Serializer<T> serializer = Serializer.of(getRedirectionClass(clazz), Serializable::serialize, data -> {
            try {
                return (T) handle.invokeWithArguments(data);
            } catch (DeserializationException e) {
                throw e;
            } catch (Throwable throwable) {
                throw new DeserializationException(clazz, data, throwable);
            }
        });
        this.registerSerializer(
                Serializer.of(clazz, serializer.getSerializerFunction(), serializer.getDeserializerFunction()));
        this.registerSerializer(serializer);
        return serializer;
    }

    @SuppressWarnings("unchecked")
    private <T> Serializer<T> registerSerializableByAnnotations(Class<T> clazz) {
        BiConsumer<T, SerializationData> serialization = null;
        Function<DeserializationData, T> deserialization = null;
        for (Method method : clazz.getDeclaredMethods()) {
            Class<?> returnType = method.getReturnType();
            Class<?>[] params = method.getParameterTypes();
            if (method.isAnnotationPresent(org.diorite.config.annotations.Serializable.class)) {
                if (Modifier.isStatic(method.getModifiers())) {
                    if (returnType.equals(void.class) && (params.length == 2)) {
                        if ((params[0].equals(clazz) || params[0].equals(Object.class))
                                && params[1].equals(SerializationData.class)) {
                            MethodInvoker methodInvoker = new MethodInvoker(method);
                            methodInvoker.ensureAccessible();
                            serialization = (t, data) -> methodInvoker.invoke(null, t, data);
                        } else if ((params[1].equals(clazz) || params[1].equals(Object.class))
                                && params[0].equals(SerializationData.class)) {
                            MethodInvoker methodInvoker = new MethodInvoker(method);
                            methodInvoker.ensureAccessible();
                            serialization = (t, data) -> methodInvoker.invoke(null, data, t);
                        } else {
                            throw annotationError(clazz);
                        }
                        continue;
                    }
                    if (returnType.equals(clazz) && (params.length == 1)
                            && params[0].equals(DeserializationData.class)) {
                        MethodInvoker methodInvoker = new MethodInvoker(method);
                        methodInvoker.ensureAccessible();
                        deserialization = data -> (T) methodInvoker.invoke(null, data);
                        continue;
                    }
                    throw annotationError(clazz);
                }
                if (returnType.equals(void.class) && (params.length == 1)
                        && params[0].equals(SerializationData.class)) {
                    MethodInvoker methodInvoker = new MethodInvoker(method);
                    methodInvoker.ensureAccessible();
                    serialization = methodInvoker::invoke;
                    continue;
                }
                throw annotationError(clazz);
            }
        }
        if (serialization == null) {
            throw annotationError(clazz);
        }
        if (deserialization == null) {
            for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
                if (constructor.isAnnotationPresent(org.diorite.config.annotations.Serializable.class)) {
                    Class<?>[] params = constructor.getParameterTypes();
                    if ((params.length == 1) && params[0].equals(DeserializationData.class)) {
                        ConstructorInvoker<T> constructorInvoker = new ConstructorInvoker<>(constructor);
                        //noinspection Convert2MethodRef Java 9 conpiler bug: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8171993 // FIXME
                        deserialization = (arguments) -> constructorInvoker.invokeWith(arguments);
                    } else {
                        throw annotationError(clazz);
                    }
                }
            }
            if (deserialization == null) {
                throw annotationError(clazz);
            }
        }
        Serializer<T> serializer = Serializer.of(getRedirectionClass(clazz), serialization, deserialization);
        this.registerSerializer(
                Serializer.of(clazz, serializer.getSerializerFunction(), serializer.getDeserializerFunction()));
        this.registerSerializer(serializer);
        return serializer;
    }

    private static IllegalArgumentException stringAnnotationError(Class<?> clazz) {
        String simpleName = clazz.getSimpleName();
        return new IllegalArgumentException("Given class (" + clazz.getName()
                + ") does not contain needed methods (or contains annotations on invalid methods)! "
                + "Make sure to create valid serialization and deserialization methods, with @StringSerializable annotation over them!\n"
                + "  Serialization methods:\n" + "    static String nameOfMethod(" + simpleName + ")\n"
                + "    static String nameOfMethod(Object)\n" + "    String nameOfMethod()\n"
                + "  Deserialization methods:\n" + "    static " + simpleName + " nameOfMethod(String)\n"
                + "    constructor(String)");
    }

    private static IllegalArgumentException annotationError(Class<?> clazz) {
        String simpleName = clazz.getSimpleName();
        return new IllegalArgumentException("Given class (" + clazz.getName()
                + ") does not contain needed methods (or contains annotations on invalid methods)! "
                + "Make sure to create valid serialization and deserialization methods, with @Serializable annotation over them!\n"
                + "  Serialization methods:\n" + "    static void nameOfMethod(" + simpleName
                + ", SerializationData)\n" + "    static void nameOfMethod(SerializationData, " + simpleName + ")\n"
                + "    static void nameOfMethod(Object, SerializationData)\n"
                + "    static void nameOfMethod(SerializationData, Object)\n"
                + "    void nameOfMethod(SerializationData)\n" + "  Deserialization methods:\n" + "    static "
                + simpleName + " nameOfMethod(DeserializationData)\n" + "    constructor(DeserializationData)");
    }

    @SuppressWarnings("unchecked")
    private static <T> Class<? super T> getRedirectionClass(Class<T> type) {
        if (type.isAnnotationPresent(SerializableAs.class)) {
            SerializableAs serializableAs = type.getAnnotation(SerializableAs.class);
            return (Class<? super T>) getRedirectionClass(serializableAs.value());
        }
        return type;
    }

    @SuppressWarnings("unchecked")
    private static <T> Class<? extends T> getDelegatedLookupClass(Class<T> type) {
        if (type.isAnnotationPresent(DelegateSerializable.class)) {
            DelegateSerializable serializableAs = type.getAnnotation(DelegateSerializable.class);
            return (Class<? extends T>) getDelegatedLookupClass(serializableAs.value());
        }
        return type;
    }

    static boolean isSimpleType(Class<?> type) {
        if (type.isPrimitive()) {
            return true;
        }
        if (type.isEnum()) {
            return true;
        }
        if (type.equals(String.class)) {
            return true;
        }
        if (type.equals(Boolean.class)) {
            return true;
        }
        if (type.equals(Byte.class)) {
            return true;
        }
        if (type.equals(Character.class)) {
            return true;
        }
        if (type.equals(Short.class)) {
            return true;
        }
        if (type.equals(Integer.class)) {
            return true;
        }
        if (type.equals(Long.class)) {
            return true;
        }
        if (type.equals(Float.class)) {
            return true;
        }
        return type.equals(Double.class);
    }

    static boolean isSimple(@Nullable Object object) {
        if (object == null) {
            return true;
        }
        if (object instanceof Enum) {
            return true;
        }
        if (object instanceof String) {
            return true;
        }
        if (object instanceof Boolean) {
            return true;
        }
        if (object instanceof Byte) {
            return true;
        }
        if (object instanceof Character) {
            return true;
        }
        if (object instanceof Short) {
            return true;
        }
        if (object instanceof Integer) {
            return true;
        }
        if (object instanceof Long) {
            return true;
        }
        if (object instanceof Float) {
            return true;
        }
        return object instanceof Double;
    }

    // json delegates.

    /**
     * This method serializes the specified object into its equivalent representation as a tree of
     * {@link JsonElement}s. This method should be used when the specified object is not a generic
     * type. This method uses {@link Class#getClass()} to get the type for the specified object, but
     * the {@code getClass()} loses the generic type information because of the Type Erasure feature
     * of Java. Note that this method works fine if the any of the object fields are of generic type,
     * just the object itself should not be of a generic type. If the object is of generic type, use
     * {@link #toJsonTree(Object, Type)} instead.
     *
     * @param src
     *         the object for which Json representation is to be created setting for Gson
     *
     * @return Json representation of {@code src}.
     */
    public JsonElement toJsonTree(Object src) {
        return this.gson().toJsonTree(src);
    }

    /**
     * This method serializes the specified object, including those of generic types, into its
     * equivalent representation as a tree of {@link JsonElement}s. This method must be used if the
     * specified object is a generic type. For non-generic objects, use {@link #toJsonTree(Object)}
     * instead.
     *
     * @param src
     *         the object for which JSON representation is to be created
     * @param typeOfSrc
     *         The specific genericized type of src. You can obtain this type by using the {@link TypeToken} class. For example, to get the type for {@code
     *         Collection<Foo>}, you should use:
     *         <pre> Type typeOfSrc = new TypeToken&lt;Collection&lt;Foo&gt;&gt;(){}.getType(); </pre>
     *
     * @return Json representation of {@code src}
     */
    public JsonElement toJsonTree(Object src, Type typeOfSrc) {
        return this.gson().toJsonTree(src, typeOfSrc);
    }

    /**
     * This method serializes the specified object into its equivalent Json representation.
     * This method should be used when the specified object is not a generic type. This method uses
     * {@link Class#getClass()} to get the type for the specified object, but the
     * {@code getClass()} loses the generic type information because of the Type Erasure feature
     * of Java. Note that this method works fine if the any of the object fields are of generic type,
     * just the object itself should not be of a generic type. If the object is of generic type, use
     * {@link #toJson(Object, Type)} instead. If you want to write out the object to a
     * {@link Writer}, use {@link #toJson(Object, Appendable)} instead.
     *
     * @param src
     *         the object for which Json representation is to be created setting for Gson
     *
     * @return Json representation of {@code src}.
     */
    public String toJson(Object src) {
        return this.gson().toJson(src);
    }

    /**
     * This method serializes the specified object, including those of generic types, into its
     * equivalent Json representation. This method must be used if the specified object is a generic
     * type. For non-generic objects, use {@link #toJson(Object)} instead. If you want to write out
     * the object to a {@link Appendable}, use {@link #toJson(Object, Type, Appendable)} instead.
     *
     * @param src
     *         the object for which JSON representation is to be created
     * @param typeOfSrc
     *         The specific genericized type of src. You can obtain this type by using the {@link TypeToken} class. For example, to get the type for {@code
     *         Collection<Foo>}, you should use:
     *         <pre> new TypeToken&lt;Collection&lt;Foo&gt;&gt;(){} </pre>
     *
     * @return Json representation of {@code src}
     */
    public String toJson(Object src, Type typeOfSrc) {
        return this.gson().toJson(src, typeOfSrc);
    }

    /**
     * This method serializes the specified object into its equivalent Json representation.
     * This method should be used when the specified object is not a generic type. This method uses
     * {@link Class#getClass()} to get the type for the specified object, but the
     * {@code getClass()} loses the generic type information because of the Type Erasure feature
     * of Java. Note that this method works fine if the any of the object fields are of generic type,
     * just the object itself should not be of a generic type. If the object is of generic type, use
     * {@link #toJson(Object, Type, Appendable)} instead.
     *
     * @param src
     *         the object for which Json representation is to be created setting for Gson
     * @param writer
     *         Writer to which the Json representation needs to be written
     *
     * @throws JsonIOException
     *         if there was a problem writing to the writer
     */
    public void toJson(Object src, Appendable writer) throws JsonIOException {
        this.gson().toJson(src, writer);
    }

    /**
     * This method serializes the specified object, including those of generic types, into its
     * equivalent Json representation. This method must be used if the specified object is a generic
     * type. For non-generic objects, use {@link #toJson(Object, Appendable)} instead.
     *
     * @param src
     *         the object for which JSON representation is to be created
     * @param typeOfSrc
     *         The specific genericized type of src. You can obtain this type by using the {@link TypeToken} class. For example, to get the type for {@code
     *         Collection<Foo>}, you should use:
     *         <pre> new TypeToken&lt;Collection&lt;Foo&gt;&gt;(){} </pre>
     * @param writer
     *         Writer to which the Json representation of src needs to be written.
     *
     * @throws JsonIOException
     *         if there was a problem writing to the writer
     */
    public void toJson(Object src, Type typeOfSrc, Appendable writer) throws JsonIOException {
        this.gson().toJson(src, typeOfSrc, writer);
    }

    /**
     * Writes the JSON representation of {@code src} of type {@code typeOfSrc} to
     * {@code writer}.
     *
     * @param src
     *         the object for which JSON representation is to be created
     * @param typeOfSrc
     *         The specific genericized type of src. You can obtain this type by using the {@link TypeToken} class. For example, to get the type for {@code
     *         Collection<Foo>}, you should use:
     *         <pre> new TypeToken&lt;Collection&lt;Foo&gt;&gt;(){} </pre>
     * @param writer
     *         Writer to which the Json representation of src needs to be written.
     *
     * @throws JsonIOException
     *         if there was a problem writing to the writer
     */
    public void toJson(Object src, Type typeOfSrc, JsonWriter writer) throws JsonIOException {
        this.gson().toJson(src, typeOfSrc, writer);
    }

    /**
     * Converts a tree of {@link JsonElement}s into its equivalent JSON representation.
     *
     * @param jsonElement
     *         root of a tree of {@link JsonElement}s
     *
     * @return JSON String representation of the tree
     */
    public String toJson(JsonElement jsonElement) {
        return this.gson().toJson(jsonElement);
    }

    /**
     * Writes out the equivalent JSON for a tree of {@link JsonElement}s.
     *
     * @param jsonElement
     *         root of a tree of {@link JsonElement}s
     * @param writer
     *         Writer to which the Json representation needs to be written
     *
     * @throws JsonIOException
     *         if there was a problem writing to the writer
     */
    public void toJson(JsonElement jsonElement, Appendable writer) throws JsonIOException {
        this.gson().toJson(jsonElement, writer);
    }

    /**
     * Returns a new JSON writer configured for the settings on this Gson instance.
     *
     * @param writer
     *         writer to wrap.
     *
     * @return created json writer.
     *
     * @throws IOException
     *         if write to writer fails.
     */
    public JsonWriter newJsonWriter(Writer writer) throws IOException {
        return this.gson().newJsonWriter(writer);
    }

    /**
     * Returns a new JSON reader configured for the settings on this Gson instance.
     *
     * @param reader
     *         reader to wrap.
     *
     * @return created json reader.
     */
    public JsonReader newJsonReader(Reader reader) {
        return this.gson().newJsonReader(reader);
    }

    /**
     * Writes the JSON for {@code jsonElement} to {@code writer}.
     *
     * @param writer
     *         Writer to which the Json representation needs to be written
     * @param jsonElement
     *         root of a tree of {@link JsonElement}s
     *
     * @throws JsonIOException
     *         if there was a problem writing to the writer
     */
    public void toJson(JsonElement jsonElement, JsonWriter writer) throws JsonIOException {
        this.gson().toJson(jsonElement, writer);
    }

    /**
     * This method deserializes the specified Json into an object of the specified class. It is not
     * suitable to use if the specified class is a generic type since it will not have the generic
     * type information because of the Type Erasure feature of Java. Therefore, this method should not
     * be used if the desired type is a generic type. Note that this method works fine if the any of
     * the fields of the specified object are generics, just the object itself should not be a
     * generic type. For the cases when the object is of generic type, invoke
     * {@link #fromJson(String, Type)}. If you have the Json in a {@link Reader} instead of
     * a String, use {@link #fromJson(Reader, Class)} instead.
     *
     * @param json
     *         the string from which the object is to be deserialized
     * @param classOfT
     *         the class of T
     * @param <T>
     *         type of class.
     *
     * @return an object of type T from the string. Returns {@code null} if {@code json} is {@code null}.
     *
     * @throws JsonSyntaxException
     *         if json is not a valid representation for an object of type classOfT
     */
    public <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
        return this.gson().fromJson(json, classOfT);
    }

    /**
     * This method deserializes the specified Json into an object of the specified type. This method
     * is useful if the specified object is a generic type. For non-generic objects, use
     * {@link #fromJson(String, Class)} instead. If you have the Json in a {@link Reader} instead of
     * a String, use {@link #fromJson(Reader, Type)} instead.
     *
     * @param json
     *         the string from which the object is to be deserialized
     * @param typeOfT
     *         The specific genericized type of src. You can obtain this type by using the {@link TypeToken} class. For example, to get the type for {@code
     *         Collection<Foo>}, you should use:
     *         <pre> new TypeToken&lt;Collection&lt;Foo&gt;&gt;(){}.getType(); </pre>
     * @param <T>
     *         type of class.
     *
     * @return an object of type T from the string. Returns {@code null} if {@code json} is {@code null}.
     *
     * @throws com.google.gson.JsonParseException
     *         if json is not a valid representation for an object of type typeOfT
     * @throws JsonSyntaxException
     *         if json is not a valid representation for an object of type
     */
    public <T> T fromJson(String json, Type typeOfT) throws JsonSyntaxException {
        return this.gson().fromJson(json, typeOfT);
    }

    /**
     * This method deserializes the Json read from the specified reader into an object of the
     * specified class. It is not suitable to use if the specified class is a generic type since it
     * will not have the generic type information because of the Type Erasure feature of Java.
     * Therefore, this method should not be used if the desired type is a generic type. Note that
     * this method works fine if the any of the fields of the specified object are generics, just the
     * object itself should not be a generic type. For the cases when the object is of generic type,
     * invoke {@link #fromJson(Reader, Type)}. If you have the Json in a String form instead of a
     * {@link Reader}, use {@link #fromJson(String, Class)} instead.
     *
     * @param json
     *         the reader producing the Json from which the object is to be deserialized.
     * @param classOfT
     *         the class of T
     * @param <T>
     *         type of class.
     *
     * @return an object of type T from the string. Returns {@code null} if {@code json} is at EOF.
     *
     * @throws JsonIOException
     *         if there was a problem reading from the Reader
     * @throws JsonSyntaxException
     *         if json is not a valid representation for an object of type
     */
    public <T> T fromJson(Reader json, Class<T> classOfT) throws JsonSyntaxException, JsonIOException {
        return this.gson().fromJson(json, classOfT);
    }

    /**
     * This method deserializes the Json read from the specified reader into an object of the
     * specified type. This method is useful if the specified object is a generic type. For
     * non-generic objects, use {@link #fromJson(Reader, Class)} instead. If you have the Json in a
     * String form instead of a {@link Reader}, use {@link #fromJson(String, Type)} instead.
     *
     * @param json
     *         the reader producing Json from which the object is to be deserialized
     * @param typeOfT
     *         The specific genericized type of src. You can obtain this type by using the {@link TypeToken} class. For example, to get the type for {@code
     *         Collection<Foo>}, you should use:
     *         <pre> new TypeToken&lt;Collection&lt;Foo&gt;&gt;(){} </pre>
     * @param <T>
     *         type of class.
     *
     * @return an object of type T from the json. Returns {@code null} if {@code json} is at EOF.
     *
     * @throws JsonIOException
     *         if there was a problem reading from the Reader
     * @throws JsonSyntaxException
     *         if json is not a valid representation for an object of type
     */
    public <T> T fromJson(Reader json, Type typeOfT) throws JsonIOException, JsonSyntaxException {
        return this.gson().fromJson(json, typeOfT);
    }

    /**
     * Reads the next JSON value from {@code reader} and convert it to an object
     * of type {@code typeOfT}. Returns {@code null}, if the {@code reader} is at EOF.
     * Since Type is not parameterized by T, this method is type unsafe and should be used carefully
     *
     * @param reader
     *         the reader producing Json from which the object is to be deserialized
     * @param typeOfT
     *         The specific genericized type of src. You can obtain this type by using the {@link TypeToken} class. For example, to get the type for {@code
     *         Collection<Foo>}, you should use:
     *         <pre> new TypeToken&lt;Collection&lt;Foo&gt;&gt;(){} </pre>
     * @param <T>
     *         type of class.
     *
     * @return an object of type T from the json. Returns {@code null} if {@code json} is at EOF.
     *
     * @throws JsonIOException
     *         if there was a problem writing to the Reader
     * @throws JsonSyntaxException
     *         if json is not a valid representation for an object of type
     */
    public <T> T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, JsonSyntaxException {
        return this.gson().fromJson(reader, typeOfT);
    }

    /**
     * This method deserializes the Json read from the specified parse tree into an object of the
     * specified type. It is not suitable to use if the specified class is a generic type since it
     * will not have the generic type information because of the Type Erasure feature of Java.
     * Therefore, this method should not be used if the desired type is a generic type. Note that
     * this method works fine if the any of the fields of the specified object are generics, just the
     * object itself should not be a generic type. For the cases when the object is of generic type,
     * invoke {@link #fromJson(JsonElement, Type)}.
     *
     * @param json
     *         the root of the parse tree of {@link JsonElement}s from which the object is to be deserialized
     * @param classOfT
     *         The class of T
     * @param <T>
     *         type of class.
     *
     * @return an object of type T from the json. Returns {@code null} if {@code json} is {@code null}.
     *
     * @throws JsonSyntaxException
     *         if json is not a valid representation for an object of type typeOfT
     */
    public <T> T fromJson(JsonElement json, Class<T> classOfT) throws JsonSyntaxException {
        return this.gson().fromJson(json, classOfT);
    }

    /**
     * This method deserializes the Json read from the specified parse tree into an object of the
     * specified type. This method is useful if the specified object is a generic type. For
     * non-generic objects, use {@link #fromJson(JsonElement, Class)} instead.
     *
     * @param json
     *         the root of the parse tree of {@link JsonElement}s from which the object is to be deserialized
     * @param typeOfT
     *         The specific genericized type of src. You can obtain this type by using the {@link TypeToken} class. For example, to get the type for {@code
     *         Collection<Foo>}, you should use:
     *         <pre> new TypeToken&lt;Collection&lt;Foo&gt;&gt;(){}.getType(); </pre>
     * @param <T>
     *         type of class.
     *
     * @return an object of type T from the json. Returns {@code null} if {@code json} is {@code null}.
     *
     * @throws JsonSyntaxException
     *         if json is not a valid representation for an object of type typeOfT
     */
    public <T> T fromJson(JsonElement json, Type typeOfT) throws JsonSyntaxException {
        return this.gson().fromJson(json, typeOfT);
    }

    // yaml delegate:

    /**
     * Serialize a Java object into a YAML String.
     *
     * @param data
     *         Java object to be Serialized to YAML
     *
     * @return YAML String
     */
    public String toYamlWithComments(Object data) {
        return this.yaml().toYamlWithComments(data, this.commentsManager.getComments(data.getClass()));
    }

    /**
     * Serialize a Java object into a YAML String.
     *
     * @param data
     *         Java object to be Serialized to YAML
     * @param comments
     *         comments node.
     *
     * @return YAML String
     */
    public String toYamlWithComments(Object data, DocumentComments comments) {
        return this.yaml().toYamlWithComments(data, comments);
    }

    /**
     * Serialize a Java object into a YAML Node.
     *
     * @param data
     *         Java object to be Serialized to YAML
     *
     * @return YAML Node
     */
    public Node toYamlNode(@Nullable Object data) {
        Node represent = this.yaml().represent(data);
        if (data != null) {
            if (!(data instanceof Map) && !(data instanceof Collection)) {
                represent.setType(data.getClass());
                represent.setTag(new Tag(data.getClass()));
            }
        }
        return represent;
    }

    /**
     * Serialize a Java object into a YAML String.
     *
     * @param data
     *         Java object to be Serialized to YAML
     *
     * @return YAML String
     */
    public String toYaml(Object data) {
        return this.yaml().toYaml(data);
    }

    /**
     * Serialize a sequence of Java objects into a YAML String.
     *
     * @param data
     *         Iterator with Objects
     *
     * @return YAML String with all the objects in proper sequence
     */
    public String toYaml(Iterator<?> data) {
        return this.yaml().toYaml(data);
    }

    /**
     * Serialize a Java object into a YAML stream.
     *
     * @param data
     *         Java object to be serialized to YAML
     * @param output
     *         output for yaml.
     */
    public void toYaml(Object data, Writer output) {
        this.yaml().toYaml(data, output);
    }

    /**
     * Serialize a Java object into a YAML stream.
     *
     * @param data
     *         Java object to be serialized to YAML
     * @param output
     *         output for yaml.
     */
    public void toYamlWithComments(Object data, Writer output) {
        this.yaml().toYamlWithComments(data, output, this.commentsManager.getComments(data.getClass()));
    }

    /**
     * Serialize a Java object into a YAML stream.
     *
     * @param data
     *         Java object to be serialized to YAML
     * @param output
     *         output for yaml.
     * @param comments
     *         comments node.
     */
    public void toYamlWithComments(Object data, Writer output, DocumentComments comments) {
        this.yaml().toYamlWithComments(data, output, comments);
    }

    /**
     * Serialize a sequence of Java objects into a YAML stream.
     *
     * @param data
     *         Iterator with Objects
     * @param output
     *         output for yaml.
     */
    public void toYaml(Iterator<?> data, Writer output) {
        this.yaml().toYaml(data, output);
    }

    /**
     * <p>
     * Serialize a Java object into a YAML string. Override the default root tag
     * with <code>rootTag</code>.
     * </p>
     *
     * <p>
     * This method is similar to <code>Yaml.dump(data)</code> except that the
     * root tag for the whole document is replaced with the given tag. This has
     * two main uses.
     * </p>
     *
     * <p>
     * First, if the root tag is replaced with a standard YAML tag, such as
     * <code>Tag.MAP</code>, then the object will be dumped as a map. The root
     * tag will appear as <code>!!map</code>, or blank (implicit !!map).
     * </p>
     *
     * <p>
     * Second, if the root tag is replaced by a different custom tag, then the
     * document appears to be a different type when loaded. For example, if an
     * instance of MyClass is dumped with the tag !!YourClass, then it will be
     * handled as an instance of YourClass when loaded.
     * </p>
     *
     * @param data
     *         Java object to be serialized to YAML
     * @param rootTag
     *         the tag for the whole YAML document. The tag should be Tag.MAP for a JavaBean to make the tag disappear (to use implicit tag !!map). If
     *         <code>null</code> is provided then the standard tag with the full class name is used.
     * @param flowStyle
     *         flow style for the whole document. See Chapter 10. Collection Styles http://yaml.org/spec/1.1/#id930798. If <code>null</code> is provided then
     *         the flow style from DumperOptions is used.
     *
     * @return YAML String
     */
    public String toYaml(Object data, Tag rootTag, @Nullable FlowStyle flowStyle) {
        return this.yaml().toYaml(data, rootTag, flowStyle);
    }

    /**
     * <p>
     * Serialize a Java object into a YAML string. Override the default root tag
     * with <code>Tag.MAP</code>.
     * </p>
     * <p>
     * This method is similar to <code>Yaml.dump(data)</code> except that the
     * root tag for the whole document is replaced with <code>Tag.MAP</code> tag
     * (implicit !!map).
     * </p>
     * <p>
     * Block Mapping is used as the collection style. See 10.2.2. Block Mappings
     * (http://yaml.org/spec/1.1/#id934537)
     * </p>
     *
     * @param data
     *         Java object to be serialized to YAML
     *
     * @return YAML String
     */
    public String toYamlAsMap(Object data) {
        return this.yaml().toYamlAsMap(data);
    }

    /**
     * Construct object from given node, node must have set type tags.
     *
     * @param node
     *         node to load.
     * @param <T>
     *         type of deserialized object.
     *
     * @return loaded object.
     */
    @Nullable
    public <T> T fromYamlNode(Node node) {
        return this.yaml().fromYamlNode(node);
    }

    /**
     * Construct object from given node.
     *
     * @param node
     *         node to load.
     * @param type
     *         type of node.
     * @param <T>
     *         type of deserialized object.
     *
     * @return loaded object.
     */
    public <T> T fromYamlNode(Node node, Class<T> type) {
        return this.yaml().fromYamlNode(node, type);
    }

    /**
     * Parse the only YAML document in a String and produce the corresponding
     * Java object. (Because the encoding in known BOM is not respected.)
     *
     * @param yaml
     *         YAML data to load from (BOM must not be present)
     *
     * @return parsed object
     */
    public Object fromYaml(String yaml) {
        return this.yaml().fromYaml(yaml);
    }

    /**
     * Parse the only YAML document in a stream and produce the corresponding
     * Java object.
     *
     * @param io
     *         data to load from (BOM is respected and removed)
     *
     * @return parsed object
     */
    public Object fromYaml(InputStream io) {
        return this.yaml().fromYaml(io);
    }

    /**
     * Parse the only YAML document in a stream and produce the corresponding
     * Java object.
     *
     * @param io
     *         data to load from (BOM must not be present)
     *
     * @return parsed object
     */
    public Object fromYaml(Reader io) {
        return this.yaml().fromYaml(io);
    }

    /**
     * Parse the only YAML document in a stream as configuration object.
     *
     * @param template
     *         template of config object.
     * @param io
     *         data to load from (BOM must not be present)
     * @param <T>
     *         type of deserialized object.
     *
     * @return parsed object
     */
    public <T extends Config> T fromYaml(ConfigTemplate<T> template, Reader io) {
        return this.yaml().fromYaml(template, io);
    }

    /**
     * Parse the only YAML document in a stream and produce the corresponding
     * Java object.
     *
     * @param io
     *         data to load from (BOM must not be present)
     * @param type
     *         Class of the object to be created
     * @param <T>
     *         type of deserialized object.
     *
     * @return parsed object
     */
    public <T> T fromYaml(Reader io, Class<T> type) {
        return this.yaml().fromYaml(io, type);
    }

    /**
     * Parse the only YAML document in a String and produce the corresponding
     * Java object. (Because the encoding in known BOM is not respected.)
     *
     * @param yaml
     *         YAML data to load from (BOM must not be present)
     * @param type
     *         Class of the object to be created
     * @param <T>
     *         type of deserialized object.
     *
     * @return parsed object
     */
    public <T> T fromYaml(String yaml, Class<T> type) {
        return this.yaml().fromYaml(yaml, type);
    }

    /**
     * Parse the only YAML document in a stream and produce the corresponding
     * Java object.
     *
     * @param input
     *         data to load from (BOM is respected and removed)
     * @param type
     *         Class of the object to be created
     * @param <T>
     *         type of deserialized object.
     *
     * @return parsed object
     */
    public <T> T fromYaml(InputStream input, Class<T> type) {
        return this.yaml().fromYaml(input, type);
    }

    /**
     * Parse all YAML documents in a String and produce corresponding Java
     * objects. The documents are parsed only when the iterator is invoked.
     *
     * @param yaml
     *         YAML data to load from (BOM must not be present)
     *
     * @return an iterator over the parsed Java objects in this String in proper sequence
     */
    public Iterable<Object> fromAllYaml(Reader yaml) {
        return this.yaml().fromAllYaml(yaml);
    }

    /**
     * Parse all YAML documents in a String and produce corresponding Java
     * objects. (Because the encoding in known BOM is not respected.) The
     * documents are parsed only when the iterator is invoked.
     *
     * @param yaml
     *         YAML data to load from (BOM must not be present)
     *
     * @return an iterator over the parsed Java objects in this String in proper sequence
     */
    public Iterable<Object> fromAllYaml(String yaml) {
        return this.yaml().fromAllYaml(yaml);
    }

    /**
     * Parse all YAML documents in a stream and produce corresponding Java
     * objects. The documents are parsed only when the iterator is invoked.
     *
     * @param yaml
     *         YAML data to load from (BOM is respected and ignored)
     *
     * @return an iterator over the parsed Java objects in this stream in proper sequence
     */
    public Iterable<Object> fromAllYaml(InputStream yaml) {
        return this.yaml().fromAllYaml(yaml);
    }
}