GeneratorUtil.java

package net.morimekta.terminal.args.impl;

import net.morimekta.strings.NamingUtil;
import net.morimekta.terminal.args.ArgException;
import net.morimekta.terminal.args.ValueParser;
import net.morimekta.terminal.args.annotations.ArgIsHidden;
import net.morimekta.terminal.args.annotations.ArgIsRepeated;
import net.morimekta.terminal.args.annotations.ArgIsRequired;
import net.morimekta.terminal.args.annotations.ArgKeyParser;
import net.morimekta.terminal.args.annotations.ArgOptions;
import net.morimekta.terminal.args.annotations.ArgValueParser;
import net.morimekta.terminal.args.reference.Reference;

import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.NavigableSet;
import java.util.Optional;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.function.Supplier;

public final class GeneratorUtil {
    private GeneratorUtil() {}

    public static Optional<Field> findField(Class<?> type, String name) {
        try {
            return Optional.of(type.getDeclaredField(name));
        } catch (NoSuchFieldException e) {
            // And in case someone makes non-standard naming of public fields.
            for (Field field : type.getDeclaredFields()) {
                var reFormatted = NamingUtil.format(field.getName(), NamingUtil.Format.CAMEL);
                if (name.equals(reFormatted)) {
                    return Optional.of(field);
                }
            }
            return Optional.empty();
        }
    }

    public static Optional<Method> findMethod(Class<?> type, String name, Class<?>... params) {
        try {
            return Optional.of(type.getDeclaredMethod(name, params));
        } catch (NoSuchMethodException e) {
            return Optional.empty();
        }
    }

    public static <T extends Annotation> T getAnnotation(Class<?> type, Class<T> annotation) {
        if (type.isAnnotationPresent(annotation)) {
            return type.getAnnotation(annotation);
        }
        if (type.getSuperclass() != null) {
            T out = getAnnotation(type.getSuperclass(), annotation);
            if (out != null) {
                return out;
            }
        }
        for (Class<?> iFace : type.getInterfaces()) {
            T out = getAnnotation(iFace, annotation);
            if (out != null) {
                return out;
            }
        }
        return null;
    }

    public static boolean isPublic(Method accessibleObject) {
        return (accessibleObject.getModifiers() & Modifier.PUBLIC) == Modifier.PUBLIC;
    }

    public static boolean isStatic(Method accessibleObject) {
        return (accessibleObject.getModifiers() & Modifier.STATIC) == Modifier.STATIC;
    }

    public static String getShortName(Reference ref) {
        if (ref.isAnnotationPresent(ArgOptions.class)) {
            var opt = ref.getAnnotation(ArgOptions.class);
            if (opt.shortChar() != '\0') {
                return "" + opt.shortChar();
            }
        }
        return "";
    }

    public static char getShortChar(Reference ref) {
        if (ref.isAnnotationPresent(ArgOptions.class)) {
            var opt = ref.getAnnotation(ArgOptions.class);
            if (opt.shortChar() != '\0') {
                return opt.shortChar();
            }
        }
        return '\0';
    }

    public static String getUsage(Reference ref) {
        if (ref.isAnnotationPresent(ArgOptions.class)) {
            var usage = ref.getAnnotation(ArgOptions.class).usage();
            if (!usage.isEmpty()) {
                return usage;
            }
        }
        return ref.getUsage();
    }

    public static Supplier<Map<Object, Object>> createMapSupplier(Class<?> type) {
        if (type == SortedMap.class ||
            type == NavigableMap.class) {
            return TreeMap::new;
        } else if (type == Map.class) {
            return HashMap::new;
        }

        if ((type.getModifiers() & Modifier.ABSTRACT) == 0 &&
            !type.isInterface()) {
            try {
                @SuppressWarnings("unchecked")
                var constructor = (Constructor<Map<Object, Object>>) type.getConstructor();
                if (!constructor.canAccess(null)) {
                    throw new IllegalArgumentException("Unable to access default constructor for " + type.getName());
                }
                return () -> {
                    try {
                        return constructor.newInstance();
                    } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
                        throw new ArgException("Unable to instantiate %s", type.getName(), e);
                    }
                };
            } catch (NoSuchMethodException e) {
                throw new IllegalArgumentException("Unable to find default constructor for " + type.getName(), e);
            }
        }

