WrapperParser.java

package it.fulminazzo.yagl.wrappers;

import it.fulminazzo.fulmicollection.interfaces.functions.BiFunctionException;
import it.fulminazzo.fulmicollection.interfaces.functions.TriConsumer;
import it.fulminazzo.fulmicollection.objects.Refl;
import it.fulminazzo.fulmicollection.utils.ClassUtils;
import it.fulminazzo.fulmicollection.utils.ReflectionUtils;
import it.fulminazzo.yamlparser.configuration.FileConfiguration;
import it.fulminazzo.yamlparser.configuration.IConfiguration;
import it.fulminazzo.yamlparser.parsers.YAMLParser;
import org.jetbrains.annotations.NotNull;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Set;

/**
 * A parser to serialize a generic {@link Wrapper} object.
 *
 * @param <W> the type parameter
 */
@SuppressWarnings("unchecked")
public class WrapperParser<W extends Wrapper> extends YAMLParser<W> {

    /**
     * Instantiates a new Wrapper parser.
     *
     * @param clazz the class of the {@link Wrapper} to serialize
     */
    public WrapperParser(@NotNull Class<W> clazz) {
        super(clazz);
    }

    @Override
    protected BiFunctionException<IConfiguration, String, W> getLoader() {
        return (c, s) -> {
            String raw = c.getString(s);
            if (raw == null || raw.trim().isEmpty()) return null;
            else return parseWrapperFromString(raw, getOClass());
        };
    }

    /**
     * Converts the given string to an instance of the given wrapper class by using the most appropriate constructor.
     *
     * @param <W>   the type of the wrapper
     * @param raw   the string to convert from
     * @param clazz the class of the wrapper
     * @return the wrapper
     * @throws NoSuchMethodException an exception thrown in case the constructor cannot be found
     */
    public static <W extends Wrapper> @NotNull W parseWrapperFromString(final @NotNull String raw, final @NotNull Class<W> clazz) throws NoSuchMethodException {
        String[] rawData = raw.split(":");
        Constructor<W> constructor = findConstructorFromRaw(rawData, clazz);
        Object[] parameters = initializeParameters(rawData, constructor);
        return new Refl<>(clazz, parameters).getObject();
    }

    private static <W extends Wrapper> @NotNull Constructor<W> findConstructorFromRaw(final String @NotNull [] rawData, final @NotNull Class<W> clazz) throws NoSuchMethodException {
        Constructor<W> constructor = (Constructor<W>) Arrays.stream(clazz.getConstructors())
                .filter(t -> t.getParameterCount() <= rawData.length)
                .min(Comparator.comparing(t -> -t.getParameterCount())).orElse(null);
        if (constructor == null) {
            StringBuilder builder = new StringBuilder();
            for (int i = 0; i < rawData.length; i++) builder.append("?, ");
            throw new NoSuchMethodException(String.format("Could not find method %s(%s)",
                    clazz.getSimpleName(), builder.substring(0, Math.max(0, builder.length() - 2))));
        }
        return constructor;
    }

    private static <W extends Wrapper> Object @NotNull [] initializeParameters(final String @NotNull [] rawData,
                                                                               final @NotNull Constructor<W> constructor) {
        Object[] parameters = new Object[rawData.length];
        Class<?>[] types = constructor.getParameterTypes();
        for (int i = 0; i < types.length; i++) {
            Class<?> type = types[i];
            if (!ReflectionUtils.isPrimitiveOrWrapper(type))
                throw new IllegalArgumentException(String.format("Cannot parse type %s", type.getCanonicalName()));
            else {
                type = ReflectionUtils.getWrapperClass(type);
                String t = rawData[i];
                if (type.equals(String.class)) parameters[i] = t;
                else parameters[i] = new Refl<>(type).invokeMethod("valueOf", t);
            }
        }
        return parameters;
    }

    @Override
    protected TriConsumer<IConfiguration, String, W> getDumper() {
        return (c, s, w) -> {
            c.set(s, null);
            if (w == null) return;
            StringBuilder tmp = new StringBuilder();
            Refl<?> wRefl = new Refl<>(w);
            for (Field field : wRefl.getNonStaticFields()) {
                Object o = wRefl.getFieldObject(field);
                String result = o == null ? "" : o.toString();
                if (o instanceof String) result = result.toLowerCase();
                tmp.append(result).append(":");
            }
            c.set(s, tmp.substring(0, Math.max(0, tmp.length() - 1)));
        };
    }

    /**
     * Adds all the parsers in the {@link it.fulminazzo.yagl.wrappers} package as {@link WrapperParser}s.
     */
    public static void addAllParsers() {
        @NotNull Set<Class<?>> classes = ClassUtils.findClassesInPackage(Wrapper.class.getPackage().getName());
        for (Class<?> clazz : classes)
            if (!clazz.equals(Wrapper.class) && Wrapper.class.isAssignableFrom(clazz))
                FileConfiguration.addParsers(new WrapperParser<>((Class<? extends Wrapper>) clazz));
    }
}