        throw new IllegalArgumentException("Unable to determine constructor for " + type.getName());
    }

    public static Supplier<Collection<Object>> createCollectionSupplier(Class<?> type) {
        if (type == NavigableSet.class ||
            type == SortedSet.class) {
            return TreeSet::new;
        } else if (type == Set.class) {
            return HashSet::new;
        } else if (type == List.class ||
                   type == Collection.class) {
            return ArrayList::new;
        }

        if ((type.getModifiers() & Modifier.ABSTRACT) == 0 &&
            !type.isInterface()) {
            try {
                @SuppressWarnings("unchecked")
                var constructor = (Constructor<Collection<Object>>) type.getConstructor();
                if (!constructor.canAccess(null)) {
                    throw new IllegalArgumentException("Unable to access default constructor for " + type.getName());
                }
                return () -> {
                    try {
                        return constructor.newInstance();
                    } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
                        throw new ArgException("Unable to instantiate %s", type.getName(), e);
                    }
                };
            } catch (NoSuchMethodException e) {
                throw new IllegalArgumentException("Unable to find default constructor for " + type.getName(), e);
            }
        }
        throw new IllegalArgumentException("Unable to determine constructor for " + type.getName());
    }

    @SuppressWarnings("unchecked")
    public static ValueParser<Object> getKeyParser(Class<?> type, Reference ref) {
        if (ref.isAnnotationPresent(ArgKeyParser.class)) {
            return (ValueParser<Object>) getAnnotationParser(ref.getAnnotation(ArgKeyParser.class).value());
        }
        if (type.isAnnotationPresent(ArgValueParser.class)) {
            return (ValueParser<Object>) getAnnotationParser(type.getAnnotation(ArgValueParser.class).value());
        }
        return (ValueParser<Object>) ValueParser.defaultParser(type).orElse(null);
    }

    @SuppressWarnings("unchecked")
    public static ValueParser<Object> getValueParser(Class<?> type, Reference ref) {
        if (ref.isAnnotationPresent(ArgValueParser.class)) {
            return (ValueParser<Object>) getAnnotationParser(ref.getAnnotation(ArgValueParser.class).value());
        }
        if (type.isAnnotationPresent(ArgValueParser.class)) {
            return (ValueParser<Object>) getAnnotationParser(type.getAnnotation(ArgValueParser.class).value());
        }
        return (ValueParser<Object>) ValueParser.defaultParser(type).orElse(null);
    }

    public static boolean isDeclared(Reference ref) {
        return ref.isAnnotationPresent(ArgOptions.class) ||
               ref.isAnnotationPresent(ArgKeyParser.class) ||
               ref.isAnnotationPresent(ArgValueParser.class) ||
               ref.isAnnotationPresent(ArgIsRequired.class) ||
               ref.isAnnotationPresent(ArgIsRepeated.class) ||
               ref.isAnnotationPresent(ArgIsHidden.class);
    }

    public static boolean isDeclared(AccessibleObject object) {
        return object.isAnnotationPresent(ArgOptions.class) ||
               object.isAnnotationPresent(ArgKeyParser.class) ||
               object.isAnnotationPresent(ArgValueParser.class) ||
               object.isAnnotationPresent(ArgIsRequired.class) ||
               object.isAnnotationPresent(ArgIsRepeated.class) ||
               object.isAnnotationPresent(ArgIsHidden.class);
    }

    public static ValueParser<?> getAnnotationParser(Class<? extends ValueParser<?>> type) {
        try {
            return type.getConstructor().newInstance();
        } catch (InstantiationException |
                 IllegalAccessException |
                 NoSuchMethodException |
                 InvocationTargetException e) {
            throw new IllegalArgumentException("Invalid value parser: " + type.getName(), e);
        }
    }

    public static ValueParser<?> getValueParser(Class<?> type) {
        return ValueParser.defaultParser(type).orElse(null);
    }
}